/* Copyright 2005, 2006 by Gerald Friedland, Kristian Jantz and Lars Knipping Conversion to C++ for Inkscape by Bob Jamison Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #include "siox.h" #include #include //for error() and trace() #include //sqrt(), pow(), round(), etc namespace org { namespace siox { //######################################################################## //## U T I L S (originally Utils.java) //######################################################################## /** * Collection of auxiliary image processing methods used by the * SioxSegmentator mainly for postprocessing. * * @author G. Friedland, K. Jantz, L. Knipping * @version 1.05 * * Conversion to C++ by Bob Jamison * */ /** Caches color conversion values to speed up RGB->CIELAB conversion.*/ static std::map RGB_TO_LAB; //forward decls static void premultiplyMatrix(float alpha, float *cm, int cmSize); //static float colordiffsq(unsigned long rgb0, unsigned long rgb1); //static int getAlpha(unsigned long argb); static int getRed(unsigned long rgb); static int getGreen(unsigned long rgb); static int getBlue(unsigned long rgb); //static unsigned long packPixel(int a, int r, int g, int b); static CLAB rgbToClab(unsigned long rgb); /** * Applies the morphological dilate operator. * * Can be used to close small holes in the given confidence matrix. * * @param cm Confidence matrix to be processed. * @param xres Horizontal resolution of the matrix. * @param yres Vertical resolution of the matrix. */ static void dilate(float *cm, int xres, int yres) { for (int y=0; ycm[idx]) cm[idx]=cm[idx+1]; } } for (int y=0; y=1; x--) { int idx=(y*xres)+x; if (cm[idx-1]>cm[idx]) cm[idx]=cm[idx-1]; } } for (int y=0; y cm[idx]) cm[idx]=cm[((y+1)*xres)+x]; } } for (int y=yres-1; y>=1; y--) { for (int x=0; x cm[idx]) cm[idx]=cm[((y-1)*xres)+x]; } } } /** * Applies the morphological erode operator. * * @param cm Confidence matrix to be processed. * @param xres Horizontal resolution of the matrix. * @param yres Vertical resolution of the matrix. */ static void erode(float *cm, int xres, int yres) { for (int y=0; y=1; x--) { int idx=(y*xres)+x; if (cm[idx-1] < cm[idx]) cm[idx]=cm[idx-1]; } } for (int y=0; y=1; y--) { for (int x=0; x * In the standard case confidence matrix entries are between 0...1 and * the weight factors sum up to 1. * * @param cm The matrix to be smoothed. * @param xres Horizontal resolution of the matrix. * @param yres Vertical resolution of the matrix. * @param f1 Weight factor for the first pixel. * @param f2 Weight factor for the mid-pixel. * @param f3 Weight factor for the last pixel. */ static void smoothcm(float *cm, int xres, int yres, float f1, float f2, float f3) { for (int y=0; y=2; x--) { int idx=(y*xres)+x; cm[idx]=f3*cm[idx-2]+f2*cm[idx-1]+f1*cm[idx]; } } for (int y=0; y=2; y--) { for (int x=0; x * Usage hint: When only comparisons between Euclidian distances without * actual values are needed, the squared distance can be preferred * for being faster to compute. * * @param p First euclidian point coordinates. * @param pSize Length of coordinate array. * @param q Second euclidian point coordinates. * Dimension must not be smaller than that of p. * Any extra dimensions will be ignored. * @return Squared euclidian distance of p and q. * @see #euclid */ static float sqrEuclidianDist(float *p, int pSize, float *q) { float sum=0; for (int i=0; i * Usage hint: When only comparisons between Euclidian distances without * actual values are needed, the squared distance can be preferred * for being faster to compute. * * @param p CLAB value * @param q second CLAB value * @return Squared euclidian distance of p and q. * @see #euclid */ static float sqrEuclidianDist(const CLAB &p, const CLAB &q) { float sum=0; sum += (p.C - q.C) * (p.C - q.C); sum += (p.L - q.L) * (p.L - q.L); sum += (p.A - q.A) * (p.A - q.A); sum += (p.B - q.B) * (p.B - q.B); return sum; } /** * Euclidian distance of p and q. * * @param p First euclidian point coordinates. * @param pSize Length of coordinate array. * @param q Second euclidian point coordinates. * Dimension must not be smaller than that of p. * Any extra dimensions will be ignored. * @return Squared euclidian distance of p and q. * @see #sqrEuclidianDist */ /* static float euclid(float *p, int pSize, float *q) { return (float)sqrt(sqrEuclidianDist(p, pSize, q)); } */ /** * Computes Euclidian distance of two RGB color values. * * @param rgb0 First color value. * @param rgb1 Second color value. * @return Euclidian distance between the two color values. */ /* static float colordiff(long rgb0, long rgb1) { return (float)sqrt(colordiffsq(rgb0, rgb1)); } */ /** * Computes squared euclidian distance of two RGB color values. *

