在C中将PPM从RGB转换为HSL

对于我的学术课程,我需要有关RGB图像文件中的直方图均衡的帮助。

我查看了我之前关于直方图均衡的代码示例,但我没有找到任何有关此问题的线索。 我从未练习过直方图均衡示例,即RGB图像。

图像是PPM文件。 因此,我们需要将文件从RGB转换为YCbCr,从RGB转换为HSI。

然后,我们需要在图像为YCbCr和HSI格式时进行直方图均衡。

之后,我们需要再次将PPM文件转换为RGB格式。 而已。

*void write_image function is writing the data to the pnr.ppm* *void get_image_data function is getting the image that is mandrill1.ppm* 

我们只需要指定代码:

 #include #include #include #include #include #include #include  #include  #include  #define PI 3.1415926535897932384626433832795 struct ppm_header { char pgmtype1; char pgmtype2; int pwidth; int pheight; int pmax; }; struct ppm_file { struct ppm_header *pheader; unsigned char *rdata,*gdata,*bdata; }; void get_image_data(char *filename,struct ppm_file *image); void write_image(char *filename,struct ppm_file *image); main() { struct ppm_file resim; get_image_data("mandrill1.ppm",&resim); printf("pgmtype...=%c%c\n",resim.pheader->pgmtype1,resim.pheader->pgmtype2); printf("width...=%d\n",resim.pheader->pwidth); printf("height...=%d\n",resim.pheader->pheight); printf("max gray level...=%d\n",resim.pheader->pmax); write_image("pnr.ppm",&resim); return 0; } void write_image(char *filename,struct ppm_file *image) { FILE *fp; int i,max=0; fp=fopen(filename,"wb"); fputc(image->pheader->pgmtype1,fp); fputc(image->pheader->pgmtype2,fp); fputc('\n',fp); fprintf(fp,"%d %d\n",image->pheader->pwidth,image->pheader->pheight); fprintf(fp,"%d\n",255/*max*/); for(i=0;ipheader->pwidth*image->pheader->pheight;i++) { fwrite(&image->rdata[i],1,1,fp); fwrite(&image->gdata[i],1,1,fp); fwrite(&image->bdata[i],1,1,fp); } fclose(fp); } void get_image_data(char *filename, struct ppm_file *image ) { FILE* fp; int i=0; char temp[256]; image->pheader=(struct ppm_header *)malloc(sizeof(struct ppm_header)); fp = fopen(filename, "rb" ); if (fp==NULL) { printf("Dosya acilamadi: %s.\n\n", filename); exit(1); } printf ("Okunan PPM dosyasi : %s...\n", filename); fscanf (fp, "%s", temp); if (strcmp(temp, "P6") == 0) { image->pheader->pgmtype1=temp[0]; image->pheader->pgmtype2=temp[1]; fscanf (fp, "%s", temp); if (temp[0]=='#') { while(fgetc(fp)!='\n'); fscanf (fp, "%d %d\n",&image->pheader->pwidth,&image->pheader->pheight); fscanf (fp, "%d\n", &image->pheader->pmax); } else { sscanf (temp, "%d", &image->pheader->pwidth); fscanf (fp, "%d", &image->pheader->pheight); fscanf (fp, "%d", &image->pheader->pmax); } image->rdata=(unsigned char *)malloc(image->pheader->pheight*image->pheader->pwidth*sizeof(unsigned char)); image->gdata=(unsigned char *)malloc(image->pheader->pheight*image->pheader->pwidth*sizeof(unsigned char)); image->bdata=(unsigned char *)malloc(image->pheader->pheight*image->pheader->pwidth*sizeof(unsigned char)); if (image->rdata==NULL) printf("bellek problemi...\n"); for(i=0;ipheader->pwidth*image->pheader->pheight;i++) { fread(&image->rdata[i],1,1,fp); fread(&image->gdata[i],1,1,fp); fread(&image->bdata[i],1,1,fp); } } else { printf ("\nHata Resim dosyasi PGM P6 formatinda degil"); exit(1); } fclose(fp); } 

