迹忆客 专注技术分享

当前位置:主页 > 学无止境 > 编程语言 > C++ >

用 C++ 读取 PPM 文件

作者:迹忆客 最近更新:2023/08/24 浏览次数:

在本文中,我们将了解 PPM 文件并使用 C++ 读取它们。

我们将首先讨论并了解 PPM 文件格式。 稍后,我们将学习用 C++ 读取 PPM 文件的分步过程。


PPM文件

每个图像文件都有一个附加到图像数据的标头/元数据。 标头包含有关图像和文件的不同信息。

在每个图像文件的开头,都存在文件格式的标识。 例如,BM被写在每个BMP文件的开头。

标头的其余部分包含宽度、高度、颜色数量、图像类型(二进制、灰度、16 色、256 色、24 位颜色)、压缩、偏移等信息。

可移植像素图(PPM)格式是一种格式相对简单的彩色图像。 PPM 有两种变体:一种是 P3,另一种是 P6。

P3是ASCII格式,而P6是二进制格式; ASCII 格式通常比二进制格式占用更多空间。 大多数图像都包含大量数据; 因此,空间至关重要。

例如,100x100 的小图像有 10000 个像素。 对于24位彩色图像,每个像素占用3个字节; 因此,需要 30000 字节来存储图像。

在本教程中,我们将讨论 P6 格式。 P3相对来说比较容易阅读和理解; 但是,学习 P6 格式后,您会变得更容易。

P6 是 24 位彩色图像。 P6的表头相对来说更简单; 您可以将其与 BMP 格式进行比较。

PPM 文件格式有 3 个标题行,占用 15 个字节的空间。 第一行具有 PPM 的标识符/签名(P3 或 P6)。

第二行有图像的宽度和空间(用空格分隔)信息,第三行有最大颜色值信息(例如15或255)。