* Note: Faster to compute than colordiff * * @param rgb0 First color value. * @param rgb1 Second color value. * @return Squared Euclidian distance between the two color values. */ /* static float colordiffsq(long rgb0, long rgb1) { int rDist=getRed(rgb0) - getRed(rgb1); int gDist=getGreen(rgb0) - getGreen(rgb1); int bDist=getBlue(rgb0) - getBlue(rgb1); return (float)(rDist*rDist+gDist*gDist+bDist*bDist); } */ /** * Averages two ARGB colors. * * @param argb0 First color value. * @param argb1 Second color value. * @return The averaged ARGB color. */ /* static long average(long argb0, long argb1) { long ret = packPixel( (getAlpha(argb0) + getAlpha(argb1))/2, (getRed(argb0) + getRed(argb1) )/2, (getGreen(argb0) + getGreen(argb1))/2, (getBlue(argb0) + getBlue(argb1) )/2); return ret; } */ /** * Computes squared euclidian distance in CLAB space for two colors * given as RGB values. * * @param rgb0 First color value. * @param rgb1 Second color value. * @return Squared Euclidian distance in CLAB space. */ static float labcolordiffsq(unsigned long rgb1, unsigned long rgb2) { CLAB c1 = rgbToClab(rgb1); CLAB c2 = rgbToClab(rgb2); float euclid=0.0f; euclid += (c1.L - c2.L) * (c1.L - c2.L); euclid += (c1.A - c2.A) * (c1.A - c2.A); euclid += (c1.B - c2.B) * (c1.B - c2.B); return euclid; } /** * Computes squared euclidian distance in CLAB space for two colors * given as RGB values. * * @param rgb0 First color value. * @param rgb1 Second color value. * @return Euclidian distance in CLAB space. */ static float labcolordiff(unsigned long rgb0, unsigned long rgb1) { return (float)sqrt(labcolordiffsq(rgb0, rgb1)); } /** * Converts 24-bit RGB values to {l,a,b} float values. *

* The conversion used is decribed at * CLAB Conversion * for reference white D65. Note that that the conversion is computational * expensive. Result are cached to speed up further conversion calls. * * @param rgb RGB color value, * @return CLAB color value tripel. */ static CLAB rgbToClab(unsigned long rgb) { std::map::iterator iter = RGB_TO_LAB.find(rgb); if (iter != RGB_TO_LAB.end()) { CLAB res = iter->second; return res; } int R=getRed(rgb); int G=getGreen(rgb); int B=getBlue(rgb); float var_R=(R/255.0f); //R = From 0 to 255 float var_G=(G/255.0f); //G = From 0 to 255 float var_B=(B/255.0f); //B = From 0 to 255 if (var_R>0.04045) var_R=(float) pow((var_R+0.055f)/1.055f, 2.4); else var_R=var_R/12.92f; if (var_G>0.04045) var_G=(float) pow((var_G+0.055f)/1.055f, 2.4); else var_G=var_G/12.92f; if (var_B>0.04045) var_B=(float) pow((var_B+0.055f)/1.055f, 2.4); else var_B=var_B/12.92f; var_R=var_R*100.0f; var_G=var_G*100.0f; var_B=var_B*100.0f; // Observer. = 2�, Illuminant = D65 float X=var_R*0.4124f + var_G*0.3576f + var_B*0.1805f; float Y=var_R*0.2126f + var_G*0.7152f + var_B*0.0722f; float Z=var_R*0.0193f + var_G*0.1192f + var_B*0.9505f; float var_X=X/95.047f; float var_Y=Y/100.0f; float var_Z=Z/108.883f; if (var_X>0.008856f) var_X=(float) pow(var_X, 0.3333f); else var_X=(7.787f*var_X)+(16.0f/116.0f); if (var_Y>0.008856f) var_Y=(float) pow(var_Y, 0.3333f); else var_Y=(7.787f*var_Y)+(16.0f/116.0f); if (var_Z>0.008856f) var_Z=(float) pow(var_Z, 0.3333f); else var_Z=(7.787f*var_Z)+(16.0f/116.0f); CLAB lab((116.0f*var_Y)-16.0f , 500.0f*(var_X-var_Y), 200.0f*(var_Y-var_Z)); RGB_TO_LAB[rgb] = lab; return lab; } /** * Converts an CLAB value to a RGB color value. *

* This is the reverse operation to rgbToClab. * @param clab CLAB value. * @return RGB value. * @see #rgbToClab */ /* static long clabToRGB(const CLAB &clab) { float L=clab.L; float a=clab.A; float b=clab.B; float var_Y=(L+16.0f)/116.0f; float var_X=a/500.0f+var_Y; float var_Z=var_Y-b/200.0f; float var_yPow3=(float)pow(var_Y, 3.0); float var_xPow3=(float)pow(var_X, 3.0); float var_zPow3=(float)pow(var_Z, 3.0); if (var_yPow3>0.008856f) var_Y=var_yPow3; else var_Y=(var_Y-16.0f/116.0f)/7.787f; if (var_xPow3>0.008856f) var_X=var_xPow3; else var_X=(var_X-16.0f/116.0f)/7.787f; if (var_zPow3>0.008856f) var_Z=var_zPow3; else var_Z=(var_Z-16.0f/116.0f)/7.787f; float X=95.047f * var_X; //ref_X= 95.047 Observer=2�, Illuminant=D65 float Y=100.0f * var_Y; //ref_Y=100.000 float Z=108.883f * var_Z; //ref_Z=108.883 var_X=X/100.0f; //X = From 0 to ref_X var_Y=Y/100.0f; //Y = From 0 to ref_Y var_Z=Z/100.0f; //Z = From 0 to ref_Y float var_R=(float)(var_X * 3.2406f + var_Y * -1.5372f + var_Z * -0.4986f); float var_G=(float)(var_X * -0.9689f + var_Y * 1.8758f + var_Z * 0.0415f); float var_B=(float)(var_X * 0.0557f + var_Y * -0.2040f + var_Z * 1.0570f); if (var_R>0.0031308f) var_R=(float)(1.055f*pow(var_R, (1.0f/2.4f))-0.055f); else var_R=12.92f*var_R; if (var_G>0.0031308f) var_G=(float)(1.055f*pow(var_G, (1.0f/2.4f))-0.055f); else var_G=12.92f*var_G; if (var_B>0.0031308f) var_B=(float)(1.055f*pow(var_B, (1.0f/2.4f))-0.055f); else var_B=12.92f*var_B; int R = (int)lround(var_R*255.0f); int G = (int)lround(var_G*255.0f); int B = (int)lround(var_B*255.0f); return packPixel(0xFF, R, G, B); } */ /** * Sets the alpha byte of a pixel. * * Constructs alpha to values from 0 to 255. * @param alpha Alpha value from 0 (transparent) to 255 (opaque). * @param rgb The 24bit rgb color to be combined with the alpga value. * @return An ARBG calor value. */ static long setAlpha(int alpha, unsigned long rgb) { if (alpha>255) alpha=0; else if (alpha<0) alpha=0; return (alpha<<24)|(rgb&0xFFFFFF); } /** * Sets the alpha byte of a pixel. * * Constricts alpha to values from 0 to 255. * @param alpha Alpha value from 0.0f (transparent) to 1.0f (opaque). * @param rgb The 24bit rgb color to be combined with the alpga value. * @return An ARBG calor value. */ static long setAlpha(float alpha, unsigned long rgb) { return setAlpha((int)(255.0f*alpha), rgb); } /** * Limits the values of a,r,g,b to values from 0 to 255 and puts them * together into an 32 bit integer. * * @param a Alpha part, the first byte. * @param r Red part, the second byte. * @param g Green part, the third byte. * @param b Blue part, the fourth byte. * @return A ARBG value packed to an int. */ /* static long packPixel(int a, int r, int g, int b) { if (a<0) a=0; else if (a>255) a=255; if (r<0) r=0; else if (r>255) r=255; if (g<0) g=0; else if (g>255) g=255; if (b<0) b=0; else if (b>255) b=255; return (a<<24)|(r<<16)|(g<<8)|b; } */ /** * Returns the alpha component of an ARGB value. * * @param argb An ARGB color value. * @return The alpha component, ranging from 0 to 255. */ /* static int getAlpha(unsigned long argb) { return (argb>>24)&0xFF; } */ /** * Returns the red component of an (A)RGB value. * * @param rgb An (A)RGB color value. * @return The red component, ranging from 0 to 255. */ static int getRed(unsigned long rgb) { return (rgb>>16)&0xFF; } /** * Returns the green component of an (A)RGB value. * * @param rgb An (A)RGB color value. * @return The green component, ranging from 0 to 255. */ static int getGreen(unsigned long rgb) { return (rgb>>8)&0xFF; } /** * Returns the blue component of an (A)RGB value. * * @param rgb An (A)RGB color value. * @return The blue component, ranging from 0 to 255. */ static int getBlue(unsigned long rgb) { return (rgb)&0xFF; } /** * Returns a string representation of a CLAB value. * * @param clab The CLAB value. * @param clabSize Size of the CLAB value. * @return A string representation of the CLAB value. */ /* static std::string clabToString(const CLAB &clab) { std::string buff; char nbuf[60]; snprintf(nbuf, 59, "%5.3f, %5.3f, %5.3f", clab.L, clab.A, clab.B); buff = nbuf; return buff; } */ //######################################################################## //## C O L O R S I G N A T U R E (originally ColorSignature.java) //######################################################################## /** * Representation of a color signature. *

* This class implements a clustering algorithm based on a modified kd-tree. * The splitting rule is to simply divide the given interval into two equally * sized subintervals. * In the stageone(), approximate clusters are found by building * up such a tree and stopping when an interval at a node has become smaller * than the allowed cluster diameter, which is given by limits. * At this point, clusters may be split in several nodes.
* Therefore, in stagetwo(), nodes that belong to several clusters * are recombined by another k-d tree clustering on the prior cluster * centroids. To guarantee a proper level of abstraction, clusters that contain * less than 0.01% of the pixels of the entire sample are removed. Please * refer to the file NOTICE to get links to further documentation. * * @author Gerald Friedland, Lars Knipping * @version 1.02 * * Conversion to C++ by Bob Jamison * */ /** * Stage one of clustering. * @param points float[][] the input points in LAB space * @param depth int used for recursion, start with 0 * @param clusters ArrayList an Arraylist to store the clusters * @param limits float[] the cluster diameters */ static void stageone(std::vector &points, int depth, std::vector< std::vector > &clusters, float *limits) { if (points.size() < 1) return; int dims=3; int curdim=depth%dims; float min = 0.0f; float max = 0.0f; if (curdim == 0) { min=points[0].C; max=points[0].C; // find maximum and minimum for (unsigned int i=1; ipoints[i].C) min=points[i].C; if (maxpoints[i].L) min=points[i].L; if (maxpoints[i].A) min=points[i].A; if (maxpoints[i].B) min=points[i].B; if (maxlimits[curdim]) { // Split according to Rubner-Rule // split float pivotvalue=((max-min)/2.0f)+min; std::vector smallerpoints; // allocate mem std::vector biggerpoints; for (unsigned int i=0; i &points, int depth, std::vector< std::vector > &clusters, float *limits, int total, float threshold) { if (points.size() < 1) return; int curdim=depth%3; // without cardinality float min = 0.0f; float max = 0.0f; if (curdim == 0) { min=points[0].L; max=points[0].L; // find maximum and minimum for (unsigned int i=1; ipoints[i].L) min=points[i].L; if (maxpoints[i].A) min=points[i].A; if (maxpoints[i].B) min=points[i].B; if (maxlimits[curdim]) { // Split according to Rubner-Rule // split float pivotvalue=((max-min)/2.0f)+min; std::vector smallerpoints; // allocate mem std::vector biggerpoints; for (unsigned int i=0; i=threshold) { CLAB point; for (unsigned int i=0; i newCluster; newCluster.push_back(point); clusters.push_back(newCluster); } } } /** * Create a color signature for the given set of pixels. * @param input float[][] a set of pixels in LAB space * @param length int the number of pixels that should be processed from the input * @param limits float[] the cluster diameters for each dimension * @param threshold float the abstraction threshold * @return float[][] a color siganture containing cluster centroids in LAB space */ static std::vector createSignature(std::vector &input, float *limits, float threshold) { std::vector< std::vector > clusters1; std::vector< std::vector > clusters2; stageone(input, 0, clusters1, limits); std::vector centroids; for (unsigned int i=0; i cluster = clusters1[i]; CLAB centroid; // +1 for the cardinality for (unsigned int k=0; k see paper by tomasi std::vector res; for (unsigned int i=0 ; i=FOREGROUND_CONFIDENCE) knownFg.push_back(rgbToClab(image[i])); } bgSignature = createSignature(knownBg, limits, BACKGROUND_CONFIDENCE); fgSignature = createSignature(knownFg, limits, BACKGROUND_CONFIDENCE); if (bgSignature.size() < 1) { // segmentation impossible return false; } // classify using color signatures, // classification cached in hashmap for drb and speedup purposes for (int i=0; i=FOREGROUND_CONFIDENCE) { cm[i]=CERTAIN_FOREGROUND_CONFIDENCE; continue; } if (cm[i]>BACKGROUND_CONFIDENCE) { bool isBackground=true; std::map::iterator iter = hs.find(i); Tupel tupel(0.0f, 0, 0.0f, 0); if (iter == hs.end()) { CLAB lab = rgbToClab(image[i]); float minBg = sqrEuclidianDist(lab, bgSignature[0]); int minIndex=0; for (unsigned int j=1; j=UNKNOWN_REGION_CONFIDENCE) { cm[i]=CERTAIN_FOREGROUND_CONFIDENCE; } else { cm[i]=CERTAIN_BACKGROUND_CONFIDENCE; } } keepOnlyLargeComponents(cm, UNKNOWN_REGION_CONFIDENCE, sizeFactorToKeep); fillColorRegions(cm, image); dilate(cm, imgWidth, imgHeight); segmentated=true; return true; } void SioxSegmentator::keepOnlyLargeComponents(float *cm, float threshold, double sizeFactorToKeep) { int idx = 0; for (int i=0 ; i labelSizes; for (int i=0 ; i=threshold) { regionCount=depthFirstSearch(cm, i, threshold, curlabel++); labelSizes.push_back(regionCount); } if (regionCount>maxregion) { maxregion=regionCount; maxblob=curlabel-1; } } for (int i=0 ; i pixelsToVisit; int componentSize=0; if (labelField[i]==-1 && cm[i]>=threshold) { // label #i labelField[i] = curLabel; ++componentSize; pixelsToVisit.push_back(i); } while (pixelsToVisit.size() > 0) { int pos=pixelsToVisit[pixelsToVisit.size()-1]; pixelsToVisit.erase(pixelsToVisit.end()-1); int x=pos%imgWidth; int y=pos/imgWidth; // check all four neighbours int left = pos-1; if (x-1>=0 && labelField[left]==-1 && cm[left]>=threshold) { labelField[left]=curLabel; ++componentSize; pixelsToVisit.push_back(left); } int right = pos+1; if (x+1=threshold) { labelField[right]=curLabel; ++componentSize; pixelsToVisit.push_back(right); } int top = pos-imgWidth; if (y-1>=0 && labelField[top]==-1 && cm[top]>=threshold) { labelField[top]=curLabel; ++componentSize; pixelsToVisit.push_back(top); } int bottom = pos+imgWidth; if (y+1=threshold) { labelField[bottom]=curLabel; ++componentSize; pixelsToVisit.push_back(bottom); } } return componentSize; } void SioxSegmentator::subpixelRefine(int x, int y, int brushmode, float threshold, float *cf, int brushsize) { subpixelRefine(x-brushsize, y-brushsize, 2*brushsize, 2*brushsize, brushmode, threshold, cf); } bool SioxSegmentator::subpixelRefine(int xa, int ya, int dx, int dy, int brushmode, float threshold, float *cf) { if (!segmentated) { error("no segmentation yet"); return false; } int x0 = (xa > 0) ? xa : 0; int y0 = (ya > 0) ? ya : 0; int xTo = (imgWidth - 1 < xa+dx ) ? imgWidth-1 : xa+dx; int yTo = (imgHeight - 1 < ya+dy ) ? imgHeight-1 : ya+dy; for (int ey=y0; ey::iterator iter = hs.find(val); if (iter != hs.end()) { minDistBg=(float) sqrt((float)iter->second.minBgDist); minDistFg=(float) sqrt((float)iter->second.minFgDist); } else { continue; } if (ADD_EDGE == brushmode) { // handle adder if (cf[ey*imgWidth+ex]FOREGROUND_CONFIDENCE) { // foreground, we want to take something away float alpha; if (minDistBg==0) { alpha=CERTAIN_BACKGROUND_CONFIDENCE; } else { alpha=CERTAIN_FOREGROUND_CONFIDENCE- (minDistFg/minDistBg < CERTAIN_FOREGROUND_CONFIDENCE) ? // more background -> >1 minDistFg/minDistBg : CERTAIN_FOREGROUND_CONFIDENCE; // bg = gf -> 1 // more fg -> <1 } if (alpha pixelsToVisit; for (int i=0; i 0) { int pos=pixelsToVisit[pixelsToVisit.size()-1]; pixelsToVisit.erase(pixelsToVisit.end()-1); int x=pos%imgWidth; int y=pos/imgWidth; // check all four neighbours int left = pos-1; if (x-1>=0 && labelField[left]==-1 && labcolordiff(image[left], origColor)<1.0) { labelField[left]=curLabel; cm[left]=CERTAIN_FOREGROUND_CONFIDENCE; // ++componentSize; pixelsToVisit.push_back(left); } int right = pos+1; if (x+1=0 && labelField[top]==-1 && labcolordiff(image[top], origColor)<1.0) { labelField[top]=curLabel; cm[top]=CERTAIN_FOREGROUND_CONFIDENCE; // ++componentSize; pixelsToVisit.push_back(top); } int bottom = pos+imgWidth; if (y+1maxRegion) { // maxRegion=componentSize; //} } } } //namespace siox } //namespace org