在PP中将PPM从RGB转换为HSL [英] Converting a PPM from RGB to HSL in C

查看:137
本文介绍了在PP中将PPM从RGB转换为HSL的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I need a help about histogram equalization in RGB image files for my academic coursework.

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

I checked my previous code samples about histogram equalization and I did not find any clue about this issue. I've never practiced a histogram equalization example which is RGB image.

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

The image is PPM file. So, we need to convert the file from RGB to YCbCr and from RGB to HSI.

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

Then, we need to do the histogram equalization while the image is in the YCbCr and HSI format.

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

After that, we'll need to convert the PPM file to RGB format again. That's it.

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


推荐答案

让我们来看看问题所在算法级别。

Let's look at the problems at the algorithmic level.


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

  1. Your get_image_data() does not handle the PPM format (Netpbm P6 format) correctly. Just like the other binary Netpbm formats -- PBM, PGM, PPM, PNM --, the P6 format can have comments before the maximum component value (which is followed by exactly one newline, \0, followed by the binary data).

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

(Although the Wikipedia Netpbm format article says that a comment is possible even after the maximum component value, that makes the binary formats ambiquous, as a parser cannot tell whether a # (binary \x23) is part of the image data or the start of a comment. So, a lot of utilities do not allow a comment after the last header value at all, to keep the formats unambiguous.)

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

To parse the binary Netpbm formats correctly in C, you need to read the two first characters of the file or stream first, to detect the format. The rest of the header values are all nonnegative integers, and can be scanned using a single function that also skips comment lines. If we use the C I/O facilities, then we can write that function easily using the one-character pushback facility; in pseudocode,

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 才能使格式有效(并且明确无误)。

After you have read the header fields using the above function, for binary formats you should read one more character from the stream or file, and it must be a newline \n for the format to be valid (and unambiguous).

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

Binary data can be read in C using getc(stream); there is no need to use fread(). This is faster, because getc() is often a macro (that may evaluate its argument, stream, more than once; it does not harm anything in this particular case).

对于P6格式,如果 maxval 字段在标题(第三个值,在宽度高度之后),最多为255,有宽度×高度 x3个数据字符;首先是红色成分,然后是绿色,最后是蓝色。

For the P6 format, if the maxval field in the header (the third value, after width and height in pixels) is at most 255, there are width×heightx3 chars of data; red component first, then the green, and finally blue.

如果 maxval 字段是256到65535,那么是宽度×高度×P6格式的6个数据字符。在每组六个字符中,前两个是红色,后两个是绿色,后两个是蓝色;首先使用最重要的字节。
 

If the maxval field is 256 to 65535, there are width×height×6 chars of data in the P6 format. In each set of six characters, the first two are red, the next two green, and the last two blue components; with the most significant byte first.
 

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

For High Dynamic Range images, including exploration of different color spaces, I recommend using a data structure with 64 bits per pixel, 20 bits per component. For example,

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;

单独的步幅可让您为像素地图分配额外的像素,以防万一。将过滤器内核应用于数据;那么你不需要以任何特殊方式处理边界像素,只需将它们初始化为适当的颜色(通常复制图像边缘像素)。

A separate stride lets you allocate the pixel map with extra pixels, in case you wish to e.g. apply a filter kernel to the data; then you do not need to handle border pixels in any special way, just initialize them to appropriate colors (duplicating the image edge pixels, typically).

当读取PNM文件时上面的数据结构,而不是保存从文件中读取的任何值,你计算

When reading PNM files into the above data structure, instead of saving whatever value you read from the file, you compute

component = (1048575 * file_component) / maxvalue;

。这样可以确保每个组件的组件值始终在0到1048575之间,无论文件中保存的组件的精度如何。

for each color component read from the file. This ensures that you always have component values between 0 and 1048575 for each component, regardless of the precision of the component saved in the file.

实际上,读取像素从P6 / PPM文件到64位,每个元素20位像素值,您可以使用例如

In practice, to read a pixel from a P6/PPM file into a 64-bit, 20 bits per component pixel value, you could use e.g.

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 chars if maxval< = 255 6 * width * height chars if maxval> = 256 ),没有转换。
 

