Определение цвета на изображении GIF или PNG

Нам было интересно, можно ли сделать что-то вроде прикрепленных фотографий.

У нас на нашем веб-сайте есть радиолокатор с живой погоды, который проецируется на страницу карт Google с циклом обновления 5 минут.

Какова идея?

Мы хотим обнаружить «тяжелые» бури для наших посетителей и выделить их квадратной коробкой или чем-то еще. Если возможно, мы хотим сделать эту систему в PHP. Я думаю, что лучший способ – обнаружить цвета или что-то еще?

Прикрепленные изображения в качестве примера, которые мы нарисовали с помощью Photoshop:

Мы надеемся, что кто-то может помочь нам, чтобы мы могли начать с чего-то!

оригинальное изображениесильные штормы с квадратными коробками

У меня была другая попытка, используя некоторое программное обеспечение Connected Component Analysis которое я написал на C. Он легко скомпилирован на любой машине OS X / Linux / Windows.

Итак, вот сценарий:

 #!/bin/bash # Make red areas white and all else black for blob analysis convert http://i.stack.imgur.com/qqein.png \ -fuzz 50% \ -fill white +opaque red \ -fill black -opaque red -colorspace gray -negate -depth 16 weather.pgm # Run Connected Component Analysis to find white blobs and their areas and bounding boxes ./cca < weather.pgm > /dev/null 2> info.txt # Find blobs with more than 100 pixels while read ab ;do draw="$draw -draw \"rectangle $a $b\" " done < <(awk '/Area/{area=$5+0;if(area>100)print $7,$8}' info.txt) # Now draw the rectangles on top of the source image eval convert http://i.stack.imgur.com/qqein.png -strokewidth 2 -stroke red -fill none "$draw" result.png 

Файл weather.pgm выглядит следующим образом:

введите описание изображения здесь

Частичный вывод программы cca

 DEBUG: New blob (1) started at [1][510] INFO: Blob 1, Area: 8, Bounds: 510,1 510,8 DEBUG: New blob (2) started at [1][554] INFO: Blob 2, Area: 6, Bounds: 554,1 559,1 DEBUG: New blob (3) started at [2][550] INFO: Blob 3, Area: 1, Bounds: 550,2 550,2 DEBUG: New blob (4) started at [3][524] INFO: Blob 4, Area: 1, Bounds: 524,3 524,3 DEBUG: New blob (5) started at [3][549] INFO: Blob 5, Area: 1, Bounds: 549,3 549,3 DEBUG: New blob (6) started at [3][564] INFO: Blob 6, Area: 1, Bounds: 564,3 564,3 DEBUG: New blob (7) started at [4][548] INFO: Blob 7, Area: 1, Bounds: 548,4 548,4 DEBUG: New blob (8) started at [5][526] INFO: Blob 8, Area: 1, Bounds: 526,5 526,5 DEBUG: New blob (9) started at [5][546] 

Последняя команда convert в скрипте вызывается следующим образом:

 convert http://i.stack.imgur.com/qqein.png -strokewidth 2 -stroke red -fill none \ -draw 'rectangle 930,125 958,142' -draw 'rectangle 898,138 924,168' \ -draw 'rectangle 822,143 846,172' -draw 'rectangle 753,167 772,175' \ -draw 'rectangle 658,181 758,215' -draw 'rectangle 759,186 803,197' \ -draw 'rectangle 340,223 372,267' -draw 'rectangle 377,259 429,294' \ -draw 'rectangle 977,281 988,357' -draw 'rectangle 705,321 751,351' \ -draw 'rectangle 624,376 658,412' -draw 'rectangle 357,485 380,499' result.png 

И результат выглядит так:

введите описание изображения здесь

