Need help quantizing palette colors: java - Android

I am developing a project on Android, for this I have to filter up to 5 different colors, including the background color (approximately white). I canโ€™t use advanced mathematical algorithms because I donโ€™t know too much mathematics (and Iโ€™m completely mistaken), but with some simple logical algorithms that I received partly due to trial and error, partly logical, I was able to achieve 70% . But I need suggestions on how to make it work 100%.

To test the algorithm, I scribbled random words / letters with 4 different colored pens on plain white (5th color) paper and wrote code to decode the colors. So, in the code, the algorithm ...

  • if the pixel with the red pen is decoded, the pixel is set to digital red
  • if a pixel with a black pen is decoded, the pixel is set to digital blue
  • if the green pixel of the pen ink is decoded, the pixel is set to green green
  • if the black pixel of the pen ink is decoded, the pixel is set to digital yellow

The black ink pixel is set to digital yellow (not black because black was difficult to distinguish from ink visually black).

My code is below, but first, here is one of the best results that I had. As you can see, the overlap is displayed on the bands between the black (represented by digital yellow) and red (on red). As I said, my code was partly logical, partly trial and error.

My first question is: how can I improve this algorithm to decode / filter colors perfectly (I chose this method because I get lost very quickly with very complicated mathematics)?

Secondly, how can I make it work for different shades of light (white light, yellow light ...)?

EDIT: after further comments and discussions, I realized that the help I need is an example of the color quantization code of the Adroid palette, thanks

Undecoded Original image

Decoded image, almost but not good enough

if( (Blue > Green) &&(Red > Blue) && (Red - Green) > 25 ) copyOfBM.setPixel(x, y, Color.RED); //red else if( (Blue > Red) && ( (Blue > Green)) ) copyOfBM.setPixel(x, y, Color.BLUE); else if( (Green >= 82) && ((Green - Blue) >= 12) && ((DecodeLuminaceColor( x, y, copyOfBM )>= 82 ) && (DecodeLuminaceColor( x, y, copyOfBM )< 124)) ) copyOfBM.setPixel(x, y, Color.GREEN); else if( ((Red - Green) > 6) &&((Red - Green) < 17) &&((Green - Blue) < 8) && (DecodeLuminaceColor( x, y, copyOfBM )< 118 ) ) copyOfBM.setPixel(x, y, Color.YELLOW); void DecodeLuminaceColor( int _x, int _y, Bitmap cBM ){ float avrLum = 0; Red = 0; Green = 0; Blue = 0; //Color.green(green_encoded_color); red_encoded_color = cBM.getPixel(_x, _y); green_encoded_color = cBM.getPixel(_x, _y); blue_encoded_color = cBM.getPixel(_x, _y); Red = ( (red_encoded_color >> 16) & 0xff ); Green = ( (green_encoded_color >> 8) & 0xff); Blue = ( (blue_encoded_color >> 0) & 0xff ); } 
+5
source share
1 answer

I would use HSV color space

itโ€™s better to detect colors (more similar to human perception) that should help a lot. You can also use the HSV histogram to determine how many different colors you have.

HSV Bar Graph

If you still want to use RGB, otherwise compare

You have pen color0=(r0,g0,b0) and pixel color=(r,g,b) , so you can calculate the distance between them:

 d=((r-r0)*(r-r0))+((g-g0)*(g-g0))+((b-b0)*(b-b0)) 

No need for sqrt . Now you just calculate d for each color that you have (pens) and select the smallest d ... You can also use the less accurate:

 d=abs(r-r0)+abs(g-g0)+abs(b-b0) 

If you donโ€™t know the colors before and donโ€™t want to use histograms

  • create a color chart (re) (a set of different visible colors that you set for each new pen found).
  • create an empty list of colors found
  • process all pixels of the input image
  • calculate distance d to all colors found in the list
  • if d less than some treshold constant, the pixels belong to this color in the list of colors found. Else add it as the new color found.
  • repainting pixel with color from the repeater table.

This eliminates color distortion and attenuation. You can also ignore the repainting table and use a color from the list of colors found. This process is color quantization .

[Edit1] After using the HSV color and repainting to find a list of colors (without a histogram), I got this result:

HSV simple recolor

This shows that your image does not have the same lighting conditions (not a render, but a real photo). Thus, the normalization of illumination should improve this even more. I also use 2 cods for one color for gray and one for flowers ... to distinguish between two ... You can also determine the background color:

  • number of pixels (should be much larger than the text color)
  • dispersion along the image (should cover a large area with a relatively high density, evenly distributed ... The text is localized)

