Преобразование PPM из RGB в HSL в C

Мне нужна помощь по выравниванию гистограммы в файлах изображений 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); } в #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 , а затем двоичные данные).

    (Хотя в статье формата Wikipedia Netpbm говорится, что комментарий возможен даже после максимального значения компонента, что делает двоичные форматы амбивалентными, поскольку синтаксический анализатор не может определить, является ли # (двоичный \x23 ) частью данных изображения или началом комментарий. Таким образом, многие утилиты не позволяют комментировать после последнего значения заголовка вообще, чтобы сохранить форматы недвусмысленными.)

    Чтобы корректно анализировать бинарные форматы Netpbm на C, сначала необходимо прочитать два первых символа файла или streamа, чтобы определить формат. Остальные значения заголовка представляют собой неотрицательные целые числа и могут сканироваться с использованием одной функции, которая также пропускает строки комментариев. Если мы используем средства CI / O, то мы можем легко написать эту функцию с использованием односимвольной функции pushback; в псевдокоде,

     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 

    После того, как вы прочитали поля заголовка, используя указанную выше функцию, для двоичных форматов вы должны прочитать еще один символ из streamа или файла, и он должен быть новой строкой \n для того, чтобы формат был действительным (и недвусмысленным).

    Двоичные данные можно прочитать в C с помощью getc(stream) ; нет необходимости использовать fread() . Это быстрее, потому что getc() часто является макросом (который может оценивать его аргумент, stream , не один раз, он ничего не вредит в данном конкретном случае).

    Для формата P6, если maxval в заголовке (третье значение, после width и height в пикселях) не maxval 255, width × height х3 символов данных; сначала красный компонент, затем зеленый и, наконец, синий.

    Если поле 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; 

    Отдельный шаг позволяет выделить карту пикселей с дополнительными пикселями, если вы хотите, например, применить kernel ​​фильтра к данным; то вам не нужно обрабатывать граничные пиксели каким-либо особым образом, просто инициализируйте их соответствующими цветами (обычно дублируя пиксели края изображения).

    При чтении 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 chars, если maxval<=255 , 6*width*height chars if maxval>=256 ), как есть, без преобразования ,

  3. Нет необходимости явно преобразовывать данные изображения в другую модель цвета: вы можете вычислить гистограммы во время чтения файла и настроить цвета при записи выходного файла.

    Уравнивание гистограммы - это операция, при которой каждый компонент цвета для каждого пикселя масштабируется отдельно, используя простую функцию, которая делает гистограммы максимально плоскими. Вы можете найти более практичные примеры и объяснения (например, этот PDF ) с помощью вашей любимой поисковой системы.

    Когда вы читаете красные, зеленые и синие компоненты для пикселя и масштабируете их до диапазона 0..1048575 (включительно), вы можете рассчитать Y / Cb / Cr и H / S / I, используя формулы, показанные на их соответствующие статьи в Википедии, например. Вы можете делать вычисления с использованием целых чисел или поплавков, но помните, что вам нужно определить размер ваших гистограмм (и, следовательно, в конечном итоге преобразовать каждый компонент в целое число). Чтобы избежать ошибки квантования в преобразованиях цветов, вы должны использовать больше бит на компонент в этих «временных» цветовых пространствах - скажем, 24 бит звучат хорошо.

    В любом цветовом пространстве, которое вы используете для выравнивания гистограммы, вы, скорее всего, закончите преобразование гистограммы в отображение компонентов; то есть вместо элемента c[i] описывающего количество пикселей, имеющих этот компонент цвета значения i , вы его преобразуете, так что c[i] дает значение выравниваемого цветового компонента для значения исходного цветового компонента i .

  4. Когда у вас есть три сопоставления цветовых компонентов, вы можете сохранить выходной файл.

    Для каждого пикселя вы преобразуете красные, зеленые и синие компоненты в цветовое пространство, которое вы используете для выравнивания гистограммы. Вы сопоставляете каждый компонент отдельно. Затем вы преобразуете цветовые компоненты обратно в модель RGB и, наконец, сохраните пиксельные красные, зеленые и синие компоненты.

    Если исходный файл использовал maxval 255 или меньше, сохраните файл, используя maxval 255 (и один символ для цветового компонента). Если в исходном файле использовался максимальный maxval, используйте maxval 65535 (и два символа на цветной компонент, первый старший байт). Или, еще лучше, пусть пользователь укажет полученный maxval во время выполнения.

  5. Если вход из файла, вам даже не нужно помнить пиксельные данные для изображения, так как вы можете просто прочитать его дважды.

    Обратите внимание, однако, что большинство утилит, обрабатывающих файлы Netpbm, записываются, чтобы обеспечить легкий доступ к трубопроводам. В самом деле, это самый распространенный вид использования, который я показываю своим друзьям, которым нужно, например, манипулировать определенными цветами или уровнями серого в изображении. Из-за этого обычно рекомендуется сохранять данные в пикселях в памяти и записывать все ошибки и информацию только в стандартную ошибку.

Я бы оценил, что подсчет в SLOC , ваша программа будет в основном состоять из кода, необходимого для анализа аргументов командной строки, чтения входного файла и записи выходного файла. Преобразования цветового пространства не являются трудными и длинными, а материал гистограммы почти тривиален. (В конце концов, вы просто подсчитываете, сколько раз на изображении появляется определенный компонент цвета.)

Тем не менее, самое главное, что вы пишете свою программу пошагово. Во-первых, он ограничивает область кода, которую вам нужно проверять, когда возникает ошибка.

Вместо того, чтобы работать над одной программой, мне нравится использовать временные тестовые программы (некоторые из них могут назвать эти модульные тесты ) для реализации каждой части отдельно, прежде чем объединять их в собственно программе. В вашем случае я определенно напишу сначала функции чтения-PPM-P6-изображения и записи-PPM-P6-изображения и проведу их, например, повернув изображение на 180 gradleусов (поэтому верхний левый угол станет нижним правым углом ), или что-то подобное. Когда вы его заработаете, вы можете открыть созданные вами изображения PPM / P6 в Gimp, инструментах Netpbm, eog или любых других приложениях и утилитах, которые вы можете использовать, а затем перейдите к остальной части проблемы.

Кроме того, упростите чтение кода. Это означает последовательный отступ. И много комментариев: НЕ описывая, что делает код, но описывая, какую проблему пытается решить код; какую задачу он пытается выполнить .

Как бы то ни было, код, показанный на вашем посту, - это беспорядок. У вас даже нет четкого вопроса в вашем «вопросе»! Если вы шаг за шагом продвигаетесь, внедряя и тестируя каждую часть отдельно, и не позволяйте вашему коду стать уродливым беспорядком, вы никогда не окажетесь в этой ситуации. Вместо этого вы можете задавать разумные вопросы, например, как наилучшим образом объединить ваши разрозненные части, если вы заблудитесь. (Часто это связано с переписыванием части с использованием другого представления, другой «парадигмы», но это хорошо, потому что тогда вы узнаете, почему разные взгляды и разные инструменты полезны в разных ситуациях и как определить ситуацию.)