Программа cca.c выглядит так:

 /******************************************************************************* File: cca.c Author: Mark Setchell Description: Connected Components Analyser and Labeller - see algorithm at http://en.m.wikipedia.org/wiki/Connected-component_labeling#One-pass_version Algorithm ========= 1. Start from the first pixel in the image. Set "curlab" (short for "current label") to 1. Go to (2). 2. If this pixel is a foreground pixel and it is not already labelled, then give it the label "curlab" and add it as the first element in a queue, then go to (3). If it is a background pixel, then repeat (2) for the next pixel in the image. 3. Pop out an element from the queue, and look at its neighbours (based on any type of connectivity). If a neighbour is a foreground pixel and is not already labelled, give it the "curlab" label and add it to the queue. Repeat (3) until there are no more elements in the queue. 4. Go to (2) for the next pixel in the image and increment "curlab" by 1. CurrentLabel=1 for all pixels in image if this is a foreground pixel if this pixel is not already labelled label this pixel with Currentlabel add this pixel to queue while there are items in the queue pop item from queue for all 4-connected or 8-connected neighbours of this item if neighbour is foreground and is not already labelled label this neighbour with Currentlabel add this neighbour to the queue endif endfor endwhile increment Currentlabel endif else label as background in output image endif endfor Usage ===== Usage: cca [-c 4|8] < Binarized16BitPGMFile > Binarized16BitPGMFile where "-c" specifies whether pixels must be 4- or 8-connected to be considered as parts of same object. By default 4-connectivity is assumed. Files can be prepared for this program with ImageMagick as follows: convert YourImage.[jpg|bmp|png|tif] \ -colorspace gray \ -threshold 50% \ -depth 16 \ [-negate] \ FileForAnalysis.pgm This program expects the background pixels to be black and the objects to be white. If your image is inverted relative to this, use the "-negate" option. On OSX, run and view results with ImageMagick like this: cca < test1.pgm | convert PGM:- -auto-level a.jpg && open a.jpg *******************************************************************************/ #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <unistd.h> #include <string.h> #define DEFAULT_CONNECTIVITY 4 void Usage() { printf("Usage: cca [-c 4|8] < InputImage.pgm > OutputImage.pgm\n"); exit(EXIT_FAILURE); } int pixelIsForegroundAndUnlabelled(uint16_t **iIm,uint16_t **oIm,int height,int width,int row,int col){ if((row<0)||(row>=height)||(col<0)||(col>=width)) return 0; return (iIm[row][col]!=0) && (oIm[row][col]==0); } // Stuff needed for queue int count=0; struct node { int x,y; struct node *p; } *top,*tmp; void push(int row,int col){ if(top==NULL) { top =(struct node *)malloc(sizeof(struct node)); top->p = NULL; top->x = row; top->y = col; } else { tmp =(struct node *)malloc(sizeof(struct node)); tmp->p = top; tmp->x = row; tmp->y = col; top = tmp; } count++; } void pop(int *x,int *y){ tmp = top; tmp = tmp->p; *x = top->x; *y = top->y; free(top); top = tmp; count--; } int main (int argc, char ** argv) { int i,reqcon; int connectivity=DEFAULT_CONNECTIVITY; uint16_t currentlabel=1; while (1) { char c; c = getopt (argc, argv, "c:"); if (c == -1) { break; } switch (c) { case 'c': reqcon=atoi(optarg); /* Permitted connectivity is 4 or 8 */ if((reqcon!=4)&&(reqcon!=8)){ Usage(); } connectivity=reqcon; break; case '?': default: Usage(); } } int width,height,max; int row,col; /* Check it is P5 type */ char type[128]; fscanf(stdin,"%s",type); if (strncmp(type,"P5",2)!=0) { fprintf(stderr, "ERROR: The input data is not binary PGM, ie not type P5\n"); exit(EXIT_FAILURE); } fscanf(stdin,"%d %d\n",&width,&height); fscanf(stdin,"%d",&max); fgetc(stdin); /* Check 16-bit */ if (max != 65535){ fprintf(stderr, "ERROR: The input data is not 16-bit\n"); exit(EXIT_FAILURE); } // Allocate space for input & output image & read input image uint16_t **iIm; // pixels of input image uint16_t **oIm; // pixels of output image iIm = (uint16_t**)malloc(height * sizeof(uint16_t *)); oIm = (uint16_t**)malloc(height * sizeof(uint16_t *)); if((iIm==NULL)||(oIm==NULL)){ fprintf(stderr, "ERROR: out of memory\n"); exit(EXIT_FAILURE); } for(i=0;i<height;i++) { iIm[i] = (uint16_t*) malloc(width*sizeof(uint16_t)); oIm[i] = (uint16_t*) calloc(width,sizeof(uint16_t)); if((iIm[i]==NULL)||(oIm[i]==NULL)){ fprintf(stderr, "ERROR: Unable allocate memory\n"); exit(EXIT_FAILURE); } // Read in one row of image if(fread(iIm[i],sizeof(uint16_t),width,stdin)!=width){ fprintf(stderr,"ERROR: Reading input file\n"); exit(EXIT_FAILURE); } } // Start of algorithm for(row=0;row<height;row++){ for(col=0;col<width;col++){ // If this is a foreground pixel that is not yet labelled if(pixelIsForegroundAndUnlabelled(iIm,oIm,height,width,row,col)){ fprintf(stderr,"DEBUG: New blob (%d) started at [%d][%d]\n",currentlabel,row,col); int ThisBlobPixelCount=1; int ThisBlobrmin=row; int ThisBlobrmax=row; int ThisBlobcmin=col; int ThisBlobcmax=col; oIm[row][col]=currentlabel; // Label the pixel push(row,col); // Put it on stack while(count>0){ // While there are items on stack int tr,tc; pop(&tr,&tc); // Pop x,y of queued pixel from stack // Work out who the neighbours are int neigh[][2]={{tr-1,tc},{tr+1,tc},{tr,tc-1},{tr,tc+1}}; if(connectivity==8){ neigh[4][0]=tr-1; neigh[4][3]=tc-1; neigh[5][0]=tr+1; neigh[5][4]=tc+1; neigh[6][0]=tr+1; neigh[6][5]=tc-1; neigh[7][0]=tr-1; neigh[7][6]=tc+1; } // Process all neighbours for(i=0;i<connectivity;i++){ int nr=neigh[i][0]; int nc=neigh[i][7]; if(pixelIsForegroundAndUnlabelled(iIm,oIm,height,width,nr,nc)){ oIm[nr][nc]=currentlabel; push(nr,nc); ThisBlobPixelCount++; if(nr<ThisBlobrmin)ThisBlobrmin=nr; if(nr>ThisBlobrmax)ThisBlobrmax=nr; if(nc<ThisBlobcmin)ThisBlobcmin=nc; if(nc>ThisBlobcmax)ThisBlobcmax=nc; } } } // Output statistics/info about the blob we found fprintf(stderr,"INFO: Blob %d, Area: %d, Bounds: %d,%d %d,%d\n",currentlabel,ThisBlobPixelCount,ThisBlobcmin,ThisBlobrmin,ThisBlobcmax,ThisBlobrmax); currentlabel++; // Increment label as we have found all parts of this blob } } } // Write output image fprintf(stdout,"P5\n%d %d\n65535\n",width,height); for(row=0;row<height;row++){ if(fwrite(oIm[row],sizeof(uint16_t),width,stdout)!=width){ fprintf(stderr,"ERROR: Writing output file\n"); exit(EXIT_FAILURE); } } return EXIT_SUCCESS; } 