In your particular case, this is not really important, and indeed you could just read the entire data (3*width*height chars if maxval<=255, 6*width*height chars if maxval>=256) as is, without conversion.
 

无需明确地将图像数据转换为其他颜色模型:您可以在读取文件时计算直方图,并且在编写输出文件时调整颜色。

There is no need to convert the image data to another color model explicitly: you can compute the histograms while you read the file, and adjust the colors when writing the output file.

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

Histogram equalization is an operation where each color component for each pixel is scaled separately, using a simple function that makes the histograms as flat as possible. You can find more practical examples and explanations (like this PDF) with your favourite search engine.

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

When you read the red, green, and blue components for a pixel, and scale them to the 0..1048575 range (inclusive), you can calculate the Y/Cb/Cr and H/S/I using the formulae shown on their respective Wikipedia articles, for example. You can do the calculations using integers or floats, but remember that you need to decide the size of your histograms (and therefore eventually convert each component to integer). To avoid quantization error in the color conversions, you should use more bits per component in these "temporary" colorspaces -- say, 24 bits sounds good.

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

Whichever colorspace you do use for histogram equalization, you most likely end up converting the histogram into a component mapping; that is, rather than element c[i] describing the number of pixels having this color component of value i, you transform it so that c[i] yields the equalized color component value for original color component value i.
 

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

When you have the three color component mappings, you can save the output file.

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

For each pixel, you convert the red, green, and blue components to the colorspace you use for the histogram equalization. You map each of the components separately. Then, you convert the color components back to the RGB model, and finally save the pixel red, green, and blue components.

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

If the original file used a maxval of 255 or less, save the file using a maxval of 255 (and one char per color component). If the original file used a larger maxval, use a maxval of 65535 (and two chars per color component; most significant byte first). Or, better yet, let the user specify the resulting maxval at run time.
 

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

If the input is from a file, you don't even need to remember the pixel data for the image, as you can simply read it twice.

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

Note, however, that most utilities that process Netpbm files are written to allow easy piping. Indeed, that is the most common type of use that I show my fellow users that need e.g. manipulate specific colors or gray levels in an image. Because of this, it is typically recommended to keep the pixel data in memory, and write all errors and information to standard error only.
 

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

I would estimate that counting in SLOC, your program will mostly consist of the code needed to parse command-line arguments, read the input file, and write the output file. The colorspace conversions are not difficult nor long, and the histogram stuff is near trivial. (After all, you are just counting how many times a specific color component appears in the image.)

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

Even so, it is most important that you write your program one step at a time. For one, it limits the region of code you need to inspect when a bug occurs.

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

Rather than working on a single program, I like to use temporary test programs (some might call these unit tests) to implement each part separately, before combining them into the program proper. In your case, I would definitely write the read-PPM-P6-image and write-PPM-P6-image functions first, and test them, for example by rotating the image 180 degrees (so upper left corner will become the lower right corner), or something similar. When you get it working, and you can open your generated PPM/P6 images in Gimp, Netpbm tools, eog, or whatever applications and utilities you might use, only then progress to the rest of the problem.

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

Also, make your code easy to read. That means consistent indentation. And lots of comments: NOT describing what the code does, but describing what problem the code tries to solve; what task it tries to accomplish.

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

As it stands, the code shown in your post is a mismash of stuff. You do not even have a clear question in your 'question'! If you progress step by step, implementing and testing each part separately, and don't let your code become an ugly mess, you'll never end up in that situation. Instead, you can ask intelligent questions like how to best merge your disparate parts, if you get lost. (Often that involves rewriting a part using a different view, different "paradigm", but that is a good thing, because then you learn why different views and different tools are useful in different situations, and how to determine the situation.)

这篇关于在PP中将PPM从RGB转换为HSL的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