在 C 中将 PPM 从 RGB 转换为 HSL [关闭]

2023-12-19

我需要有关 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<stdio.h>
#include<conio.h>
#include<stdlib.h>
#include<math.h>
#include<ctype.h>
#include<string.h>
#include <fcntl.h>
#include <malloc.h>
#include <math.h>
#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;i<image->pheader->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;i<image->pheader->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. Your get_image_data()无法正确处理 PPM 格式(Netpbm P6 格式)。就像其他二进制 Netpbm 格式(PBM、PGM、PPM、PNM)一样,P6 格式可以在最大组件值之前有注释(后面紧跟一个换行符,\0,后跟二进制数据)。

    (虽然维基百科Netpbm 格式 https://en.wikipedia.org/wiki/Netpbm_format文章说,即使在最大组件值之后也可以进行注释,这使得二进制格式不明确,因为解析器无法判断是否是#(二进制\x23) 是图像数据的一部分或注释的开头。因此,许多实用程序根本不允许在最后一个标头值之后添加注释,以保持格式明确。)

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

    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格式有效(且明确)。

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

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

    If the maxval字段为256到65535,有width×height×6 个字符的 P6 格式数据。每组六个字符中,前两个是红色,接下来的两个是绿色,最后两个是蓝色;首先是最高有效字节。
     

  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);
    

    在您的特定情况下,这并不重要,实际上您可以读取整个数据(3*width*height字符如果maxval<=255, 6*width*height字符如果maxval>=256)按原样,无需转换。
     

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

    直方图均衡化 https://en.wikipedia.org/wiki/Histogram_equalization是一种操作,其中每个像素的每个颜色分量都单独缩放,使用一个简单的函数使直方图尽可能平坦。您可以找到更多实际示例和解释(例如this PDF https://www.math.uci.edu/icamp/courses/math77c/demos/hist_eq.pdf)与您最喜欢的搜索引擎。

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

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

  4. 当您获得三个颜色分量映射后,您可以保存输出文件。

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

    如果原始文件使用的 maxval 为 255 或更少,则使用 maxval 255(每个颜色分量一个字符)保存文件。如果原始文件使用较大的 maxval,则使用 maxval 65535(每个颜色分量两个字符;最高有效字节在前)。或者,更好的是,让用户在运行时指定结果 maxval。
     

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

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

我估计算进去SLOC https://en.wikipedia.org/wiki/Source_lines_of_code,您的程序将主要由解析命令行参数、读取输入文件和写入输出文件所需的代码组成。色彩空间转换并不困难也不长,直方图的内容也几乎微不足道。 (毕竟,您只是计算特定颜色成分在图像中出现的次数。)

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

我不喜欢使用单个程序,而是喜欢使用临时测试程序(有些人可能称之为单元测试 https://en.wikipedia.org/wiki/Unit_testing)在将它们组合到适当的程序中之前分别实现每个部分。在你的情况下,我肯定会首先编写 read-PPM-P6-image 和 write-PPM-P6-image 函数,并测试它们,例如通过将图像旋转 180 度(因此左上角将变成右下角) ),或类似的东西。当您让它工作时,您可以在 Gimp、Netpbm 工具、eog 或您可能使用的任何应用程序和实用程序中打开生成的 PPM/P6 图像,只有那时问题的其余部分的进展。

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

就目前情况而言,您帖子中显示的代码是一堆乱七八糟的东西。你的‘问题’连一个明确的问题都没有!如果您一步一步地进步,分别实现和测试每个部分,并且不让您的代码变得一团糟,那么您永远不会陷入这种情况。相反,您可以提出一些明智的问题,例如如果您迷路了,如何最好地合并不同的部分。 (通常这涉及使用不同的视图、不同的“范式”重写部分,但这是一件好事,因为这样你就会了解为什么不同的视图和不同的工具在不同的情况下有用,以及如何确定情况。)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在 C 中将 PPM 从 RGB 转换为 HSL [关闭] 的相关文章

随机推荐