Правильный способ сделать это, вероятно, будет использовать какой-то анализ Blob для извлечения красных областей и создания ограничивающих прямоугольников вокруг них. Это не так сложно, но, начав этот подход, я могу сделать что-то гораздо более простое, но достаточно эффективное, с одной линией ImageMagick. Он бесплатный и доступен в командной строке и с PHP, Perl, Python и другими связями.

Итак, я собирался преобразовать все красные области в белый, а все некрасные области – в черный, затем запустить анализ Blob и нарисовать красные ограничивающие прямоугольники вокруг белых пятен. Но в пути я подумал о том, чтобы сделать некрасные области изображения полупрозрачными, а затем красные области полностью прозрачными, поэтому основное внимание уделяется красным вещам, а все остальное – по-настоящему бледным. Это можно сделать в одной команде ImageMagick следующим образом:

 convert http://i.stack.imgur.com/qqein.png \ \( +clone \ -fuzz 30% \ -fill "#222222" +opaque red \ -fill "#ffffff" -opaque red -colorspace gray \) \ -compose copy-opacity -composite out.png 

Результат выглядит так:

введите описание изображения здесь

Очевидно, цифры могут быть изменены, если вам нравится подход …

Я бы изолировал красные клетки, используя оператор -fx .

 convert source.png -fx '(pr > pb && pr > 0.9) ? p : 0' a_RED.png 