Here is the C ++ / VCL source for this:

 backbuffer bmp; // source and target image struct _color { DWORD rgb; int h,s,v; }; // color entry in (re)color table _color ld_rgb(DWORD rgb) // just RGB -> HSV conversion { const int _b=0; const int _g=1; const int _r=2; const int _a=3; union { DWORD dd; BYTE db[4]; } c; double r,g,b,min,max,del,h,s,v,dr,dg,db; c.dd=rgb; r=c.db[_r]; r/=255.0; g=c.db[_g]; g/=255.0; b=c.db[_b]; b/=255.0; min=r; if (min>g) min=g; if(min>b) min=b; max=r; if (max<g) max=g; if(max<b) max=b; del=max-min; v=max; if (del<=0.1) { h=0; s=0; } // grayscale else{ s=del/max; dr=(((max-r)/6.0)+(del/2.0))/del; dg=(((max-g)/6.0)+(del/2.0))/del; db=(((max-b)/6.0)+(del/2.0))/del; if (fabs(r-max)<1e-10) h=db-dg; else if (fabs(g-max)<1e-10) h=(1.0/3.0)+dr-db; else if (fabs(b-max)<1e-10) h=(2.0/3.0)+dg-dr; if (h<0.0) h+=1.0; if (h>1.0) h-=1.0; } _color ccc; ccc.rgb=rgb; ccc.h=255.0*h; ccc.s=255.0*s; ccc.v=255.0*v; return ccc; } void recolor() // this is the recolor you want { // load input jpg file to bmp image TJPEGImage *jpg=new TJPEGImage(); jpg->LoadFromFile("in.jpg"); bmp.bmp->Assign(jpg); bmp.resize(bmp.bmp->Width,bmp.bmp->Height); delete jpg; // recolor bmp int i,x,y,d; _color c0,c1; List<_color> col; // color list col.num=0; // clear colro list for (y=0;y<bmp.ys;y++) // process all pixels for (x=0;x<bmp.xs;x++) { c0=ld_rgb(bmp.pyx[y][x]); // pixel color -> hsv if ((c0.h==0)&&(c0.s==0)) // compare it to found colors (grayscales) for (i=0;i<col.num;i++) { // i=-1; c1.rgb=0x00202020; break; c1=col[i]; if ((c1.h!=0)||(c1.s!=0)) continue; d=abs(c1.v-c0.v); if (d<32) { i=-1; break; } // match found ? } else // compare it to found colors for (i=0;i<col.num;i++) { // i=-1; c1.rgb=0x0000FF00; break; c1=col[i]; if ((c1.h==0)&&(c1.s==0)) continue; d=(abs(c1.h-c0.h))+(abs(c1.s-c0.s)); if (d<50) { i=-1; break; } // match found ? } if (i>=0) { c1=c0; col.add(c1); } // if not add new color bmp.pyx[y][x]=c1.rgb; // recolor; } bmp.bmp->Canvas->Brush->Style=bsClear; bmp.bmp->Canvas->Font->Color=0x00802040; bmp.bmp->Canvas->TextOutA(5,0,"Found colors: "+AnsiString(col.num)); bmp.bmp->Canvas->Brush->Style=bsSolid; for (d=16,i=0;i<col.num;i++) for (y=d;y<=d+d;y++) for (x=d*i+1;(x<d*i+d)&&(x<bmp.xs);x++) bmp.pyx[y][x]=col[i].rgb; } 
  • List<T> l; is a dynamic array such as std::vector<T> ... represents T l[l.num];
  • backbuffer bmp; is my image class ... bmp.bmp Holds the GDI bitmap, and bmp.xs,bmp.ys is the resolution
  • col contains the colors found ...

[Edit1] bi-cubic normalization of lighting

I recently rewrote my standard DIP world illumination so that I could take a picture on your original image (as one of many test images), and here is the result (with forced (detected) blank space to Repaint):

normalized image

As you can see, the middle spot of red lighting has disappeared. You can try your algorithm to see if normalizing lighting helps before you encode it (it's a bit complicated if everything is done correctly). This is done as follows:

  • create a grid (table) for your image

    each cell contains a medium color and cumulative delta strong> (noise) cells. Also one flag tells if the cell is paper or ink . The cell size should be around <0.5 - 1.5> minimum size of the part (for example, letter or pen width ...)

  • set all cells with high deltas as ink, the rest as paper

  • calculate the average color of all merged paper cells
  • each adjacent cell ink cell

    set as ink if its average color is too far from the average average color of the paper. Be careful not to take these new ink cells as an adjacent condition for this step. (use the temp flag or a bit of another flag and restore it after that ...

  • find 16 control points evenly distributed along the image (use only paper cells)

    they should be around the coordinates 0%,33%,66%,100% image resolution. Thus, bi-cubic interpolation is permissible.

  • for each pixel

    calculate the cell color bi-cubically and name it c0 Then apply normalization to the pixel ( in RGB space !!! ):

    • pixel+=global_avg_color-c0 ;

    This will align the color of the paper throughout the image to a very close match with global_avg_color . Leave unused parts without paper.

  • optionally recolor the entire area of โ€‹โ€‹paper cells with global_avg_color

    This is not necessary, but it will save most of the noise from the background. Like paper texture ...

+4
source

Source: https://habr.com/ru/post/1234946/


All Articles