blob: 8444ee9b4362e3e1bf5eb5235e18d0ead7798f64 [file] [log] [blame]
/*====================================================================*
- Copyright (C) 2001 Leptonica. All rights reserved.
- This software is distributed in the hope that it will be
- useful, but with NO WARRANTY OF ANY KIND.
- No author or distributor accepts responsibility to anyone for the
- consequences of using this software, or for whether it serves any
- particular purpose or works at all, unless he or she says so in
- writing. Everyone is granted permission to copy, modify and
- redistribute this source code, for commercial or non-commercial
- purposes, with the following restrictions: (1) the origin of this
- source code must not be misrepresented; (2) modified versions must
- be plainly marked as such; and (3) this notice may not be removed
- or altered from any source or modified source distribution.
*====================================================================*/
/*
* colorseg.c
*
* Unsupervised color segmentation
*
* PIX *pixColorSegment()
* PIX *pixColorSegmentCluster()
* static l_int32 pixColorSegmentTryCluster()
* l_int32 pixAssignToNearestColor()
* l_int32 pixColorSegmentClean()
* l_int32 pixColorSegmentRemoveColors()
*
* Selection and display of range of colors in HSV space
*
* PIX *pixMakeRangeMaskHS()
* PIX *pixMakeRangeMaskHV()
* PIX *pixMakeRangeMaskSV()
* PIX *pixMakeHistoHS()
* PIX *pixMakeHistoHV()
* PIX *pixMakeHistoSV()
* PIX *pixFindHistoPeaksHSV()
* PIX *displayHSVColorRange()
*/
#include <stdio.h>
#include <stdlib.h>
#include "allheaders.h"
/* Maximum allowed iterations in Phase 1. */
static const l_int32 MAX_ALLOWED_ITERATIONS = 20;
/* Factor by which max dist is increased on each iteration */
static const l_float32 DIST_EXPAND_FACT = 1.3;
/* Octcube division level for computing nearest colormap color using LUT.
* Using 4 should suffice for up to 50 - 100 colors, and it is
* very fast. Using 5 takes 8 times as long to set up the LUT
* for little perceptual gain, even with 100 colors. */
static const l_int32 LEVEL_IN_OCTCUBE = 4;
static l_int32 pixColorSegmentTryCluster(PIX *pixd, PIX *pixs,
l_int32 maxdist, l_int32 maxcolors);
#ifndef NO_CONSOLE_IO
#define DEBUG_HISTO 1
#endif /* ~NO_CONSOLE_IO */
/*------------------------------------------------------------------*
* Unsupervised color segmentation *
*------------------------------------------------------------------*/
/*!
* 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;
}
/*!
* pixColorSegmentCluster()
*
* Input: pixs (32 bpp; 24-bit color)
* maxdist (max euclidean dist to existing cluster)
* maxcolors (max number of colors allowed in first pass)
* Return: pixd (8 bit with colormap), or null on error
*
* Notes:
* (1) This is phase 1. See description in pixColorSegment().
* (2) Greedy unsupervised classification. If the limit 'maxcolors'
* is exceeded, the computation is repeated with a larger
* allowed cluster size.
* (3) On each successive iteration, 'maxdist' is increased by a
* constant factor. See comments in pixColorSegment() for
* a guideline on parameter selection.
* Note that the diagonal of the 8-bit rgb color cube is about
* 440, so for 'maxdist' = 440, you are guaranteed to get 1 color!
*/
PIX *
pixColorSegmentCluster(PIX *pixs,
l_int32 maxdist,
l_int32 maxcolors)
{
l_int32 w, h, newmaxdist, ret, niters, ncolors, success;
PIX *pixd;
PIXCMAP *cmap;
PROCNAME("pixColorSegmentCluster");
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);
w = pixGetWidth(pixs);
h = pixGetHeight(pixs);
if ((pixd = pixCreate(w, h, 8)) == NULL)
return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
cmap = pixcmapCreate(8);
pixSetColormap(pixd, cmap);
pixCopyResolution(pixd, pixs);
newmaxdist = maxdist;
niters = 0;
success = TRUE;
while (1) {
ret = pixColorSegmentTryCluster(pixd, pixs, newmaxdist, maxcolors);
niters++;
if (!ret) {
ncolors = pixcmapGetCount(cmap);
L_INFO_INT2("Success with %d colors after %d iters", procName,
ncolors, niters);
break;
}
if (niters == MAX_ALLOWED_ITERATIONS) {
L_WARNING_INT("too many iters; newmaxdist = %d",
procName, newmaxdist);
success = FALSE;
break;
}
newmaxdist = (l_int32)(DIST_EXPAND_FACT * (l_float32)newmaxdist);
}
if (!success) {
pixDestroy(&pixd);
return (PIX *)ERROR_PTR("failure in phase 1", procName, NULL);
}
return pixd;
}
/*!
* pixColorSegmentTryCluster()
*
* Input: pixd
* pixs
* maxdist
* maxcolors
* Return: 0 if OK, 1 on error
*
* Note: This function should only be called from pixColorSegCluster()
*/
static l_int32
pixColorSegmentTryCluster(PIX *pixd,
PIX *pixs,
l_int32 maxdist,
l_int32 maxcolors)
{
l_int32 rmap[256], gmap[256], bmap[256];
l_int32 w, h, wpls, wpld, i, j, k, found, ret, index, ncolors;
l_int32 rval, gval, bval, dist2, maxdist2;
l_int32 countarray[256];
l_int32 rsum[256], gsum[256], bsum[256];
l_uint32 *ppixel;
l_uint32 *datas, *datad, *lines, *lined;
PIXCMAP *cmap;
PROCNAME("pixColorSegmentTryCluster");
if (!pixs)
return ERROR_INT("pixs not defined", procName, 1);
if (!pixd)
return ERROR_INT("pixd not defined", procName, 1);
w = pixGetWidth(pixs);
h = pixGetHeight(pixs);
maxdist2 = maxdist * maxdist;
cmap = pixGetColormap(pixd);
pixcmapClear(cmap);
for (k = 0; k < 256; k++) {
rsum[k] = gsum[k] = bsum[k] = 0;
rmap[k] = gmap[k] = bmap[k] = 0;
}
datas = pixGetData(pixs);
datad = pixGetData(pixd);
wpls = pixGetWpl(pixs);
wpld = pixGetWpl(pixd);
for (i = 0; i < h; i++) {
lines = datas + i * wpls;
lined = datad + i * wpld;
for (j = 0; j < w; j++) {
ppixel = lines + j;
rval = GET_DATA_BYTE(ppixel, COLOR_RED);
gval = GET_DATA_BYTE(ppixel, COLOR_GREEN);
bval = GET_DATA_BYTE(ppixel, COLOR_BLUE);
ncolors = pixcmapGetCount(cmap);
found = FALSE;
for (k = 0; k < ncolors; k++) {
dist2 = (rval - rmap[k]) * (rval - rmap[k]) +
(gval - gmap[k]) * (gval - gmap[k]) +
(bval - bmap[k]) * (bval - bmap[k]);
if (dist2 <= maxdist2) { /* take it; greedy */
found = TRUE;
SET_DATA_BYTE(lined, j, k);
countarray[k]++;
rsum[k] += rval;
gsum[k] += gval;
bsum[k] += bval;
break;
}
}
if (!found) { /* Add a new color */
ret = pixcmapAddNewColor(cmap, rval, gval, bval, &index);
/* fprintf(stderr,
"index = %d, (i,j) = (%d,%d), rgb = (%d, %d, %d)\n",
index, i, j, rval, gval, bval); */
if (ret == 0 && index < maxcolors) {
countarray[index] = 1;
SET_DATA_BYTE(lined, j, index);
rmap[index] = rval;
gmap[index] = gval;
bmap[index] = bval;
rsum[index] = rval;
gsum[index] = gval;
bsum[index] = bval;
}
else {
L_INFO_INT("maxcolors exceeded for maxdist = %d",
procName, maxdist);
return 1;
}
}
}
}
/* Replace the colors in the colormap by the averages */
for (k = 0; k < ncolors; k++) {
rval = rsum[k] / countarray[k];
gval = gsum[k] / countarray[k];
bval = bsum[k] / countarray[k];
pixcmapResetColor(cmap, k, rval, gval, bval);
}
return 0;
}
/*!
* pixAssignToNearestColor()
*
* Input: pixd (8 bpp, colormapped)
* pixs (32 bpp; 24-bit color)
* pixm (<optional> 1 bpp)
* level (of octcube used for finding nearest color in cmap)
* countarray (<optional> ptr to array, in which we can store
* the number of pixels found in each color in
* the colormap in pixd)
* Return: 0 if OK, 1 on error
*
* Notes:
* (1) This is used in phase 2 of color segmentation, where pixs
* is the original input image to pixColorSegment(), and
* pixd is the colormapped image returned from
* pixColorSegmentCluster(). It is also used, with a mask,
* in phase 4.
* (2) This is an in-place operation.
* (3) The colormap in pixd is unchanged.
* (4) pixs and pixd must be the same size (w, h).
* (5) The selection mask pixm can be null. If it exists, it must
* be the same size as pixs and pixd, and only pixels
* corresponding to fg in pixm are assigned. Set to
* NULL if all pixels in pixd are to be assigned.
* (6) The countarray can be null. If it exists, it is pre-allocated
* and of a size at least equal to the size of the colormap in pixd.
* (7) This does a best-fit (non-greedy) assignment of pixels to
* existing clusters. Specifically, it assigns each pixel
* in pixd to the color index in the pixd colormap that has a
* color closest to the corresponding rgb pixel in pixs.
* (8) 'level' is the octcube level used to quickly find the nearest
* color in the colormap for each pixel. For color segmentation,
* this parameter is set to LEVEL_IN_OCTCUBE.
* (9) We build a mapping table from octcube to colormap index so
* that this function can run in a time (otherwise) independent
* of the number of colors in the colormap. This avoids a
* brute-force search for the closest colormap color to each
* pixel in the image.
*/
l_int32
pixAssignToNearestColor(PIX *pixd,
PIX *pixs,
PIX *pixm,
l_int32 level,
l_int32 *countarray)
{
l_int32 w, h, wpls, wpld, wplm, i, j;
l_int32 rval, gval, bval, index;
l_int32 *cmaptab;
l_uint32 octindex;
l_uint32 *rtab, *gtab, *btab;
l_uint32 *ppixel;
l_uint32 *datas, *datad, *datam, *lines, *lined, *linem;
PIXCMAP *cmap;
PROCNAME("pixAssignToNearestColor");
if (!pixd)
return ERROR_INT("pixd not defined", 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);
if (pixGetDepth(pixs) != 32)
return ERROR_INT("pixs not 32 bpp", procName, 1);
/* Set up the tables to map rgb to the nearest colormap index */
if (makeRGBToIndexTables(&rtab, &gtab, &btab, level))
return ERROR_INT("index tables not made", procName, 1);
if ((cmaptab = pixcmapToOctcubeLUT(cmap, level, L_MANHATTAN_DISTANCE))
== NULL)
return ERROR_INT("cmaptab not made", procName, 1);
w = pixGetWidth(pixs);
h = pixGetHeight(pixs);
datas = pixGetData(pixs);
datad = pixGetData(pixd);
wpls = pixGetWpl(pixs);
wpld = pixGetWpl(pixd);
if (pixm) {
datam = pixGetData(pixm);
wplm = pixGetWpl(pixm);
}
for (i = 0; i < h; i++) {
lines = datas + i * wpls;
lined = datad + i * wpld;
if (pixm)
linem = datam + i * wplm;
for (j = 0; j < w; j++) {
if (pixm) {
if (!GET_DATA_BIT(linem, j))
continue;
}
ppixel = lines + j;
rval = GET_DATA_BYTE(ppixel, COLOR_RED);
gval = GET_DATA_BYTE(ppixel, COLOR_GREEN);
bval = GET_DATA_BYTE(ppixel, COLOR_BLUE);
/* Map from rgb to octcube index */
getOctcubeIndexFromRGB(rval, gval, bval, rtab, gtab, btab,
&octindex);
/* Map from octcube index to nearest colormap index */
index = cmaptab[octindex];
if (countarray)
countarray[index]++;
SET_DATA_BYTE(lined, j, index);
}
}
FREE(cmaptab);
FREE(rtab);
FREE(gtab);
FREE(btab);
return 0;
}
/*!
* pixColorSegmentClean()
*
* Input: pixs (8 bpp, colormapped)
* selsize (for closing)
* countarray (ptr to array containing the number of pixels
* found in each color in the colormap)
* Return: 0 if OK, 1 on error
*
* Notes:
* (1) This operation is in-place.
* (2) This is phase 3 of color segmentation. It is the first
* part of a two-step noise removal process. Colors with a
* large population are closed first; this operation absorbs
* small sets of intercolated pixels of a different color.
*/
l_int32
pixColorSegmentClean(PIX *pixs,
l_int32 selsize,
l_int32 *countarray)
{
l_int32 i, ncolors, val;
NUMA *na, *nasi;
PIX *pixt1, *pixt2;
PIXCMAP *cmap;
SEL *sel;
PROCNAME("pixColorSegmentClean");
if (!pixs)
return ERROR_INT("pixs not defined", procName, 1);
if (pixGetDepth(pixs) != 8)
return ERROR_INT("pixs not 8 bpp", procName, 1);
if ((cmap = pixGetColormap(pixs)) == NULL)
return ERROR_INT("cmap not found", procName, 1);
if (!countarray)
return ERROR_INT("countarray not defined", procName, 1);
if (selsize <= 1)
return 0; /* nothing to do */
/* Sort colormap indices in decreasing order of pixel population */
ncolors = pixcmapGetCount(cmap);
na = numaCreate(ncolors);
for (i = 0; i < ncolors; i++)
numaAddNumber(na, countarray[i]);
if ((nasi = numaGetSortIndex(na, L_SORT_DECREASING)) == NULL)
return ERROR_INT("nasi not made", procName, 1);
/* For each color, in order of decreasing population,
* do a closing and absorb the added pixels. Note that
* if the closing removes pixels at the border, they'll
* still appear in the xor and will be properly (re)set. */
sel = selCreateBrick(selsize, selsize, selsize / 2, selsize / 2, SEL_HIT);
for (i = 0; i < ncolors; i++) {
numaGetIValue(nasi, i, &val);
pixt1 = pixGenerateMaskByValue(pixs, val);
pixt2 = pixClose(NULL, pixt1, sel);
pixXor(pixt2, pixt2, pixt1); /* pixels to be added to type 'val' */
pixSetMasked(pixs, pixt2, val); /* add them */
pixDestroy(&pixt1);
pixDestroy(&pixt2);
}
selDestroy(&sel);
numaDestroy(&na);
numaDestroy(&nasi);
return 0;
}
/*!
* pixColorSegmentRemoveColors()
*
* Input: pixd (8 bpp, colormapped)
* pixs (24 bpp rgb, with initial pixel values)
* finalcolors (max number of colors to retain)
* Return: 0 if OK, 1 on error
*
* 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.
*/
l_int32
pixColorSegmentRemoveColors(PIX *pixd,
PIX *pixs,
l_int32 finalcolors)
{
l_int32 i, w, h, npix, minpix, ncolors, index, selected, tempcolor;
NUMA *na, *nasi;
PIX *pixm, *pixt;
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 'tempcolor'. */
na = pixGetGrayHistogram(pixd, 1);
if ((nasi = numaGetSortIndex(na, L_SORT_DECREASING)) == NULL)
return ERROR_INT("nasi not made", procName, 1);
numaGetIValue(nasi, finalcolors - 1, &index); /* retain down to this */
numaGetIValue(na, index, &minpix);
w = pixGetWidth(pixd);
h = pixGetHeight(pixd);
pixm = pixCreate(w, h, 1); /* initial mask with no fg pixels */
selected = FALSE;
for (i = 0; i < ncolors; i++) {
numaGetIValue(na, i, &npix);
if (npix < minpix && npix > 0) { /* these pixels to be reassigned */
pixt = pixGenerateMaskByValue(pixd, i);
pixOr(pixm, pixm, pixt);
pixDestroy(&pixt);
}
else if (selected == FALSE) {
tempcolor = i;
selected = TRUE;
}
}
/* Reassign the masked pixels temporarily to the saved index
* (tempcolor). 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 'tempcolor' 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;
}
/*------------------------------------------------------------------*
* Selection and display of range of colors in HSV space *
*------------------------------------------------------------------*/
/*!
* pixMakeRangeMaskHS()
*
* Input: pixs (24 bpp rgb)
* huecenter (center value of hue range)
* huehw (half-width of hue range)
* satcenter (center value of saturation range)
* sathw (half-width of saturation range)
* regionflag (L_INCLUDE_REGION, L_EXCLUDE_REGION)
* Return: pixd (1 bpp mask over selected pixels), or null on error
*
* Notes:
* (1) The pixels are selected based on the specified ranges of
* hue and saturation. For selection or exclusion, the pixel
* HS component values must be within both ranges. Care must
* be taken in finding the hue range because of wrap-around.
* (2) Use @regionflag == L_INCLUDE_REGION to take only those
* pixels within the rectangular region specified in HS space.
* Use @regionflag == L_EXCLUDE_REGION to take all pixels except
* those within the rectangular region specified in HS space.
*/
PIX *
pixMakeRangeMaskHS(PIX *pixs,
l_int32 huecenter,
l_int32 huehw,
l_int32 satcenter,
l_int32 sathw,
l_int32 regionflag)
{
l_int32 i, j, w, h, wplt, wpld, hstart, hend, sstart, send, hval, sval;
l_int32 *hlut, *slut;
l_uint32 pixel;
l_uint32 *datat, *datad, *linet, *lined;
PIX *pixt, *pixd;
PROCNAME("pixMakeRangeMaskHS");
if (!pixs || pixGetDepth(pixs) != 32)
return (PIX *)ERROR_PTR("pixs undefined or not 32 bpp", procName, NULL);
if (regionflag != L_INCLUDE_REGION && regionflag != L_EXCLUDE_REGION)
return (PIX *)ERROR_PTR("invalid regionflag", procName, NULL);
/* Set up LUTs for hue and saturation. These have the value 1
* within the specified intervals of hue and saturation. */
hlut = (l_int32 *)CALLOC(240, sizeof(l_int32));
slut = (l_int32 *)CALLOC(256, sizeof(l_int32));
sstart = L_MAX(0, satcenter - sathw);
send = L_MIN(255, satcenter + sathw);
for (i = sstart; i <= send; i++)
slut[i] = 1;
hstart = (huecenter - huehw + 240) % 240;
hend = (huecenter + huehw + 240) % 240;
if (hstart < hend) {
for (i = hstart; i <= hend; i++)
hlut[i] = 1;
}
else { /* wrap */
for (i = hstart; i < 240; i++)
hlut[i] = 1;
for (i = 0; i <= hend; i++)
hlut[i] = 1;
}
/* Generate the mask */
pixt = pixConvertRGBToHSV(NULL, pixs);
pixGetDimensions(pixs, &w, &h, NULL);
pixd = pixCreateNoInit(w, h, 1);
if (regionflag == L_INCLUDE_REGION)
pixClearAll(pixd);
else /* L_EXCLUDE_REGION */
pixSetAll(pixd);
datat = pixGetData(pixt);
datad = pixGetData(pixd);
wplt = pixGetWpl(pixt);
wpld = pixGetWpl(pixd);
for (i = 0; i < h; i++) {
linet = datat + i * wplt;
lined = datad + i * wpld;
for (j = 0; j < w; j++) {
pixel = linet[j];
hval = (pixel >> L_RED_SHIFT) & 0xff;
sval = (pixel >> L_GREEN_SHIFT) & 0xff;
if (hlut[hval] == 1 && slut[sval] == 1) {
if (regionflag == L_INCLUDE_REGION)
SET_DATA_BIT(lined, j);
else /* L_EXCLUDE_REGION */
CLEAR_DATA_BIT(lined, j);
}
}
}
FREE(hlut);
FREE(slut);
pixDestroy(&pixt);
return pixd;
}
/*!
* pixMakeRangeMaskHV()
*
* Input: pixs (24 bpp rgb)
* huecenter (center value of hue range)
* huehw (half-width of hue range)
* valcenter (center value of max intensity range)
* valhw (half-width of max intensity range)
* regionflag (L_INCLUDE_REGION, L_EXCLUDE_REGION)
* Return: pixd (1 bpp mask over selected pixels), or null on error
*
* Notes:
* (1) The pixels are selected based on the specified ranges of
* hue and max intensity values. For selection or exclusion,
* the pixel HV component values must be within both ranges.
* Care must be taken in finding the hue range because of wrap-around.
* (2) Use @regionflag == L_INCLUDE_REGION to take only those
* pixels within the rectangular region specified in HV space.
* Use @regionflag == L_EXCLUDE_REGION to take all pixels except
* those within the rectangular region specified in HV space.
*/
PIX *
pixMakeRangeMaskHV(PIX *pixs,
l_int32 huecenter,
l_int32 huehw,
l_int32 valcenter,
l_int32 valhw,
l_int32 regionflag)
{
l_int32 i, j, w, h, wplt, wpld, hstart, hend, vstart, vend, hval, vval;
l_int32 *hlut, *vlut;
l_uint32 pixel;
l_uint32 *datat, *datad, *linet, *lined;
PIX *pixt, *pixd;
PROCNAME("pixMakeRangeMaskHV");
if (!pixs || pixGetDepth(pixs) != 32)
return (PIX *)ERROR_PTR("pixs undefined or not 32 bpp", procName, NULL);
if (regionflag != L_INCLUDE_REGION && regionflag != L_EXCLUDE_REGION)
return (PIX *)ERROR_PTR("invalid regionflag", procName, NULL);
/* Set up LUTs for hue and maximum intensity (val). These have
* the value 1 within the specified intervals of hue and value. */
hlut = (l_int32 *)CALLOC(240, sizeof(l_int32));
vlut = (l_int32 *)CALLOC(256, sizeof(l_int32));
vstart = L_MAX(0, valcenter - valhw);
vend = L_MIN(255, valcenter + valhw);
for (i = vstart; i <= vend; i++)
vlut[i] = 1;
hstart = (huecenter - huehw + 240) % 240;
hend = (huecenter + huehw + 240) % 240;
if (hstart < hend) {
for (i = hstart; i <= hend; i++)
hlut[i] = 1;
}
else {
for (i = hstart; i < 240; i++)
hlut[i] = 1;
for (i = 0; i <= hend; i++)
hlut[i] = 1;
}
/* Generate the mask */
pixt = pixConvertRGBToHSV(NULL, pixs);
pixGetDimensions(pixs, &w, &h, NULL);
pixd = pixCreateNoInit(w, h, 1);
if (regionflag == L_INCLUDE_REGION)
pixClearAll(pixd);
else /* L_EXCLUDE_REGION */
pixSetAll(pixd);
datat = pixGetData(pixt);
datad = pixGetData(pixd);
wplt = pixGetWpl(pixt);
wpld = pixGetWpl(pixd);
for (i = 0; i < h; i++) {
linet = datat + i * wplt;
lined = datad + i * wpld;
for (j = 0; j < w; j++) {
pixel = linet[j];
hval = (pixel >> L_RED_SHIFT) & 0xff;
vval = (pixel >> L_BLUE_SHIFT) & 0xff;
if (hlut[hval] == 1 && vlut[vval] == 1) {
if (regionflag == L_INCLUDE_REGION)
SET_DATA_BIT(lined, j);
else /* L_EXCLUDE_REGION */
CLEAR_DATA_BIT(lined, j);
}
}
}
FREE(hlut);
FREE(vlut);
pixDestroy(&pixt);
return pixd;
}
/*!
* pixMakeRangeMaskSV()
*
* Input: pixs (24 bpp rgb)
* satcenter (center value of saturation range)
* sathw (half-width of saturation range)
* valcenter (center value of max intensity range)
* valhw (half-width of max intensity range)
* regionflag (L_INCLUDE_REGION, L_EXCLUDE_REGION)
* Return: pixd (1 bpp mask over selected pixels), or null on error
*
* Notes:
* (1) The pixels are selected based on the specified ranges of
* saturation and max intensity (val). For selection or
* exclusion, the pixel SV component values must be within both ranges.
* (2) Use @regionflag == L_INCLUDE_REGION to take only those
* pixels within the rectangular region specified in SV space.
* Use @regionflag == L_EXCLUDE_REGION to take all pixels except
* those within the rectangular region specified in SV space.
*/
PIX *
pixMakeRangeMaskSV(PIX *pixs,
l_int32 satcenter,
l_int32 sathw,
l_int32 valcenter,
l_int32 valhw,
l_int32 regionflag)
{
l_int32 i, j, w, h, wplt, wpld, sval, vval, sstart, send, vstart, vend;
l_int32 *slut, *vlut;
l_uint32 pixel;
l_uint32 *datat, *datad, *linet, *lined;
PIX *pixt, *pixd;
PROCNAME("pixMakeRangeMaskSV");
if (!pixs || pixGetDepth(pixs) != 32)
return (PIX *)ERROR_PTR("pixs undefined or not 32 bpp", procName, NULL);
if (regionflag != L_INCLUDE_REGION && regionflag != L_EXCLUDE_REGION)
return (PIX *)ERROR_PTR("invalid regionflag", procName, NULL);
/* Set up LUTs for saturation and max intensity (val).
* These have the value 1 within the specified intervals of
* saturation and max intensity. */
slut = (l_int32 *)CALLOC(256, sizeof(l_int32));
vlut = (l_int32 *)CALLOC(256, sizeof(l_int32));
sstart = L_MAX(0, satcenter - sathw);
send = L_MIN(255, satcenter + sathw);
vstart = L_MAX(0, valcenter - valhw);
vend = L_MIN(255, valcenter + valhw);
for (i = sstart; i <= send; i++)
slut[i] = 1;
for (i = vstart; i <= vend; i++)
vlut[i] = 1;
/* Generate the mask */
pixt = pixConvertRGBToHSV(NULL, pixs);
pixGetDimensions(pixs, &w, &h, NULL);
pixd = pixCreateNoInit(w, h, 1);
if (regionflag == L_INCLUDE_REGION)
pixClearAll(pixd);
else /* L_EXCLUDE_REGION */
pixSetAll(pixd);
datat = pixGetData(pixt);
datad = pixGetData(pixd);
wplt = pixGetWpl(pixt);
wpld = pixGetWpl(pixd);
for (i = 0; i < h; i++) {
linet = datat + i * wplt;
lined = datad + i * wpld;
for (j = 0; j < w; j++) {
pixel = linet[j];
sval = (pixel >> L_GREEN_SHIFT) & 0xff;
vval = (pixel >> L_BLUE_SHIFT) & 0xff;
if (slut[sval] == 1 && vlut[vval] == 1) {
if (regionflag == L_INCLUDE_REGION)
SET_DATA_BIT(lined, j);
else /* L_EXCLUDE_REGION */
CLEAR_DATA_BIT(lined, j);
}
}
}
FREE(slut);
FREE(vlut);
pixDestroy(&pixt);
return pixd;
}
/*!
* pixMakeHistoHS()
*
* Input: pixs (HSV colorspace)
* factor (subsampling factor; integer)
* &nahue (<optional return> hue histogram)
* &nasat (<optional return> saturation histogram)
* Return: pixd (32 bpp histogram in hue and saturation), or null on error
*
* Notes:
* (1) pixs is a 32 bpp image in HSV colorspace; hue is in the "red"
* byte, saturation is in the "green" byte.
* (2) In pixd, hue is displayed vertically; saturation horizontally.
* The dimensions of pixd are w = 256, h = 240, and the depth
* is 32 bpp. The value at each point is simply the number
* of pixels found at that value of hue and saturation.
*/
PIX *
pixMakeHistoHS(PIX *pixs,
l_int32 factor,
NUMA **pnahue,
NUMA **pnasat)
{
l_int32 i, j, w, h, wplt, hval, sval, nd;
l_uint32 pixel;
l_uint32 *datat, *linet;
void **lined32;
NUMA *nahue, *nasat;
PIX *pixt, *pixd;
PROCNAME("pixMakeHistoHS");
if (pnahue) *pnahue = NULL;
if (pnasat) *pnasat = NULL;
if (!pixs || pixGetDepth(pixs) != 32)
return (PIX *)ERROR_PTR("pixs undefined or not 32 bpp", procName, NULL);
if (pnahue) {
nahue = numaCreate(240);
numaSetCount(nahue, 240);
*pnahue = nahue;
}
if (pnasat) {
nasat = numaCreate(256);
numaSetCount(nasat, 256);
*pnasat = nasat;
}
if (factor <= 1)
pixt = pixClone(pixs);
else
pixt = pixScaleBySampling(pixs, 1.0 / (l_float32)factor,
1.0 / (l_float32)factor);
/* Create the hue-saturation histogram */
pixd = pixCreate(256, 240, 32);
lined32 = pixGetLinePtrs(pixd, NULL);
pixGetDimensions(pixt, &w, &h, NULL);
datat = pixGetData(pixt);
wplt = pixGetWpl(pixt);
for (i = 0; i < h; i++) {
linet = datat + i * wplt;
for (j = 0; j < w; j++) {
pixel = linet[j];
hval = (pixel >> L_RED_SHIFT) & 0xff;
#if DEBUG_HISTO
if (hval > 239) {
fprintf(stderr, "hval = %d for (%d,%d)\n", hval, i, j);
continue;
}
#endif /* DEBUG_HISTO */
sval = (pixel >> L_GREEN_SHIFT) & 0xff;
if (pnahue)
numaShiftValue(nahue, hval, 1.0);
if (pnasat)
numaShiftValue(nasat, sval, 1.0);
nd = GET_DATA_FOUR_BYTES(lined32[hval], sval);
SET_DATA_FOUR_BYTES(lined32[hval], sval, nd + 1);
}
}
FREE(lined32);
pixDestroy(&pixt);
return pixd;
}
/*!
* pixMakeHistoHV()
*
* Input: pixs (HSV colorspace)
* factor (subsampling factor; integer)
* &nahue (<optional return> hue histogram)
* &naval (<optional return> max intensity (value) histogram)
* Return: pixd (32 bpp histogram in hue and value), or null on error
*
* Notes:
* (1) pixs is a 32 bpp image in HSV colorspace; hue is in the "red"
* byte, max intensity ("value") is in the "blue" byte.
* (2) In pixd, hue is displayed vertically; intensity horizontally.
* The dimensions of pixd are w = 256, h = 240, and the depth
* is 32 bpp. The value at each point is simply the number
* of pixels found at that value of hue and intensity.
*/
PIX *
pixMakeHistoHV(PIX *pixs,
l_int32 factor,
NUMA **pnahue,
NUMA **pnaval)
{
l_int32 i, j, w, h, wplt, hval, vval, nd;
l_uint32 pixel;
l_uint32 *datat, *linet;
void **lined32;
NUMA *nahue, *naval;
PIX *pixt, *pixd;
PROCNAME("pixMakeHistoHV");
if (pnahue) *pnahue = NULL;
if (pnaval) *pnaval = NULL;
if (!pixs || pixGetDepth(pixs) != 32)
return (PIX *)ERROR_PTR("pixs undefined or not 32 bpp", procName, NULL);
if (pnahue) {
nahue = numaCreate(240);
numaSetCount(nahue, 240);
*pnahue = nahue;
}
if (pnaval) {
naval = numaCreate(256);
numaSetCount(naval, 256);
*pnaval = naval;
}
if (factor <= 1)
pixt = pixClone(pixs);
else
pixt = pixScaleBySampling(pixs, 1.0 / (l_float32)factor,
1.0 / (l_float32)factor);
/* Create the hue-value histogram */
pixd = pixCreate(256, 240, 32);
lined32 = pixGetLinePtrs(pixd, NULL);
pixGetDimensions(pixt, &w, &h, NULL);
datat = pixGetData(pixt);
wplt = pixGetWpl(pixt);
for (i = 0; i < h; i++) {
linet = datat + i * wplt;
for (j = 0; j < w; j++) {
pixel = linet[j];
hval = (pixel >> L_RED_SHIFT) & 0xff;
vval = (pixel >> L_BLUE_SHIFT) & 0xff;
if (pnahue)
numaShiftValue(nahue, hval, 1.0);
if (pnaval)
numaShiftValue(naval, vval, 1.0);
nd = GET_DATA_FOUR_BYTES(lined32[hval], vval);
SET_DATA_FOUR_BYTES(lined32[hval], vval, nd + 1);
}
}
FREE(lined32);
pixDestroy(&pixt);
return pixd;
}
/*!
* pixMakeHistoSV()
*
* Input: pixs (HSV colorspace)
* factor (subsampling factor; integer)
* &nasat (<optional return> sat histogram)
* &naval (<optional return> max intensity (value) histogram)
* Return: pixd (32 bpp histogram in sat and value), or null on error
*
* Notes:
* (1) pixs is a 32 bpp image in HSV colorspace; sat is in the "green"
* byte, max intensity ("value") is in the "blue" byte.
* (2) In pixd, sat is displayed vertically; intensity horizontally.
* The dimensions of pixd are w = 256, h = 256, and the depth
* is 32 bpp. The value at each point is simply the number
* of pixels found at that value of saturation and intensity.
*/
PIX *
pixMakeHistoSV(PIX *pixs,
l_int32 factor,
NUMA **pnasat,
NUMA **pnaval)
{
l_int32 i, j, w, h, wplt, sval, vval, nd;
l_uint32 pixel;
l_uint32 *datat, *linet;
void **lined32;
NUMA *nasat, *naval;
PIX *pixt, *pixd;
PROCNAME("pixMakeHistoSV");
if (pnasat) *pnasat = NULL;
if (pnaval) *pnaval = NULL;
if (!pixs || pixGetDepth(pixs) != 32)
return (PIX *)ERROR_PTR("pixs undefined or not 32 bpp", procName, NULL);
if (pnasat) {
nasat = numaCreate(256);
numaSetCount(nasat, 256);
*pnasat = nasat;
}
if (pnaval) {
naval = numaCreate(256);
numaSetCount(naval, 256);
*pnaval = naval;
}
if (factor <= 1)
pixt = pixClone(pixs);
else
pixt = pixScaleBySampling(pixs, 1.0 / (l_float32)factor,
1.0 / (l_float32)factor);
/* Create the hue-value histogram */
pixd = pixCreate(256, 256, 32);
lined32 = pixGetLinePtrs(pixd, NULL);
pixGetDimensions(pixt, &w, &h, NULL);
datat = pixGetData(pixt);
wplt = pixGetWpl(pixt);
for (i = 0; i < h; i++) {
linet = datat + i * wplt;
for (j = 0; j < w; j++) {
pixel = linet[j];
sval = (pixel >> L_GREEN_SHIFT) & 0xff;
vval = (pixel >> L_BLUE_SHIFT) & 0xff;
if (pnasat)
numaShiftValue(nasat, sval, 1.0);
if (pnaval)
numaShiftValue(naval, vval, 1.0);
nd = GET_DATA_FOUR_BYTES(lined32[sval], vval);
SET_DATA_FOUR_BYTES(lined32[sval], vval, nd + 1);
}
}
FREE(lined32);
pixDestroy(&pixt);
return pixd;
}
/*!
* pixFindHistoPeaksHSV()
*
* Input: pixs (32 bpp; HS, HV or SV histogram; not changed)
* type (L_HS_HISTO, L_HV_HISTO or L_SV_HISTO)
* width (half width of sliding window)
* height (half height of sliding window)
* npeaks (number of peaks to look for)
* erasefactor (ratio of erase window size to sliding window size)
* &pta (locations of maximum for each integrated peak area)
* &natot (integrated peak areas)
* &pixa (<optional return> pixa for debugging; NULL to skip)
* Return: 0 if OK, 1 on error
*
* Notes:
* (1) pixs is a 32 bpp histogram in a pair of HSV colorspace. It
* should be thought of as a single component with 32 bpc.
* (2) After each peak is found, the peak is erased with a window
* that is centered on the peak and scaled from the sliding
* window by @erasefactor. Typically, @erasefactor is chosen
* to be > 1.0.
* (3) Data for a maximum of @npeaks is returned in @pta and @natot.
*/
l_int32
pixFindHistoPeaksHSV(PIX *pixs,
l_int32 type,
l_int32 width,
l_int32 height,
l_int32 npeaks,
l_float32 erasefactor,
PTA **ppta,
NUMA **pnatot,
PIXA **ppixa)
{
l_int32 i, xmax, ymax, ewidth, eheight;
l_uint32 maxval;
BOX *box;
NUMA *natot;
PIX *pixh, *pixw, *pixt1, *pixt2;
PTA *pta;
PROCNAME("pixFindHistoPeaksHSV");
if (!pixs || pixGetDepth(pixs) != 32)
return ERROR_INT("pixs undefined or not 32 bpp", procName, 1);
if (!ppta || !pnatot)
return ERROR_INT("&pta and &natot not both defined", procName, 1);
if (type != L_HS_HISTO && type != L_HV_HISTO && type != L_SV_HISTO)
return ERROR_INT("invalid HSV histo type", procName, 1);
if ((pta = ptaCreate(npeaks)) == NULL)
return ERROR_INT("pta not made", procName, 1);
*ppta = pta;
if ((natot = numaCreate(npeaks)) == NULL)
return ERROR_INT("natot not made", procName, 1);
*pnatot = natot;
*ppta = pta;
if (type == L_SV_HISTO)
pixh = pixAddMirroredBorder(pixs, width + 1, width + 1, height + 1,
height + 1);
else /* type == L_HS_HISTO or type == L_HV_HISTO */
pixh = pixAddMixedBorder(pixs, width + 1, width + 1, height + 1,
height + 1);
/* Get the total count in the sliding window. If the window
* fully covers the peak, this will be the integrated
* volume under the peak. */
pixw = pixWindowedMean(pixh, width, height, 0);
pixDestroy(&pixh);
/* Sequentially identify and erase peaks in the histogram.
* If requested for debugging, save a pixa of the sequence of
* false color histograms. */
if (ppixa)
*ppixa = pixaCreate(0);
for (i = 0; i < npeaks; i++) {
pixGetMaxValueInRect(pixw, NULL, &maxval, &xmax, &ymax);
if (maxval == 0) break;
numaAddNumber(natot, maxval);
ptaAddPt(pta, xmax, ymax);
ewidth = (l_int32)(width * erasefactor);
eheight = (l_int32)(height * erasefactor);
box = boxCreate(xmax - ewidth, ymax - eheight, 2 * ewidth + 1,
2 * eheight + 1);
if (ppixa) {
pixt1 = pixMaxDynamicRange(pixw, L_LINEAR_SCALE);
pixt2 = pixConvertGrayToFalseColor(pixt1, 1.0);
pixaAddPix(*ppixa, pixt2, L_INSERT);
pixDestroy(&pixt1);
}
pixClearInRect(pixw, box);
boxDestroy(&box);
if (L_HS_HISTO || L_HV_HISTO) { /* clear wraps at bottom and top */
if (ymax - eheight < 0) { /* overlap to bottom */
box = boxCreate(xmax - ewidth, 240 + ymax - eheight,
2 * ewidth + 1, eheight - ymax);
}
else if (ymax + eheight > 239) { /* overlap to top */
box = boxCreate(xmax - ewidth, 0, 2 * ewidth + 1,
ymax + eheight - 239);
}
else
box = NULL;
if (box) {
pixClearInRect(pixw, box);
boxDestroy(&box);
}
}
}
pixDestroy(&pixw);
return 0;
}
/*!
* displayHSVColorRange()
*
* Input: hval (hue center value; in range [0 ... 240]
* sval (saturation center value; in range [0 ... 255]
* vval (max intensity value; in range [0 ... 255]
* huehw (half-width of hue range; > 0)
* sathw (half-width of saturation range; > 0)
* nsamp (number of samplings in each half-width in hue and sat)
* factor (linear size of each color square, in pixels; > 3)
* Return: pixd (32 bpp set of color squares over input range),
* or null on error
*
* Notes:
* (1) The total number of color samplings in each of the hue
* and saturation directions is 2 * nsamp + 1.
*/
PIX *
displayHSVColorRange(l_int32 hval,
l_int32 sval,
l_int32 vval,
l_int32 huehw,
l_int32 sathw,
l_int32 nsamp,
l_int32 factor)
{
l_int32 i, j, w, huedelta, satdelta, hue, sat, rval, gval, bval;
PIX *pixt, *pixd;
PROCNAME("displayHSVColorRange");
if (hval < 0 || hval > 240)
return (PIX *)ERROR_PTR("invalid hval", procName, NULL);
if (huehw < 5 || huehw > 120)
return (PIX *)ERROR_PTR("invalid huehw", procName, NULL);
if (sval - sathw < 0 || sval + sathw > 255)
return (PIX *)ERROR_PTR("invalid sval/sathw", procName, NULL);
if (nsamp < 1 || factor < 3)
return (PIX *)ERROR_PTR("invalid nsamp or rep. factor", procName, NULL);
if (vval < 0 || vval > 255)
return (PIX *)ERROR_PTR("invalid vval", procName, NULL);
w = (2 * nsamp + 1);
huedelta = (l_int32)((l_float32)huehw / (l_float32)nsamp);
satdelta = (l_int32)((l_float32)sathw / (l_float32)nsamp);
pixt = pixCreate(w, w, 32);
for (i = 0; i < w; i++) {
hue = hval + huedelta * (i - nsamp);
if (hue < 0) hue += 240;
if (hue >= 240) hue -= 240;
for (j = 0; j < w; j++) {
sat = sval + satdelta * (j - nsamp);
convertHSVToRGB(hue, sat, vval, &rval, &gval, &bval);
pixSetRGBPixel(pixt, j, i, rval, gval, bval);
}
}
pixd = pixExpandReplicate(pixt, factor);
pixDestroy(&pixt);
return pixd;
}