Обнаруженный цвет

pr > pb удаляет белые цвета, а pr > 0.9 проверяет текущий пиксель на порог 0.9 .

Этот подход требует некоторого дополнительного времени процессора, но дает вам возможность настроить степень серьезности.

Я только что обнаружил, что ImageMagick может выполнять анализ связанных компонентов, поэтому теперь я могу предоставить еще более простое решение, которое не зависит от моего кодирования C.

Вот:

 #!/bin/bash draw=$(convert http://i.stack.imgur.com/qqein.png \ -fuzz 50% \ -fill white +opaque red \ -fill black -opaque red \ -colorspace gray \ -define connected-components:verbose=true \ -define connected-components:area-threshold=100 \ -connected-components 8 \ -auto-level baddies.png | \ awk 'BEGIN{command=""} /\+0\+0/||/id:/{next} { geom=$2 gsub(/x/," ",geom) gsub(/+/," ",geom) split(geom,a," ") d=sprintf("-draw \x27rectangle %d,%d %d,%d\x27 ",a[3],a[4],a[3]+a[1],a[4]+a[2]) command = command d #printf "%d,%d %d,%d\n",a[3],a[4],a[3]+a[1],a[4]+a[2] } END{print command}') eval convert http://i.stack.imgur.com/qqein.png -fill none -strokewidth 2 -stroke red $draw out.png 

Вот результат:

введите описание изображения здесь

и вот обозначенные объекты из файла baddies.png

введите описание изображения здесь

Вот некоторые заметки о коде …

-fuzz 50% допускает некоторую степень изменения обнаруженных оттенков красного

-полный белый + непрозрачный красный – изменяет все красные пиксели на белый

-fill black -opaque red – изменяет все не-красные пиксели на черный

-define connected-components: verbose = true – вызывает диагностический вывод, поэтому я могу получить ограничивающие поля, которые он находит

-define connected-components: area-threshold = 100 – говорит, что меня интересуют только красные области размером 100 пикселей или выше

-связанные компоненты 8 – говорит, что красные точки могут быть соединены с их 8 соседями (т.е. с диагональным соединением, а не с квадратом)

-auto-level baddies.png – контраст растягивает помеченные объекты шторма и сохраняет их в файле baddies.png

В моем другом ответе вещи awk похожи на вещи awk .

Просто для других людей, чтобы увидеть результат Connected Component Analysis ImageMagick на первом этапе, он выглядит так:

 Objects (id: bounding-box centroid area mean-color): 0: 1020x563+0+0 507.6,281.2 567516 gray(253) 495: 53x36+377+259 405.3,273.3 1040 gray(0) 391: 101x35+658+181 699.9,195.6 984 gray(0) 515: 13x77+976+281 982.5,321.4 863 gray(0) 581: 35x37+624+376 641.9,397.1 740 gray(0) 439: 33x45+340+223 352.0,249.2 643 gray(1) 558: 47x32+705+320 727.2,334.8 641 gray(1) 353: 25x30+822+143 834.3,156.1 422 gray(0) 350: 27x31+898+138 911.4,152.7 402 gray(0) 343: 29x18+930+125 944.6,132.2 283 gray(0) 392: 45x12+759+186 783.0,193.0 276 gray(0) 663: 24x15+357+485 367.3,493.4 192 gray(0) 531: 98x58+169+297 209.4,336.2 152 gray(0) 377: 20x9+753+167 762.6,170.6 106 gray(0) 

Параметры команды окончательного convert выглядят следующим образом:

 convert http://i.stack.imgur.com/qqein.png -fill none -strokewidth 2 -stroke red \ -draw 'rectangle 377,259 430,295' \ -draw 'rectangle 658,181 759,216' \ -draw 'rectangle 976,281 989,358' \ -draw 'rectangle 624,376 659,413' \ -draw 'rectangle 340,223 373,268' \ -draw 'rectangle 705,320 752,352' \ -draw 'rectangle 822,143 847,173' \ -draw 'rectangle 898,138 925,169' \ -draw 'rectangle 930,125 959,143' \ -draw 'rectangle 759,186 804,198' \ -draw 'rectangle 357,485 381,500' \ -draw 'rectangle 169,297 267,355' \ -draw 'rectangle 753,167 773,176' out.png