/*! * \brief pixColorSegment() * * \param[in] pixs 32 bpp; 24-bit color * \param[in] maxdist max euclidean dist to existing cluster * \param[in] maxcolors max number of colors allowed in first pass * \param[in] selsize linear size of sel for closing to remove noise * \param[in] finalcolors max number of final colors allowed after 4th pass * \param[in] debugflag 1 for debug output; 0 otherwise * \return pixd 8 bit with colormap, or NULL on error * * <pre> * Color segmentation proceeds in four phases: * * Phase 1: pixColorSegmentCluster() * The image is traversed in raster order. Each pixel either * becomes the representative for a new cluster or is assigned to an * existing cluster. Assignment is greedy. The data is stored in * a colormapped image. Three auxiliary arrays are used to hold * the colors of the representative pixels, for fast lookup. * The average color in each cluster is computed. * * Phase 2. pixAssignToNearestColor() * A second non-greedy clustering pass is performed, where each pixel * is assigned to the nearest cluster average. We also keep track * of how many pixels are assigned to each cluster. * * Phase 3. pixColorSegmentClean() * For each cluster, starting with the largest, do a morphological * closing to eliminate small components within larger ones. * * Phase 4. pixColorSegmentRemoveColors() * Eliminate all colors except the most populated 'finalcolors'. * Then remove unused colors from the colormap, and reassign those * pixels to the nearest remaining cluster, using the original pixel values. * * Notes: * (1) The goal is to generate a small number of colors. * Typically this would be specified by 'finalcolors', * a number that would be somewhere between 3 and 6. * The parameter 'maxcolors' specifies the maximum number of * colors generated in the first phase. This should be * larger than finalcolors, perhaps twice as large. * If more than 'maxcolors' are generated in the first phase * using the input 'maxdist', the distance is repeatedly * increased by a multiplicative factor until the condition * is satisfied. The implicit relation between 'maxdist' * and 'maxcolors' is thus adjusted programmatically. * (2) As a very rough guideline, given a target value of 'finalcolors', * here are approximate values of 'maxdist' and 'maxcolors' * to start with: * * finalcolors maxcolors maxdist * ----------- --------- ------- * 3 6 100 * 4 8 90 * 5 10 75 * 6 12 60 * * For a given number of finalcolors, if you use too many * maxcolors, the result will be noisy. If you use too few, * the result will be a relatively poor assignment of colors. * </pre> */ PIX * pixColorSegment(PIX *pixs, l_int32 maxdist, l_int32 maxcolors, l_int32 selsize, l_int32 finalcolors, l_int32 debugflag) { l_int32 *countarray; PIX *pixd; PROCNAME("pixColorSegment"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 32) return (PIX *)ERROR_PTR("must be rgb color", procName, NULL); /* Phase 1; original segmentation */ pixd = pixColorSegmentCluster(pixs, maxdist, maxcolors, debugflag); if (!pixd) return (PIX *)ERROR_PTR("pixd not made", procName, NULL); if (debugflag) { lept_mkdir("lept/segment"); pixWriteDebug("/tmp/lept/segment/colorseg1.png", pixd, IFF_PNG); } /* Phase 2; refinement in pixel assignment */ if ((countarray = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32))) == NULL) { pixDestroy(&pixd); return (PIX *)ERROR_PTR("countarray not made", procName, NULL); } pixAssignToNearestColor(pixd, pixs, NULL, LEVEL_IN_OCTCUBE, countarray); if (debugflag) pixWriteDebug("/tmp/lept/segment/colorseg2.png", pixd, IFF_PNG); /* Phase 3: noise removal by separately closing each color */ pixColorSegmentClean(pixd, selsize, countarray); LEPT_FREE(countarray); if (debugflag) pixWriteDebug("/tmp/lept/segment/colorseg3.png", pixd, IFF_PNG); /* Phase 4: removal of colors with small population and * reassignment of pixels to remaining colors */ pixColorSegmentRemoveColors(pixd, pixs, finalcolors); return pixd; }
/*! * pixColorSegment() * * Input: pixs (32 bpp; 24-bit color) * maxdist (max euclidean dist to existing cluster) * maxcolors (max number of colors allowed in first pass) * selsize (linear size of sel for closing to remove noise) * finalcolors (max number of final colors allowed after 4th pass) * Return: pixd (8 bit with colormap), or null on error * * Color segmentation proceeds in four phases: * * Phase 1: pixColorSegmentCluster() * The image is traversed in raster order. Each pixel either * becomes the representative for a new cluster or is assigned to an * existing cluster. Assignment is greedy. The data is stored in * a colormapped image. Three auxiliary arrays are used to hold * the colors of the representative pixels, for fast lookup. * The average color in each cluster is computed. * * Phase 2. pixAssignToNearestColor() * A second (non-greedy) clustering pass is performed, where each pixel * is assigned to the nearest cluster (average). We also keep track * of how many pixels are assigned to each cluster. * * Phase 3. pixColorSegmentClean() * For each cluster, starting with the largest, do a morphological * closing to eliminate small components within larger ones. * * Phase 4. pixColorSegmentRemoveColors() * Eliminate all colors except the most populated 'finalcolors'. * Then remove unused colors from the colormap, and reassign those * pixels to the nearest remaining cluster, using the original pixel values. * * Notes: * (1) The goal is to generate a small number of colors. * Typically this would be specified by 'finalcolors', * a number that would be somewhere between 3 and 6. * The parameter 'maxcolors' specifies the maximum number of * colors generated in the first phase. This should be * larger than finalcolors, perhaps twice as large. * If more than 'maxcolors' are generated in the first phase * using the input 'maxdist', the distance is repeatedly * increased by a multiplicative factor until the condition * is satisfied. The implicit relation between 'maxdist' * and 'maxcolors' is thus adjusted programmatically. * (2) As a very rough guideline, given a target value of 'finalcolors', * here are approximate values of 'maxdist' and 'maxcolors' * to start with: * * finalcolors maxcolors maxdist * ----------- --------- ------- * 3 6 100 * 4 8 90 * 5 10 75 * 6 12 60 * * For a given number of finalcolors, if you use too many * maxcolors, the result will be noisy. If you use too few, * the result will be a relatively poor assignment of colors. */ PIX * pixColorSegment(PIX *pixs, l_int32 maxdist, l_int32 maxcolors, l_int32 selsize, l_int32 finalcolors) { l_int32 *countarray; PIX *pixd; PROCNAME("pixColorSegment"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 32) return (PIX *)ERROR_PTR("must be rgb color", procName, NULL); /* Phase 1; original segmentation */ if ((pixd = pixColorSegmentCluster(pixs, maxdist, maxcolors)) == NULL) return (PIX *)ERROR_PTR("pixt1 not made", procName, NULL); /* pixWrite("junkpixd1", pixd, IFF_PNG); */ /* Phase 2; refinement in pixel assignment */ if ((countarray = (l_int32 *)CALLOC(256, sizeof(l_int32))) == NULL) return (PIX *)ERROR_PTR("countarray not made", procName, NULL); pixAssignToNearestColor(pixd, pixs, NULL, LEVEL_IN_OCTCUBE, countarray); /* pixWrite("junkpixd2", pixd, IFF_PNG); */ /* Phase 3: noise removal by separately closing each color */ pixColorSegmentClean(pixd, selsize, countarray); /* pixWrite("junkpixd3", pixd, IFF_PNG); */ FREE(countarray); /* Phase 4: removal of colors with small population and * reassignment of pixels to remaining colors */ pixColorSegmentRemoveColors(pixd, pixs, finalcolors); return pixd; }
/*! * \brief pixColorSegmentRemoveColors() * * \param[in] pixd 8 bpp, colormapped * \param[in] pixs 32 bpp rgb, with initial pixel values * \param[in] finalcolors max number of colors to retain * \return 0 if OK, 1 on error * * <pre> * Notes: * (1) This operation is in-place. * (2) This is phase 4 of color segmentation, and the second part * of the 2-step noise removal. Only 'finalcolors' different * colors are retained, with colors with smaller populations * being replaced by the nearest color of the remaining colors. * For highest accuracy, for pixels that are being replaced, * we find the nearest colormap color to the original rgb color. * </pre> */ l_ok pixColorSegmentRemoveColors(PIX *pixd, PIX *pixs, l_int32 finalcolors) { l_int32 i, ncolors, index, tempindex; l_int32 *tab; l_uint32 tempcolor; NUMA *na, *nasi; PIX *pixm; PIXCMAP *cmap; PROCNAME("pixColorSegmentRemoveColors"); if (!pixd) return ERROR_INT("pixd not defined", procName, 1); if (pixGetDepth(pixd) != 8) return ERROR_INT("pixd not 8 bpp", procName, 1); if ((cmap = pixGetColormap(pixd)) == NULL) return ERROR_INT("cmap not found", procName, 1); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); ncolors = pixcmapGetCount(cmap); if (finalcolors >= ncolors) /* few enough colors already; nothing to do */ return 0; /* Generate a mask over all pixels that are not in the * 'finalcolors' most populated colors. Save the colormap * index of any one of the retained colors in 'tempindex'. * The LUT has values 0 for the 'finalcolors' most populated colors, * which will be retained; and 1 for the rest, which are marked * by fg pixels in pixm and will be removed. */ na = pixGetCmapHistogram(pixd, 1); if ((nasi = numaGetSortIndex(na, L_SORT_DECREASING)) == NULL) { numaDestroy(&na); return ERROR_INT("nasi not made", procName, 1); } numaGetIValue(nasi, finalcolors - 1, &tempindex); /* retain down to this */ pixcmapGetColor32(cmap, tempindex, &tempcolor); /* use this color */ tab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32)); for (i = finalcolors; i < ncolors; i++) { numaGetIValue(nasi, i, &index); tab[index] = 1; } pixm = pixMakeMaskFromLUT(pixd, tab); LEPT_FREE(tab); /* Reassign the masked pixels temporarily to the saved index * (tempindex). This guarantees that no pixels are labeled by * a colormap index of any colors that will be removed. * The actual value doesn't matter, as long as it's one * of the retained colors, because these pixels will later * be reassigned based on the full set of colors retained * in the colormap. */ pixSetMasked(pixd, pixm, tempcolor); /* Now remove unused colors from the colormap. This reassigns * image pixels as required. */ pixRemoveUnusedColors(pixd); /* Finally, reassign the pixels under the mask (those that were * given a 'tempindex' value) to the nearest color in the colormap. * This is the function used in phase 2 on all image pixels; here * it is only used on the masked pixels given by pixm. */ pixAssignToNearestColor(pixd, pixs, pixm, LEVEL_IN_OCTCUBE, NULL); pixDestroy(&pixm); numaDestroy(&na); numaDestroy(&nasi); return 0; }