【问题标题】:Converting a PPM from RGB to HSL in C [closed]在 C 中将 PPM 从 RGB 转换为 HSL [关闭]
【发布时间】:2017-09-25 19:31:07
【问题描述】:

我需要有关 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);
}

【问题讨论】:

  • 恐怕你的问题是乞求被否决和关闭。请消除卑躬屈膝,并将您的问题范围缩小到可管理的范围内。
  • 即使是 PPM 阅读器功能 (get_image_data()) 也不正确。您确定自己应该通过课程,但没有达到课程的要求吗?我不是。
  • a) 如果我们帮助您,我们就不会互相帮助,因为您没有为我们做任何事情。 b)如果这会影响您的期末考试,我真的很想知道您最初是如何做到这一点的,因为您的帖子质量和风格令人难以置信。 c) 你甚至没有提出任何问题或提出你遇到的问题。你刚刚说了你的任务是什么并发布了一些代码。你希望我们用它做什么?你宁愿在生活中赚钱。不要乞求他们......
  • 你们会坐在一起作为一个团队解决这个问题,而不是在家庭作业中发送垃圾邮件吗?现在的学生有什么问题? stackoverflow.com/questions/43667345/…stackoverflow.com/questions/43660624/…
  • @raskolnikov:我不确定是否可以帮助您。你看,你来这里是为了找到一个简单的解决方案,它可以让你以最小的努力通过。从我的立场来看,这将是最糟糕的结果。再过一段时间,你就会成为一个自称是程序员的人,在我看来,这个领域已经被不合逻辑、不理性、懒惰、缺乏创造力的非思考者所淹没;在我们的文化中,人们比癌症更糟糕。如果你在这里失败了,你实际上可能不得不重新考虑你的生活策略!这可能是这里最好的结果。

标签: c image-processing histogram ppm


【解决方案1】:

让我们看看算法层面的问题。

  1. 您的get_image_data() 无法正确处理 PPM 格式(Netpbm P6 格式)。就像其他二进制 Netpbm 格式——PBM、PGM、PPM、PNM——一样,P6 格式可以在最大分量值之前有 cmets(紧跟一个换行符 \0,然后是二进制数据)。

    (虽然 Wikipedia 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 才能使格式有效(且明确)。

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

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

    如果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&lt;=2556*width*height 字符,如果maxval&gt;=256),无需转换。
     

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

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

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

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

  4. 当你有了三个颜色分量映射后,你就可以保存输出文件了。

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

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

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

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

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

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

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

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

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

【讨论】:

  • 感谢您的关注。我误会你了。对此感到抱歉。不过,我需要你来解决这个问题。我对 Piglet 的报价也对你有效。我的提议是金钱。如果你要解决我的问题,我正在考虑付钱给你,因为我的时间有限。这对你来说可能不是真实的想法。因为您对编程的想法是正确的。我支持你。但是,即使如此,我也需要解决问题。那么,您对此有何看法?你会帮我吗?我等你的答复。顺便说一句,我解决这个问题的时间有限。
  • @raskolnikov 老兄,这个家伙正在给你免费的建议,这样你就可以真正思考你的问题并自己解决问题。不要通过给他钱来侮辱他,这样他就会通过一些考试来推动你令人毛骨悚然的懒惰。
  • @raskolnikov:小猪是完全正确的。我宁愿吃食肉动物的粪便——这比任何食草动物的粪便都要糟糕——也不愿帮助某人通过教育假装自己的方式。 学习的能力和意愿是我们这个物种为数不多的可取之处之一。任何认为自己“高于”以艰苦的方式学习的人,除了痛苦、痛苦和失望之外什么都不值得。 “西伯利亚教。”
  • @NominalAnimal 伙计们。让我们一步一步地继续解决问题。 1-) 我需要将文件从 RGB 转换为 YCbCr 以及从 RGB 转换为 HSI。 2-) 我需要做直方图均衡。 3-) 我需要将文件从 HSI 和 YCbCr 转换为 RGB。 //// 我知道如何均衡灰度图像。我知道第二步。我不知道第一步。这就是为什么,你认为我很懒惰。你说得对。我无法彻底解释我的问题。对此感到抱歉。第一步我们能做什么?因为我不了解 NominalAnimal 的方式。很抱歉侮辱你们,伙计们。
  • @Piglet 上面的评论你也很感兴趣。
猜你喜欢
  • 2017-09-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-09-21
  • 1970-01-01
  • 1970-01-01
  • 2021-06-29
相关资源
最近更新 更多