P6
111 132
255
U`6Xe8Xk8Ul9Tg:Ve<Wd7Wd5Td5N_0MY/NZ3P^5Ub5Wc4T`2R`4T`4[d6Yd7NY1CM,@J-FQ/O\2Vg8Ra5FW.?O+@M,BQ/:F-0:**5)*1(6@-CQ5=H1;H-

如前所述,前三行包含标题信息。 第一行的P6标识图像的类型。

程序员可以检查标识符并根据格式读取图像。

第二行将宽度描述为 111,高度描述为 132。第三行将 255 描述为最大颜色值。

从第四行开始,我们以二进制格式存储图像数据(以字符的形式呈现在屏幕上,其中一些可以识别,但不是全部)。

用 C++ 读取 PPM 文件 要读取PPM文件,首先,我们必须读取其文件头以获取有关宽度和高度的信息。 这很重要,因为我们必须相应地声明动态内存并读取图像的数据。

P6是二进制格式; 因此,我们必须以二进制格式打开图像并进行相应的读取:

FILE *read;
read = fopen("west_1.ppm","rb");

在第一行中,声明 FILE 指针以存储第二行中的文件的处理程序。 注意 fopen 函数中的第二个参数是 rb,其中 r 表示读取,b 表示二进制(即文件的二进制读取模式)。

如果您对二进制存储不满意,请阅读这篇有关二进制文件的文章。 打开文件后,您必须读取文件头。

这是代码:

unsigned char header[15];
fread( header , 15,  1, read);

成功读取标头后,下一步就是提取宽度和高度信息以进一步进行。 由于这些信息是以字符形式存储的,我们可以通过以下代码将字符信息一一读取并转换为整数值(也可以使用atoi函数):

int x=0;
for (pos=3 ;header[pos]!='\n' && header[pos]!=' ';pos++)
    x = x * 10 + (header[pos] - '0');

如前所述,标头的前两个字节包含 P6 值,第三个字节是下一行的分隔符。 因此,我们将从第 4 个元素开始,该元素从索引 3 开始。

我们逐个字符地读取以完成一个整数值(由空格字符或换行字符分隔)。 对于每个字符,我们必须减去 ASCII 值 0,因为数字的 ASCII 值从 48 或 32 十六进制(ASCII 值 0)开始。

例如,6的ASCII值换算为十进制为54; 因此,如果我们从 54 中减去 48,我们将得到 6。利用这个事实,我们可以从字符数组中获取整数值。

请参阅一个将字符数组 135 转换为等效整数的小示例。

integer x = 0 //initial integer value
x = x * 10 + 1 = 1
x = x * 10 + 3 = 13 //find out 10s by
x = x * 10 + 5 = 135 //find out 100s and x will have final integer value

获取宽度和高度信息后,我们将声明一个大小为宽 x 高 x 3 的动态数组,因为图像中有宽 x 高像素,每个图像有 3 个字节(红、绿、蓝,称为 RGB)。

unsigned char *image;
image = new unsigned char [width * height * 3];

我们使用 unsigned char 而不是 char,因为在简单 char 类型中,第一位用于符号(0 表示正数,1 表示负数)。

对于char来说,正值的最大范围是0到127,而我们要读取的信息是0到255。因此,我们需要一个unsigned char。

最后一步是读取图像,这非常简单。 只需将 fread 函数调用为:

fread( image , size,  1, file);//where size is width x height x 3

同样,第一个参数是一个用于存储图像数据的 char 数组,其大小已经很好地解释了。

可变图像具有所有像素 - 红色、绿色和蓝色值。 我们可以声明三个大小为宽 x 高的单独数组,或者我们可以通过记住第一个值是红色、第二个值是绿色、第三个值是蓝色来处理操作。

例如,如果我们想从图像中删除红色分量,我们必须在所有像素的第一个索引中分配0。

我们可以这样做:

for (i=0;i<size;i=i+3){
    image[i] = 0;

请注意 for 循环中的增量步骤,其中我们将 i 增加了 3 而不是 1。这是因为第二个和第三个值是绿色和蓝色,我们不想更改绿色和蓝色值。

我们希望您对阅读 PPM 文件有完整的了解。 最后,让我们看一个完整的C++程序来读取、修改和写入PPM文件:

#include <iostream>
#include <cstdio>

using namespace std;

void readPPMHeader(FILE *file, unsigned char *header){
    fread( header , 15,  1, file);
}
void readPPMImage(FILE *file, unsigned char *image, int size){
    fread( image , size,  1, file);
}
void writePPM(FILE *file, unsigned char *header, unsigned char *image, int size){
    fwrite( header , 15,  1, file);//writing header information
    fwrite( image , size,  1, file);//writing image information
}
void removeRed(unsigned char *image, unsigned char *withoutredimage, int size){
    int i;
    for (i=0;i<size;i=i+3){
        withoutredimage[i]=0;//red component is set to 0
        withoutredimage[i+1]=image[i+1];
        withoutredimage[i+2]=image[i+2];
    }
}
void removeGreen(unsigned char *image, unsigned char *withoutgreenimage, int size){
    int i;
    for (i=0;i<size;i=i+3){
        withoutgreenimage[i]=image[i];
        withoutgreenimage[i+1]=0;//green component is set to 0
        withoutgreenimage[i+2]=image[i+2];
    }
}
void removeBlue(unsigned char *image, unsigned char *withoutblueimage, int size){
    int i;
    for (i=0;i<size;i=i+3){
        withoutblueimage[i]=image[i];
        withoutblueimage[i+1]=image[i+1];
        withoutblueimage[i+2]=0;//blue component is set to 0
    }
}
//To extract width & height from header
int getDimension(unsigned char *header, int &pos){
    int dim=0;
    for ( ;header[pos]!='\n' && header[pos]!=' ';pos++)
        dim = dim * 10 + (header[pos] - '0');
    return dim;
}
int main(){
    FILE *read, *write1, *write2, *write3;
    read = fopen("west_1.ppm","rb");
    unsigned char header[15], *image;
    readPPMHeader(read, header);
    if (header[0]!='P' || header[1]!='6'){
        cout << "Wrong file format\n";
        return 0;
    }
    write1 = fopen("west_1_without_red.ppm","wb");
    write2 = fopen("west_1_without_green.ppm","wb");
    write3 = fopen("west_1_without_blue.ppm","wb");
    int width, height, clrs, pos = 3;
    width = getDimension(header, pos);
    pos++;
    height = getDimension(header, pos);
    cout << "Width:" << width << "\tHeight:" << height << '\n';
    image = new unsigned char [width * height * 3];
    unsigned char *withoutredimage, *withoutgreenimage, *withoutblueimage;
    withoutredimage  = new unsigned char [width * height * 3];
    withoutgreenimage  = new unsigned char [width * height * 3];
    withoutblueimage  = new unsigned char [width * height * 3];
    readPPMImage(read, image, width*height*3);
    removeRed(image, withoutredimage, width*height*3);
    writePPM(write1, header, withoutredimage, width * height * 3);
    removeGreen(image, withoutgreenimage, width*height*3);
    writePPM(write2, header, withoutgreenimage, width * height * 3);
    removeBlue(image, withoutblueimage, width*height*3);
    writePPM(write3, header, withoutblueimage, width * height * 3);
    fclose(read);
    fclose(write1);
    fclose(write2);
    fclose(write3);
    return 0;
}

在这个程序中,我们读取了1个PPM文件,修改后写入了3个PPM文件。

转载请发邮件至 1244347461@qq.com 进行申请,经作者同意之后,转载请以链接形式注明出处

本文地址:

相关文章

Arduino 中停止循环

发布时间:2024/03/13 浏览次数:444 分类:C++

可以使用 exit(0),无限循环和 Sleep_n0m1 库在 Arduino 中停止循环。

Arduino 复位

发布时间:2024/03/13 浏览次数:315 分类:C++

可以通过使用复位按钮,Softwarereset 库和 Adafruit SleepyDog 库来复位 Arduino。

Arduino 的字符转换为整型

发布时间:2024/03/13 浏览次数:181 分类:C++

可以使用简单的方法 toInt()函数和 Serial.parseInt()函数将 char 转换为 int。

Arduino 串口打印多个变量

发布时间:2024/03/13 浏览次数:381 分类:C++

可以使用 Serial.print()和 Serial.println()函数在串口监视器上显示变量值。

Arduino if 语句

发布时间:2024/03/13 浏览次数:123 分类:C++

可以使用 if 语句检查 Arduino 中的不同条件。

Arduino ICSP

发布时间:2024/03/13 浏览次数:214 分类:C++

ICSP 引脚用于两个 Arduino 之间的通信以及对 Arduino 引导加载程序进行编程。

使用 C++ 编程 Arduino

发布时间:2024/03/13 浏览次数:127 分类:C++

本教程将讨论使用 Arduino IDE 在 C++ 中对 Arduino 进行编程。

Arduino 中的子程序

发布时间:2024/03/13 浏览次数:168 分类:C++

可以通过在 Arduino 中声明函数来处理子程序。

扫一扫阅读全部技术教程

社交账号
  • https://www.github.com/onmpw
  • qq:1244347461

最新推荐

教程更新

热门标签

扫码一下
查看教程更方便