让我们看看算法级别的问题。

  1. 您的get_image_data()无法正确处理PPM格式(Netpbm P6格式)。 就像其他二进制Netpbm格式–PBM,PGM,PPM,PNM – 一样,P6格式可以在最大组件值之前有注释(后面跟着一个新行, \0 ,后跟二进制数据)。

    (虽然维基百科Netpbm格式文章说即使在最大组件值之后也可以发表评论,这使得二进制格式变得模糊,因为解析器无法判断# (binary \x23 )是否是图像数据的一部分或者是因此,许多实用程序根本不允许在最后一个标题值之后发表评论,以保持格式的明确性。)

    要在C中正确解析二进制Netpbm格式,您需要首先读取文件的两个第一个字符或流,以检测格式。 其余的标题值都是非负整数,可以使用单个函数进行扫描,该函数也会跳过注释行。 如果我们使用CI / O设施,那么我们可以使用单字符后推function轻松编写该function; 在伪代码中,

     Function pnm_value(stream): Read one character from stream into c Loop: If c == EOF: Premature end of input; fail. If c == '#': Loop: Read one character from stream into c If c is not EOF or '\n', break loop End loop Continue at the start of the outer loop If c is a '\t', '\n', '\v', '\f', '\r', or ' ': Read one character from stream into c Continue at the start of the outer loop Otherwise break loop End loop If c is not a digit: Invalid input; fail Value = 0 While c is a digit: OldValue = Value Value = 10*value + (value of digit c) If (Value / 10 != OldValue): Value is too large; fail Read one character from stream into c End While If c is not EOF: Push (unget) c back to stream Return Value End function 

    在使用上述函数读取头字段后,对于二进制格式,您应该从流或文件中再读取一个字符,并且它必须是换行符\n才能使格式有效(并且明确无误)。

    可以使用getc(stream)在C中读取二进制数据; 没有必要使用fread() 。 这样更快,因为getc()通常是一个宏(可以不止一次地评估它的参数, stream ;它在这种特殊情况下不会损害任何东西)。

    对于P6格式,如果标题中的maxval字段(第三个值,在widthheight以像素为单位)最多为255,则有width × height x3个数据字符; 首先是红色成分,然后是绿色,最后是蓝色。

    如果maxval字段是256到65535,则P6格式中有width × height ×6个数据字符。 在每组六个字符中,前两个是红色,后两个是绿色,后两个是蓝色; 首先是最重要的字节。

  2. 对于高动态范围图像,包括探索不同的颜色空间,我建议使用每像素64位,每个组件20位的数据结构。 例如,

     typedef struct { size_t width; size_t height; size_t stride; /* Usually == width */ uint64_t *pixel; /* i = y*stride + x */ void *data; /* Origin of allocated pixel data */ } image; 

    如果您希望例如将滤波器内核应用于数据,则可以使用单独的步幅分配带有额外像素的像素图; 那么你不需要以任何特殊方式处理边界像素,只需将它们初始化为适当的颜色(通常复制图像边缘像素)。

    将PNM文件读入上述数据结构时,不是保存从文件中读取的任何值,而是进行计算

     component = (1048575 * file_component) / maxvalue; 

    对于从文件中读取的每个颜色分量。 这可确保每个组件的组件值始终在0到1048575之间,而不管文件中保存的组件的精度如何。

    在实践中,要将P6 / PPM文件中的像素读取为每个组件像素值为64位,20位,您可以使用例如

     uint64_t pixel; uint64_t red, green, blue; if (maxval > 255) { red = (getc(stream) & 255) << 8; red += getc(stream) & 255; green = (getc(stream) & 255) << 8; green += getc(stream) & 255; blue = (getc(stream) & 255) << 8; blue += getc(stream) & 255; } else { red = getc(stream) & 255; green = getc(stream) & 255; blue = getc(stream) & 255; } pixel = ((uint64_t)((1048575 * red) / maxval) << 40) | ((uint64_t)((1048575 * green) / maxval) << 20) | (uint64_t)((1048575 * blue) / maxval); 

    在您的特定情况下,这不是很重要,实际上您可以只读取整个数据(如果maxval<=255则为3*width*height字符,如果maxval>=256则为6*width*height字符),不进行转换。

  3. 无需显式地将图像数据转换为另一种颜色模型:您可以在读取文件时计算直方图,并在写入输出文件时调整颜色。

    直方图均衡是一种操作,其中使用使直方图尽可能平坦的简单函数分别缩放每个像素的每个颜色分量。 您可以使用自己喜欢的搜索引擎找到更多实用的示例和解释( 如此PDF )。

    当您读取像素的红色,绿色和蓝色分量,并将它们缩放到0..1048575范围(含)时,您可以使用它们上显示的公式计算Y / Cb / Cr和H / S / I.例如,相应的维基百科文章。 您可以使用整数或浮点数进行计算,但请记住,您需要确定直方图的大小(因此最终将每个组件转换为整数)。 为避免颜色转换中的量化错误,您应该在这些“临时”颜色空间中使用每个组件的更多位 - 例如,24位听起来不错。

    无论您使用哪种颜色空间进行直方图均衡,您最有可能最终将直方图转换为组件映射; 也就是说,不是元素c[i]描述具有值i该颜色分量的像素的数量,而是对其进行变换,使得c[i]产生原始颜色分量值i的均衡颜色分量值。

  4. 如果有三个颜色分量映射,则可以保存输出文件。

    对于每个像素,将红色,绿色和蓝色分量转换为用于直方图均衡的颜色空间。 您可以分别映射每个组件。 然后,将颜色分量转换回RGB模型,最后保存像素红色,绿色和蓝色分量。

    如果原始文件使用maxval为255或更小,则使用maxval为255(每个颜色组件一个char)保存文件。 如果原始文件使用较大的maxval,则使用最大值65535(每个颜色分量两个字符;最重要的字节优先)。 或者,更好的是,让用户在运行时指定最终的maxval。

  5. 如果输入来自文件,您甚至不需要记住图像的像素数据,因为您可以简单地读取它两次。

    但请注意,大多数处理Netpbm文件的实用程序都是为了便于管道而编写的。 实际上,这是我向其他用户展示的最常见的使用类型,例如需要操纵图像中的特定颜色或灰度级。 因此,通常建议将像素数据保留在内存中,并将所有错误和信息仅写入标准错误。

我估计在SLOC中计数,你的程序将主要包括解析命令行参数,读取输入文件和写入输出文件所需的代码。 色彩空间转换并不困难,也不长,直方图的内容几乎是微不足道的。 (毕竟,您只是计算特定颜色分量在图像中出现的次数。)

即便如此,最重要的是一次一步地编写程序。 首先,它限制了发生错误时需要检查的代码区域。

我喜欢使用临时测试程序(有些人可能会调用这些unit testing )来分别实现每个部分,然后再将它们组合到程序中,而不是单个程序。 在你的情况下,我肯定会首先编写read-PPM-P6-image和write-PPM-P6-image函数,并测试它们,例如将图像旋转180度(左上角将成为右下角) ),或类似的东西。 当您使用它时,您可以在Gimp,Netpbm工具,eog或您可能使用的任何应用程序和实用程序中打开生成的PPM / P6映像, 然后才能进展到问题的其余部分。

此外,使您的代码易于阅读。 这意味着一致的缩进。 还有很多评论:不是描述代码的作用,而是描述代码试图解决的问题; 它试图完成什么任务

就目前而言,post中显示的代码是一些错误的东西。 你的“问题”中甚至没有明确的问题! 如果你一步一步地进步,分别实现和测试每个部分,并且不要让你的代码变得丑陋,那么你将永远不会陷入这种境地。 相反,如果你迷路了,你可以提出一些智能问题,比如如何最好地合并你的不同部分。 (通常涉及使用不同的视图,不同的“范例”重写部件,但这是一件好事,因为这样你就会了解为什么不同的视图和不同的工具在不同的情况下有用,以及如何确定情况。)