twain3.0/3rdparty/hgOCR/leptonica/convolve.c

2577 lines
91 KiB
C

/*====================================================================*
- Copyright (C) 2001 Leptonica. All rights reserved.
-
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions
- are met:
- 1. Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
- 2. Redistributions in binary form must reproduce the above
- copyright notice, this list of conditions and the following
- disclaimer in the documentation and/or other materials
- provided with the distribution.
-
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
- CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
- OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
- NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*====================================================================*/
/*!
* \file convolve.c
* <pre>
*
* Top level grayscale or color block convolution
* PIX *pixBlockconv()
*
* Grayscale block convolution
* PIX *pixBlockconvGray()
* static void blockconvLow()
*
* Accumulator for 1, 8 and 32 bpp convolution
* PIX *pixBlockconvAccum()
* static void blockconvAccumLow()
*
* Un-normalized grayscale block convolution
* PIX *pixBlockconvGrayUnnormalized()
*
* Tiled grayscale or color block convolution
* PIX *pixBlockconvTiled()
* PIX *pixBlockconvGrayTile()
*
* Convolution for mean, mean square, variance and rms deviation
* in specified window
* l_int32 pixWindowedStats()
* PIX *pixWindowedMean()
* PIX *pixWindowedMeanSquare()
* l_int32 pixWindowedVariance()
* DPIX *pixMeanSquareAccum()
*
* Binary block sum and rank filter
* PIX *pixBlockrank()
* PIX *pixBlocksum()
* static void blocksumLow()
*
* Census transform
* PIX *pixCensusTransform()
*
* Generic convolution (with Pix)
* PIX *pixConvolve()
* PIX *pixConvolveSep()
* PIX *pixConvolveRGB()
* PIX *pixConvolveRGBSep()
*
* Generic convolution (with float arrays)
* FPIX *fpixConvolve()
* FPIX *fpixConvolveSep()
*
* Convolution with bias (for non-negative output)
* PIX *pixConvolveWithBias()
*
* Set parameter for convolution subsampling
* void l_setConvolveSampling()
*
* Additive gaussian noise
* PIX *pixAddGaussNoise()
* l_float32 gaussDistribSampling()
* </pre>
*/
#include <math.h>
#include "allheaders.h"
/* These globals determine the subsampling factors for
* generic convolution of pix and fpix. Declare extern to use.
* To change the values, use l_setConvolveSampling(). */
LEPT_DLL l_int32 ConvolveSamplingFactX = 1;
LEPT_DLL l_int32 ConvolveSamplingFactY = 1;
/* Low-level static functions */
static void blockconvLow(l_uint32 *data, l_int32 w, l_int32 h, l_int32 wpl,
l_uint32 *dataa, l_int32 wpla, l_int32 wc,
l_int32 hc);
static void blockconvAccumLow(l_uint32 *datad, l_int32 w, l_int32 h,
l_int32 wpld, l_uint32 *datas, l_int32 d,
l_int32 wpls);
static void blocksumLow(l_uint32 *datad, l_int32 w, l_int32 h, l_int32 wpl,
l_uint32 *dataa, l_int32 wpla, l_int32 wc, l_int32 hc);
/*----------------------------------------------------------------------*
* Top-level grayscale or color block convolution *
*----------------------------------------------------------------------*/
/*!
* \brief pixBlockconv()
*
* \param[in] pix 8 or 32 bpp; or 2, 4 or 8 bpp with colormap
* \param[in] wc, hc half width/height of convolution kernel
* \return pixd, or NULL on error
*
* <pre>
* Notes:
* (1) The full width and height of the convolution kernel
* are (2 * wc + 1) and (2 * hc + 1)
* (2) Returns a copy if both wc and hc are 0
* (3) Require that w >= 2 * wc + 1 and h >= 2 * hc + 1,
* where (w,h) are the dimensions of pixs.
* </pre>
*/
PIX *
pixBlockconv(PIX *pix,
l_int32 wc,
l_int32 hc)
{
l_int32 w, h, d;
PIX *pixs, *pixd, *pixr, *pixrc, *pixg, *pixgc, *pixb, *pixbc;
PROCNAME("pixBlockconv");
if (!pix)
return (PIX *)ERROR_PTR("pix not defined", procName, NULL);
if (wc < 0) wc = 0;
if (hc < 0) hc = 0;
pixGetDimensions(pix, &w, &h, &d);
if (w < 2 * wc + 1 || h < 2 * hc + 1) {
wc = L_MIN(wc, (w - 1) / 2);
hc = L_MIN(hc, (h - 1) / 2);
L_WARNING("kernel too large; reducing!\n", procName);
L_INFO("wc = %d, hc = %d\n", procName, wc, hc);
}
if (wc == 0 && hc == 0) /* no-op */
return pixCopy(NULL, pix);
/* Remove colormap if necessary */
if ((d == 2 || d == 4 || d == 8) && pixGetColormap(pix)) {
L_WARNING("pix has colormap; removing\n", procName);
pixs = pixRemoveColormap(pix, REMOVE_CMAP_BASED_ON_SRC);
d = pixGetDepth(pixs);
} else {
pixs = pixClone(pix);
}
if (d != 8 && d != 32) {
pixDestroy(&pixs);
return (PIX *)ERROR_PTR("depth not 8 or 32 bpp", procName, NULL);
}
if (d == 8) {
pixd = pixBlockconvGray(pixs, NULL, wc, hc);
} else { /* d == 32 */
pixr = pixGetRGBComponent(pixs, COLOR_RED);
pixrc = pixBlockconvGray(pixr, NULL, wc, hc);
pixDestroy(&pixr);
pixg = pixGetRGBComponent(pixs, COLOR_GREEN);
pixgc = pixBlockconvGray(pixg, NULL, wc, hc);
pixDestroy(&pixg);
pixb = pixGetRGBComponent(pixs, COLOR_BLUE);
pixbc = pixBlockconvGray(pixb, NULL, wc, hc);
pixDestroy(&pixb);
pixd = pixCreateRGBImage(pixrc, pixgc, pixbc);
pixDestroy(&pixrc);
pixDestroy(&pixgc);
pixDestroy(&pixbc);
}
pixDestroy(&pixs);
return pixd;
}
/*----------------------------------------------------------------------*
* Grayscale block convolution *
*----------------------------------------------------------------------*/
/*!
* \brief pixBlockconvGray()
*
* \param[in] pixs 8 bpp
* \param[in] pixacc pix 32 bpp; can be null
* \param[in] wc, hc half width/height of convolution kernel
* \return pix 8 bpp, or NULL on error
*
* <pre>
* Notes:
* (1) If accum pix is null, make one and destroy it before
* returning; otherwise, just use the input accum pix.
* (2) The full width and height of the convolution kernel
* are (2 * wc + 1) and (2 * hc + 1).
* (3) Returns a copy if both wc and hc are 0.
* (4) Require that w >= 2 * wc + 1 and h >= 2 * hc + 1,
* where (w,h) are the dimensions of pixs.
* </pre>
*/
PIX *
pixBlockconvGray(PIX *pixs,
PIX *pixacc,
l_int32 wc,
l_int32 hc)
{
l_int32 w, h, d, wpl, wpla;
l_uint32 *datad, *dataa;
PIX *pixd, *pixt;
PROCNAME("pixBlockconvGray");
if (!pixs)
return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
pixGetDimensions(pixs, &w, &h, &d);
if (d != 8)
return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
if (wc < 0) wc = 0;
if (hc < 0) hc = 0;
if (w < 2 * wc + 1 || h < 2 * hc + 1) {
wc = L_MIN(wc, (w - 1) / 2);
hc = L_MIN(hc, (h - 1) / 2);
L_WARNING("kernel too large; reducing!\n", procName);
L_INFO("wc = %d, hc = %d\n", procName, wc, hc);
}
if (wc == 0 && hc == 0) /* no-op */
return pixCopy(NULL, pixs);
if (pixacc) {
if (pixGetDepth(pixacc) == 32) {
pixt = pixClone(pixacc);
} else {
L_WARNING("pixacc not 32 bpp; making new one\n", procName);
if ((pixt = pixBlockconvAccum(pixs)) == NULL)
return (PIX *)ERROR_PTR("pixt not made", procName, NULL);
}
} else {
if ((pixt = pixBlockconvAccum(pixs)) == NULL)
return (PIX *)ERROR_PTR("pixt not made", procName, NULL);
}
if ((pixd = pixCreateTemplate(pixs)) == NULL) {
pixDestroy(&pixt);
return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
}
wpl = pixGetWpl(pixs);
wpla = pixGetWpl(pixt);
datad = pixGetData(pixd);
dataa = pixGetData(pixt);
blockconvLow(datad, w, h, wpl, dataa, wpla, wc, hc);
pixDestroy(&pixt);
return pixd;
}
/*!
* \brief blockconvLow()
*
* \param[in] data data of input image, to be convolved
* \param[in] w, h, wpl
* \param[in] dataa data of 32 bpp accumulator
* \param[in] wpla accumulator
* \param[in] wc convolution "half-width"
* \param[in] hc convolution "half-height"
* \return void
*
* <pre>
* Notes:
* (1) The full width and height of the convolution kernel
* are (2 * wc + 1) and (2 * hc + 1).
* (2) The lack of symmetry between the handling of the
* first (hc + 1) lines and the last (hc) lines,
* and similarly with the columns, is due to fact that
* for the pixel at (x,y), the accumulator values are
* taken at (x + wc, y + hc), (x - wc - 1, y + hc),
* (x + wc, y - hc - 1) and (x - wc - 1, y - hc - 1).
* (3) We compute sums, normalized as if there were no reduced
* area at the boundary. This under-estimates the value
* of the boundary pixels, so we multiply them by another
* normalization factor that is greater than 1.
* (4) This second normalization is done first for the first
* hc + 1 lines; then for the last hc lines; and finally
* for the first wc + 1 and last wc columns in the intermediate
* lines.
* (5) The caller should verify that wc < w and hc < h.
* Under those conditions, illegal reads and writes can occur.
* (6) Implementation note: to get the same results in the interior
* between this function and pixConvolve(), it is necessary to
* add 0.5 for roundoff in the main loop that runs over all pixels.
* However, if we do that and have white (255) pixels near the
* image boundary, some overflow occurs for pixels very close
* to the boundary. We can't fix this by subtracting from the
* normalized values for the boundary pixels, because this results
* in underflow if the boundary pixels are black (0). Empirically,
* adding 0.25 (instead of 0.5) before truncating in the main
* loop will not cause overflow, but this gives some
* off-by-1-level errors in interior pixel values. So we add
* 0.5 for roundoff in the main loop, and for pixels within a
* half filter width of the boundary, use a L_MIN of the
* computed value and 255 to avoid overflow during normalization.
* </pre>
*/
static void
blockconvLow(l_uint32 *data,
l_int32 w,
l_int32 h,
l_int32 wpl,
l_uint32 *dataa,
l_int32 wpla,
l_int32 wc,
l_int32 hc)
{
l_int32 i, j, imax, imin, jmax, jmin;
l_int32 wn, hn, fwc, fhc, wmwc, hmhc;
l_float32 norm, normh, normw;
l_uint32 val;
l_uint32 *linemina, *linemaxa, *line;
PROCNAME("blockconvLow");
wmwc = w - wc;
hmhc = h - hc;
if (wmwc <= 0 || hmhc <= 0) {
L_ERROR("wc >= w || hc >=h\n", procName);
return;
}
fwc = 2 * wc + 1;
fhc = 2 * hc + 1;
norm = 1.0 / ((l_float32)(fwc) * fhc);
/*------------------------------------------------------------*
* Compute, using b.c. only to set limits on the accum image *
*------------------------------------------------------------*/
for (i = 0; i < h; i++) {
imin = L_MAX(i - 1 - hc, 0);
imax = L_MIN(i + hc, h - 1);
line = data + wpl * i;
linemina = dataa + wpla * imin;
linemaxa = dataa + wpla * imax;
for (j = 0; j < w; j++) {
jmin = L_MAX(j - 1 - wc, 0);
jmax = L_MIN(j + wc, w - 1);
val = linemaxa[jmax] - linemaxa[jmin]
+ linemina[jmin] - linemina[jmax];
val = (l_uint8)(norm * val + 0.5); /* see comment above */
SET_DATA_BYTE(line, j, val);
}
}
/*------------------------------------------------------------*
* Fix normalization for boundary pixels *
*------------------------------------------------------------*/
for (i = 0; i <= hc; i++) { /* first hc + 1 lines */
hn = hc + i;
normh = (l_float32)fhc / (l_float32)hn; /* > 1 */
line = data + wpl * i;
for (j = 0; j <= wc; j++) {
wn = wc + j;
normw = (l_float32)fwc / (l_float32)wn; /* > 1 */
val = GET_DATA_BYTE(line, j);
val = (l_uint8)L_MIN(val * normh * normw, 255);
SET_DATA_BYTE(line, j, val);
}
for (j = wc + 1; j < wmwc; j++) {
val = GET_DATA_BYTE(line, j);
val = (l_uint8)L_MIN(val * normh, 255);
SET_DATA_BYTE(line, j, val);
}
for (j = wmwc; j < w; j++) {
wn = wc + w - j;
normw = (l_float32)fwc / (l_float32)wn; /* > 1 */
val = GET_DATA_BYTE(line, j);
val = (l_uint8)L_MIN(val * normh * normw, 255);
SET_DATA_BYTE(line, j, val);
}
}
for (i = hmhc; i < h; i++) { /* last hc lines */
hn = hc + h - i;
normh = (l_float32)fhc / (l_float32)hn; /* > 1 */
line = data + wpl * i;
for (j = 0; j <= wc; j++) {
wn = wc + j;
normw = (l_float32)fwc / (l_float32)wn; /* > 1 */
val = GET_DATA_BYTE(line, j);
val = (l_uint8)L_MIN(val * normh * normw, 255);
SET_DATA_BYTE(line, j, val);
}
for (j = wc + 1; j < wmwc; j++) {
val = GET_DATA_BYTE(line, j);
val = (l_uint8)L_MIN(val * normh, 255);
SET_DATA_BYTE(line, j, val);
}
for (j = wmwc; j < w; j++) {
wn = wc + w - j;
normw = (l_float32)fwc / (l_float32)wn; /* > 1 */
val = GET_DATA_BYTE(line, j);
val = (l_uint8)L_MIN(val * normh * normw, 255);
SET_DATA_BYTE(line, j, val);
}
}
for (i = hc + 1; i < hmhc; i++) { /* intermediate lines */
line = data + wpl * i;
for (j = 0; j <= wc; j++) { /* first wc + 1 columns */
wn = wc + j;
normw = (l_float32)fwc / (l_float32)wn; /* > 1 */
val = GET_DATA_BYTE(line, j);
val = (l_uint8)L_MIN(val * normw, 255);
SET_DATA_BYTE(line, j, val);
}
for (j = wmwc; j < w; j++) { /* last wc columns */
wn = wc + w - j;
normw = (l_float32)fwc / (l_float32)wn; /* > 1 */
val = GET_DATA_BYTE(line, j);
val = (l_uint8)L_MIN(val * normw, 255);
SET_DATA_BYTE(line, j, val);
}
}
return;
}
/*----------------------------------------------------------------------*
* Accumulator for 1, 8 and 32 bpp convolution *
*----------------------------------------------------------------------*/
/*!
* \brief pixBlockconvAccum()
*
* \param[in] pixs 1, 8 or 32 bpp
* \return accum pix 32 bpp, or NULL on error.
*
* <pre>
* Notes:
* (1) The general recursion relation is
* a(i,j) = v(i,j) + a(i-1, j) + a(i, j-1) - a(i-1, j-1)
* For the first line, this reduces to the special case
* a(i,j) = v(i,j) + a(i, j-1)
* For the first column, the special case is
* a(i,j) = v(i,j) + a(i-1, j)
* </pre>
*/
PIX *
pixBlockconvAccum(PIX *pixs)
{
l_int32 w, h, d, wpls, wpld;
l_uint32 *datas, *datad;
PIX *pixd;
PROCNAME("pixBlockconvAccum");
if (!pixs)
return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
pixGetDimensions(pixs, &w, &h, &d);
if (d != 1 && d != 8 && d != 32)
return (PIX *)ERROR_PTR("pixs not 1, 8 or 32 bpp", procName, NULL);
if ((pixd = pixCreate(w, h, 32)) == NULL)
return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
datas = pixGetData(pixs);
datad = pixGetData(pixd);
wpls = pixGetWpl(pixs);
wpld = pixGetWpl(pixd);
blockconvAccumLow(datad, w, h, wpld, datas, d, wpls);
return pixd;
}
/*
* \brief blockconvAccumLow()
*
* \param[in] datad 32 bpp dest
* \param[in] w, h, wpld of 32 bpp dest
* \param[in] datas 1, 8 or 32 bpp src
* \param[in] d bpp of src
* \param[in] wpls of src
* \return void
*
* <pre>
* Notes:
* (1) The general recursion relation is
* a(i,j) = v(i,j) + a(i-1, j) + a(i, j-1) - a(i-1, j-1)
* For the first line, this reduces to the special case
* a(0,j) = v(0,j) + a(0, j-1), j > 0
* For the first column, the special case is
* a(i,0) = v(i,0) + a(i-1, 0), i > 0
* </pre>
*/
static void
blockconvAccumLow(l_uint32 *datad,
l_int32 w,
l_int32 h,
l_int32 wpld,
l_uint32 *datas,
l_int32 d,
l_int32 wpls)
{
l_uint8 val;
l_int32 i, j;
l_uint32 val32;
l_uint32 *lines, *lined, *linedp;
PROCNAME("blockconvAccumLow");
lines = datas;
lined = datad;
if (d == 1) {
/* Do the first line */
for (j = 0; j < w; j++) {
val = GET_DATA_BIT(lines, j);
if (j == 0)
lined[0] = val;
else
lined[j] = lined[j - 1] + val;
}
/* Do the other lines */
for (i = 1; i < h; i++) {
lines = datas + i * wpls;
lined = datad + i * wpld; /* curr dest line */
linedp = lined - wpld; /* prev dest line */
for (j = 0; j < w; j++) {
val = GET_DATA_BIT(lines, j);
if (j == 0)
lined[0] = val + linedp[0];
else
lined[j] = val + lined[j - 1] + linedp[j] - linedp[j - 1];
}
}
} else if (d == 8) {
/* Do the first line */
for (j = 0; j < w; j++) {
val = GET_DATA_BYTE(lines, j);
if (j == 0)
lined[0] = val;
else
lined[j] = lined[j - 1] + val;
}
/* Do the other lines */
for (i = 1; i < h; i++) {
lines = datas + i * wpls;
lined = datad + i * wpld; /* curr dest line */
linedp = lined - wpld; /* prev dest line */
for (j = 0; j < w; j++) {
val = GET_DATA_BYTE(lines, j);
if (j == 0)
lined[0] = val + linedp[0];
else
lined[j] = val + lined[j - 1] + linedp[j] - linedp[j - 1];
}
}
} else if (d == 32) {
/* Do the first line */
for (j = 0; j < w; j++) {
val32 = lines[j];
if (j == 0)
lined[0] = val32;
else
lined[j] = lined[j - 1] + val32;
}
/* Do the other lines */
for (i = 1; i < h; i++) {
lines = datas + i * wpls;
lined = datad + i * wpld; /* curr dest line */
linedp = lined - wpld; /* prev dest line */
for (j = 0; j < w; j++) {
val32 = lines[j];
if (j == 0)
lined[0] = val32 + linedp[0];
else
lined[j] = val32 + lined[j - 1] + linedp[j] - linedp[j - 1];
}
}
} else {
L_ERROR("depth not 1, 8 or 32 bpp\n", procName);
}
return;
}
/*----------------------------------------------------------------------*
* Un-normalized grayscale block convolution *
*----------------------------------------------------------------------*/
/*!
* \brief pixBlockconvGrayUnnormalized()
*
* \param[in] pixs 8 bpp
* \param[in] wc, hc half width/height of convolution kernel
* \return pix 32 bpp; containing the convolution without normalizing
* for the window size, or NULL on error
*
* <pre>
* Notes:
* (1) The full width and height of the convolution kernel
* are (2 * wc + 1) and (2 * hc + 1).
* (2) Require that w >= 2 * wc + 1 and h >= 2 * hc + 1,
* where (w,h) are the dimensions of pixs.
* (3) Returns a copy if both wc and hc are 0.
* (3) Adds mirrored border to avoid treating the boundary pixels
* specially. Note that we add wc + 1 pixels to the left
* and wc to the right. The added width is 2 * wc + 1 pixels,
* and the particular choice simplifies the indexing in the loop.
* Likewise, add hc + 1 pixels to the top and hc to the bottom.
* (4) To get the normalized result, divide by the area of the
* convolution kernel: (2 * wc + 1) * (2 * hc + 1)
* Specifically, do this:
* pixc = pixBlockconvGrayUnnormalized(pixs, wc, hc);
* fract = 1. / ((2 * wc + 1) * (2 * hc + 1));
* pixMultConstantGray(pixc, fract);
* pixd = pixGetRGBComponent(pixc, L_ALPHA_CHANNEL);
* (5) Unlike pixBlockconvGray(), this always computes the accumulation
* pix because its size is tied to wc and hc.
* (6) Compare this implementation with pixBlockconvGray(), where
* most of the code in blockconvLow() is special casing for
* efficiently handling the boundary. Here, the use of
* mirrored borders and destination indexing makes the
* implementation very simple.
* </pre>
*/
PIX *
pixBlockconvGrayUnnormalized(PIX *pixs,
l_int32 wc,
l_int32 hc)
{
l_int32 i, j, w, h, d, wpla, wpld, jmax;
l_uint32 *linemina, *linemaxa, *lined, *dataa, *datad;
PIX *pixsb, *pixacc, *pixd;
PROCNAME("pixBlockconvGrayUnnormalized");
if (!pixs)
return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
pixGetDimensions(pixs, &w, &h, &d);
if (d != 8)
return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
if (wc < 0) wc = 0;
if (hc < 0) hc = 0;
if (w < 2 * wc + 1 || h < 2 * hc + 1) {
wc = L_MIN(wc, (w - 1) / 2);
hc = L_MIN(hc, (h - 1) / 2);
L_WARNING("kernel too large; reducing!\n", procName);
L_INFO("wc = %d, hc = %d\n", procName, wc, hc);
}
if (wc == 0 && hc == 0) /* no-op */
return pixCopy(NULL, pixs);
if ((pixsb = pixAddMirroredBorder(pixs, wc + 1, wc, hc + 1, hc)) == NULL)
return (PIX *)ERROR_PTR("pixsb not made", procName, NULL);
pixacc = pixBlockconvAccum(pixsb);
pixDestroy(&pixsb);
if (!pixacc)
return (PIX *)ERROR_PTR("pixacc not made", procName, NULL);
if ((pixd = pixCreate(w, h, 32)) == NULL) {
pixDestroy(&pixacc);
return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
}
wpla = pixGetWpl(pixacc);
wpld = pixGetWpl(pixd);
datad = pixGetData(pixd);
dataa = pixGetData(pixacc);
for (i = 0; i < h; i++) {
lined = datad + i * wpld;
linemina = dataa + i * wpla;
linemaxa = dataa + (i + 2 * hc + 1) * wpla;
for (j = 0; j < w; j++) {
jmax = j + 2 * wc + 1;
lined[j] = linemaxa[jmax] - linemaxa[j] -
linemina[jmax] + linemina[j];
}
}
pixDestroy(&pixacc);
return pixd;
}
/*----------------------------------------------------------------------*
* Tiled grayscale or color block convolution *
*----------------------------------------------------------------------*/
/*!
* \brief pixBlockconvTiled()
*
* \param[in] pix 8 or 32 bpp; or 2, 4 or 8 bpp with colormap
* \param[in] wc, hc half width/height of convolution kernel
* \param[in] nx, ny subdivision into tiles
* \return pixd, or NULL on error
*
* <pre>
* Notes:
* (1) The full width and height of the convolution kernel
* are (2 * wc + 1) and (2 * hc + 1)
* (2) Returns a copy if both wc and hc are 0
* (3) Require that w >= 2 * wc + 1 and h >= 2 * hc + 1,
* where (w,h) are the dimensions of pixs.
* (4) For nx == ny == 1, this defaults to pixBlockconv(), which
* is typically about twice as fast, and gives nearly
* identical results as pixBlockconvGrayTile().
* (5) If the tiles are too small, nx and/or ny are reduced
* a minimum amount so that the tiles are expanded to the
* smallest workable size in the problematic direction(s).
* (6) Why a tiled version? Three reasons:
* (a) Because the accumulator is a uint32, overflow can occur
* for an image with more than 16M pixels.
* (b) The accumulator array for 16M pixels is 64 MB; using
* tiles reduces the size of this array.
* (c) Each tile can be processed independently, in parallel,
* on a multicore processor.
* </pre>
*/
PIX *
pixBlockconvTiled(PIX *pix,
l_int32 wc,
l_int32 hc,
l_int32 nx,
l_int32 ny)
{
l_int32 i, j, w, h, d, xrat, yrat;
PIX *pixs, *pixd, *pixc, *pixt;
PIX *pixr, *pixrc, *pixg, *pixgc, *pixb, *pixbc;
PIXTILING *pt;
PROCNAME("pixBlockconvTiled");
if (!pix)
return (PIX *)ERROR_PTR("pix not defined", procName, NULL);
if (wc < 0) wc = 0;
if (hc < 0) hc = 0;
pixGetDimensions(pix, &w, &h, &d);
if (w < 2 * wc + 3 || h < 2 * hc + 3) {
wc = L_MAX(0, L_MIN(wc, (w - 3) / 2));
hc = L_MAX(0, L_MIN(hc, (h - 3) / 2));
L_WARNING("kernel too large; reducing!\n", procName);
L_INFO("wc = %d, hc = %d\n", procName, wc, hc);
}
if (wc == 0 && hc == 0) /* no-op */
return pixCopy(NULL, pix);
if (nx <= 1 && ny <= 1)
return pixBlockconv(pix, wc, hc);
/* Test to see if the tiles are too small. The required
* condition is that the tile dimensions must be at least
* (wc + 2) x (hc + 2). */
xrat = w / nx;
yrat = h / ny;
if (xrat < wc + 2) {
nx = w / (wc + 2);
L_WARNING("tile width too small; nx reduced to %d\n", procName, nx);
}
if (yrat < hc + 2) {
ny = h / (hc + 2);
L_WARNING("tile height too small; ny reduced to %d\n", procName, ny);
}
/* Remove colormap if necessary */
if ((d == 2 || d == 4 || d == 8) && pixGetColormap(pix)) {
L_WARNING("pix has colormap; removing\n", procName);
pixs = pixRemoveColormap(pix, REMOVE_CMAP_BASED_ON_SRC);
d = pixGetDepth(pixs);
} else {
pixs = pixClone(pix);
}
if (d != 8 && d != 32) {
pixDestroy(&pixs);
return (PIX *)ERROR_PTR("depth not 8 or 32 bpp", procName, NULL);
}
/* Note that the overlaps in the width and height that
* are added to the tile are (wc + 2) and (hc + 2).
* These overlaps are removed by pixTilingPaintTile().
* They are larger than the extent of the filter because
* although the filter is symmetric with respect to its origin,
* the implementation is asymmetric -- see the implementation in
* pixBlockconvGrayTile(). */
if ((pixd = pixCreateTemplate(pixs)) == NULL) {
pixDestroy(&pixs);
return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
}
pt = pixTilingCreate(pixs, nx, ny, 0, 0, wc + 2, hc + 2);
for (i = 0; i < ny; i++) {
for (j = 0; j < nx; j++) {
pixt = pixTilingGetTile(pt, i, j);
/* Convolve over the tile */
if (d == 8) {
pixc = pixBlockconvGrayTile(pixt, NULL, wc, hc);
} else { /* d == 32 */
pixr = pixGetRGBComponent(pixt, COLOR_RED);
pixrc = pixBlockconvGrayTile(pixr, NULL, wc, hc);
pixDestroy(&pixr);
pixg = pixGetRGBComponent(pixt, COLOR_GREEN);
pixgc = pixBlockconvGrayTile(pixg, NULL, wc, hc);
pixDestroy(&pixg);
pixb = pixGetRGBComponent(pixt, COLOR_BLUE);
pixbc = pixBlockconvGrayTile(pixb, NULL, wc, hc);
pixDestroy(&pixb);
pixc = pixCreateRGBImage(pixrc, pixgc, pixbc);
pixDestroy(&pixrc);
pixDestroy(&pixgc);
pixDestroy(&pixbc);
}
pixTilingPaintTile(pixd, i, j, pixc, pt);
pixDestroy(&pixt);
pixDestroy(&pixc);
}
}
pixDestroy(&pixs);
pixTilingDestroy(&pt);
return pixd;
}
/*!
* \brief pixBlockconvGrayTile()
*
* \param[in] pixs 8 bpp gray
* \param[in] pixacc 32 bpp accum pix
* \param[in] wc, hc half width/height of convolution kernel
* \return pixd, or NULL on error
*
* <pre>
* Notes:
* (1) The full width and height of the convolution kernel
* are (2 * wc + 1) and (2 * hc + 1)
* (2) Assumes that the input pixs is padded with (wc + 1) pixels on
* left and right, and with (hc + 1) pixels on top and bottom.
* The returned pix has these stripped off; they are only used
* for computation.
* (3) Returns a copy if both wc and hc are 0
* (4) Require that w > 2 * wc + 1 and h > 2 * hc + 1,
* where (w,h) are the dimensions of pixs.
* </pre>
*/
PIX *
pixBlockconvGrayTile(PIX *pixs,
PIX *pixacc,
l_int32 wc,
l_int32 hc)
{
l_int32 w, h, d, wd, hd, i, j, imin, imax, jmin, jmax, wplt, wpld;
l_float32 norm;
l_uint32 val;
l_uint32 *datat, *datad, *lined, *linemint, *linemaxt;
PIX *pixt, *pixd;
PROCNAME("pixBlockconvGrayTile");
if (!pixs)
return (PIX *)ERROR_PTR("pix not defined", procName, NULL);
pixGetDimensions(pixs, &w, &h, &d);
if (d != 8)
return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
if (wc < 0) wc = 0;
if (hc < 0) hc = 0;
if (w < 2 * wc + 3 || h < 2 * hc + 3) {
wc = L_MAX(0, L_MIN(wc, (w - 3) / 2));
hc = L_MAX(0, L_MIN(hc, (h - 3) / 2));
L_WARNING("kernel too large; reducing!\n", procName);
L_INFO("wc = %d, hc = %d\n", procName, wc, hc);
}
if (wc == 0 && hc == 0)
return pixCopy(NULL, pixs);
wd = w - 2 * wc;
hd = h - 2 * hc;
if (pixacc) {
if (pixGetDepth(pixacc) == 32) {
pixt = pixClone(pixacc);
} else {
L_WARNING("pixacc not 32 bpp; making new one\n", procName);
if ((pixt = pixBlockconvAccum(pixs)) == NULL)
return (PIX *)ERROR_PTR("pixt not made", procName, NULL);
}
} else {
if ((pixt = pixBlockconvAccum(pixs)) == NULL)
return (PIX *)ERROR_PTR("pixt not made", procName, NULL);
}
if ((pixd = pixCreateTemplate(pixs)) == NULL) {
pixDestroy(&pixt);
return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
}
datat = pixGetData(pixt);
wplt = pixGetWpl(pixt);
datad = pixGetData(pixd);
wpld = pixGetWpl(pixd);
norm = 1. / (l_float32)((2 * wc + 1) * (2 * hc + 1));
/* Do the convolution over the subregion of size (wd - 2, hd - 2),
* which exactly corresponds to the size of the subregion that
* will be extracted by pixTilingPaintTile(). Note that the
* region in which points are computed is not symmetric about
* the center of the images; instead the computation in
* the accumulator image is shifted up and to the left by 1,
* relative to the center, because the 4 accumulator sampling
* points are taken at the LL corner of the filter and at 3 other
* points that are shifted -wc and -hc to the left and above. */
for (i = hc; i < hc + hd - 2; i++) {
imin = L_MAX(i - hc - 1, 0);
imax = L_MIN(i + hc, h - 1);
lined = datad + i * wpld;
linemint = datat + imin * wplt;
linemaxt = datat + imax * wplt;
for (j = wc; j < wc + wd - 2; j++) {
jmin = L_MAX(j - wc - 1, 0);
jmax = L_MIN(j + wc, w - 1);
val = linemaxt[jmax] - linemaxt[jmin]
+ linemint[jmin] - linemint[jmax];
val = (l_uint8)(norm * val + 0.5);
SET_DATA_BYTE(lined, j, val);
}
}
pixDestroy(&pixt);
return pixd;
}
/*----------------------------------------------------------------------*
* Convolution for mean, mean square, variance and rms deviation *
*----------------------------------------------------------------------*/
/*!
* \brief pixWindowedStats()
*
* \param[in] pixs 8 bpp grayscale
* \param[in] wc, hc half width/height of convolution kernel
* \param[in] hasborder use 1 if it already has (wc + 1 border pixels
* on left and right, and hc + 1 on top and bottom;
* use 0 to add kernel-dependent border)
* \param[out] ppixm [optional] 8 bpp mean value in window
* \param[out] ppixms [optional] 32 bpp mean square value in window
* \param[out] pfpixv [optional] float variance in window
* \param[out] pfpixrv [optional] float rms deviation from the mean
* \return 0 if OK, 1 on error
*
* <pre>
* Notes:
* (1) This is a high-level convenience function for calculating
* any or all of these derived images.
* (2) If %hasborder = 0, a border is added and the result is
* computed over all pixels in pixs. Otherwise, no border is
* added and the border pixels are removed from the output images.
* (3) These statistical measures over the pixels in the
* rectangular window are:
* ~ average value: <p> (pixm)
* ~ average squared value: <p*p> (pixms)
* ~ variance: <(p - <p>)*(p - <p>)> = <p*p> - <p>*<p> (pixv)
* ~ square-root of variance: (pixrv)
* where the brackets < .. > indicate that the average value is
* to be taken over the window.
* (4) Note that the variance is just the mean square difference from
* the mean value; and the square root of the variance is the
* root mean square difference from the mean, sometimes also
* called the 'standard deviation'.
* (5) The added border, along with the use of an accumulator array,
* allows computation without special treatment of pixels near
* the image boundary, and runs in a time that is independent
* of the size of the convolution kernel.
* </pre>
*/
l_ok
pixWindowedStats(PIX *pixs,
l_int32 wc,
l_int32 hc,
l_int32 hasborder,
PIX **ppixm,
PIX **ppixms,
FPIX **pfpixv,
FPIX **pfpixrv)
{
PIX *pixb, *pixm, *pixms;
PROCNAME("pixWindowedStats");
if (!ppixm && !ppixms && !pfpixv && !pfpixrv)
return ERROR_INT("no output requested", procName, 1);
if (ppixm) *ppixm = NULL;
if (ppixms) *ppixms = NULL;
if (pfpixv) *pfpixv = NULL;
if (pfpixrv) *pfpixrv = NULL;
if (!pixs || pixGetDepth(pixs) != 8)
return ERROR_INT("pixs not defined or not 8 bpp", procName, 1);
if (wc < 2 || hc < 2)
return ERROR_INT("wc and hc not >= 2", procName, 1);
/* Add border if requested */
if (!hasborder)
pixb = pixAddBorderGeneral(pixs, wc + 1, wc + 1, hc + 1, hc + 1, 0);
else
pixb = pixClone(pixs);
if (!pfpixv && !pfpixrv) {
if (ppixm) *ppixm = pixWindowedMean(pixb, wc, hc, 1, 1);
if (ppixms) *ppixms = pixWindowedMeanSquare(pixb, wc, hc, 1);
pixDestroy(&pixb);
return 0;
}
pixm = pixWindowedMean(pixb, wc, hc, 1, 1);
pixms = pixWindowedMeanSquare(pixb, wc, hc, 1);
pixWindowedVariance(pixm, pixms, pfpixv, pfpixrv);
if (ppixm)
*ppixm = pixm;
else
pixDestroy(&pixm);
if (ppixms)
*ppixms = pixms;
else
pixDestroy(&pixms);
pixDestroy(&pixb);
return 0;
}
/*!
* \brief pixWindowedMean()
*
* \param[in] pixs 8 or 32 bpp grayscale
* \param[in] wc, hc half width/height of convolution kernel
* \param[in] hasborder use 1 if it already has (wc + 1 border pixels
* on left and right, and hc + 1 on top and bottom;
* use 0 to add kernel-dependent border)
* \param[in] normflag 1 for normalization to get average in window;
* 0 for the sum in the window (un-normalized)
* \return pixd 8 or 32 bpp, average over kernel window
*
* <pre>
* Notes:
* (1) The input and output depths are the same.
* (2) A set of border pixels of width (wc + 1) on left and right,
* and of height (hc + 1) on top and bottom, must be on the
* pix before the accumulator is found. The output pixd
* (after convolution) has this border removed.
* If %hasborder = 0, the required border is added.
* (3) Typically, %normflag == 1. However, if you want the sum
* within the window, rather than a normalized convolution,
* use %normflag == 0.
* (4) This builds a block accumulator pix, uses it here, and
* destroys it.
* (5) The added border, along with the use of an accumulator array,
* allows computation without special treatment of pixels near
* the image boundary, and runs in a time that is independent
* of the size of the convolution kernel.
* </pre>
*/
PIX *
pixWindowedMean(PIX *pixs,
l_int32 wc,
l_int32 hc,
l_int32 hasborder,
l_int32 normflag)
{
l_int32 i, j, w, h, d, wd, hd, wplc, wpld, wincr, hincr;
l_uint32 val;
l_uint32 *datac, *datad, *linec1, *linec2, *lined;
l_float32 norm;
PIX *pixb, *pixc, *pixd;
PROCNAME("pixWindowedMean");
if (!pixs)
return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
d = pixGetDepth(pixs);
if (d != 8 && d != 32)
return (PIX *)ERROR_PTR("pixs not 8 or 32 bpp", procName, NULL);
if (wc < 2 || hc < 2)
return (PIX *)ERROR_PTR("wc and hc not >= 2", procName, NULL);
pixb = pixc = pixd = NULL;
/* Add border if requested */
if (!hasborder)
pixb = pixAddBorderGeneral(pixs, wc + 1, wc + 1, hc + 1, hc + 1, 0);
else
pixb = pixClone(pixs);
/* Make the accumulator pix from pixb */
if ((pixc = pixBlockconvAccum(pixb)) == NULL) {
L_ERROR("pixc not made\n", procName);
goto cleanup;
}
wplc = pixGetWpl(pixc);
datac = pixGetData(pixc);
/* The output has wc + 1 border pixels stripped from each side
* of pixb, and hc + 1 border pixels stripped from top and bottom. */
pixGetDimensions(pixb, &w, &h, NULL);
wd = w - 2 * (wc + 1);
hd = h - 2 * (hc + 1);
if (wd < 2 || hd < 2) {
L_ERROR("w or h is too small for the kernel\n", procName);
goto cleanup;
}
if ((pixd = pixCreate(wd, hd, d)) == NULL) {
L_ERROR("pixd not made\n", procName);
goto cleanup;
}
wpld = pixGetWpl(pixd);
datad = pixGetData(pixd);
wincr = 2 * wc + 1;
hincr = 2 * hc + 1;
norm = 1.0; /* use this for sum-in-window */
if (normflag)
norm = 1.0 / ((l_float32)(wincr) * hincr);
for (i = 0; i < hd; i++) {
linec1 = datac + i * wplc;
linec2 = datac + (i + hincr) * wplc;
lined = datad + i * wpld;
for (j = 0; j < wd; j++) {
val = linec2[j + wincr] - linec2[j] - linec1[j + wincr] + linec1[j];
if (d == 8) {
val = (l_uint8)(norm * val);
SET_DATA_BYTE(lined, j, val);
} else { /* d == 32 */
val = (l_uint32)(norm * val);
lined[j] = val;
}
}
}
cleanup:
pixDestroy(&pixb);
pixDestroy(&pixc);
return pixd;
}
/*!
* \brief pixWindowedMeanSquare()
*
* \param[in] pixs 8 bpp grayscale
* \param[in] wc, hc half width/height of convolution kernel
* \param[in] hasborder use 1 if it already has (wc + 1 border pixels
* on left and right, and hc + 1 on top and bottom;
* use 0 to add kernel-dependent border)
* \return pixd 32 bpp, average over rectangular window of
* width = 2 * wc + 1 and height = 2 * hc + 1
*
* <pre>
* Notes:
* (1) A set of border pixels of width (wc + 1) on left and right,
* and of height (hc + 1) on top and bottom, must be on the
* pix before the accumulator is found. The output pixd
* (after convolution) has this border removed.
* If %hasborder = 0, the required border is added.
* (2) The advantage is that we are unaffected by the boundary, and
* it is not necessary to treat pixels within %wc and %hc of the
* border differently. This is because processing for pixd
* only takes place for pixels in pixs for which the
* kernel is entirely contained in pixs.
* (3) Why do we have an added border of width (%wc + 1) and
* height (%hc + 1), when we only need %wc and %hc pixels
* to satisfy this condition? Answer: the accumulators
* are asymmetric, requiring an extra row and column of
* pixels at top and left to work accurately.
* (4) The added border, along with the use of an accumulator array,
* allows computation without special treatment of pixels near
* the image boundary, and runs in a time that is independent
* of the size of the convolution kernel.
* </pre>
*/
PIX *
pixWindowedMeanSquare(PIX *pixs,
l_int32 wc,
l_int32 hc,
l_int32 hasborder)
{
l_int32 i, j, w, h, wd, hd, wpl, wpld, wincr, hincr;
l_uint32 ival;
l_uint32 *datad, *lined;
l_float64 norm;
l_float64 val;
l_float64 *data, *line1, *line2;
DPIX *dpix;
PIX *pixb, *pixd;
PROCNAME("pixWindowedMeanSquare");
if (!pixs || (pixGetDepth(pixs) != 8))
return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", procName, NULL);
if (wc < 2 || hc < 2)
return (PIX *)ERROR_PTR("wc and hc not >= 2", procName, NULL);
pixd = NULL;
/* Add border if requested */
if (!hasborder)
pixb = pixAddBorderGeneral(pixs, wc + 1, wc + 1, hc + 1, hc + 1, 0);
else
pixb = pixClone(pixs);
if ((dpix = pixMeanSquareAccum(pixb)) == NULL) {
L_ERROR("dpix not made\n", procName);
goto cleanup;
}
wpl = dpixGetWpl(dpix);
data = dpixGetData(dpix);
/* The output has wc + 1 border pixels stripped from each side
* of pixb, and hc + 1 border pixels stripped from top and bottom. */
pixGetDimensions(pixb, &w, &h, NULL);
wd = w - 2 * (wc + 1);
hd = h - 2 * (hc + 1);
if (wd < 2 || hd < 2) {
L_ERROR("w or h too small for kernel\n", procName);
goto cleanup;
}
if ((pixd = pixCreate(wd, hd, 32)) == NULL) {
L_ERROR("pixd not made\n", procName);
goto cleanup;
}
wpld = pixGetWpl(pixd);
datad = pixGetData(pixd);
wincr = 2 * wc + 1;
hincr = 2 * hc + 1;
norm = 1.0 / ((l_float32)(wincr) * hincr);
for (i = 0; i < hd; i++) {
line1 = data + i * wpl;
line2 = data + (i + hincr) * wpl;
lined = datad + i * wpld;
for (j = 0; j < wd; j++) {
val = line2[j + wincr] - line2[j] - line1[j + wincr] + line1[j];
ival = (l_uint32)(norm * val + 0.5); /* to round up */
lined[j] = ival;
}
}
cleanup:
dpixDestroy(&dpix);
pixDestroy(&pixb);
return pixd;
}
/*!
* \brief pixWindowedVariance()
*
* \param[in] pixm mean over window; 8 or 32 bpp grayscale
* \param[in] pixms mean square over window; 32 bpp
* \param[out] pfpixv [optional] float variance -- the ms deviation
* from the mean
* \param[out] pfpixrv [optional] float rms deviation from the mean
* \return 0 if OK, 1 on error
*
* <pre>
* Notes:
* (1) The mean and mean square values are precomputed, using
* pixWindowedMean() and pixWindowedMeanSquare().
* (2) Either or both of the variance and square-root of variance
* are returned as an fpix, where the variance is the
* average over the window of the mean square difference of
* the pixel value from the mean:
* <(p - <p>)*(p - <p>)> = <p*p> - <p>*<p>
* (3) To visualize the results:
* ~ for both, use fpixDisplayMaxDynamicRange().
* ~ for rms deviation, simply convert the output fpix to pix,
* </pre>
*/
l_ok
pixWindowedVariance(PIX *pixm,
PIX *pixms,
FPIX **pfpixv,
FPIX **pfpixrv)
{
l_int32 i, j, w, h, ws, hs, ds, wplm, wplms, wplv, wplrv, valm, valms;
l_float32 var;
l_uint32 *linem, *linems, *datam, *datams;
l_float32 *linev, *linerv, *datav, *datarv;
FPIX *fpixv, *fpixrv; /* variance and square root of variance */
PROCNAME("pixWindowedVariance");
if (!pfpixv && !pfpixrv)
return ERROR_INT("no output requested", procName, 1);
if (pfpixv) *pfpixv = NULL;
if (pfpixrv) *pfpixrv = NULL;
if (!pixm || pixGetDepth(pixm) != 8)
return ERROR_INT("pixm undefined or not 8 bpp", procName, 1);
if (!pixms || pixGetDepth(pixms) != 32)
return ERROR_INT("pixms undefined or not 32 bpp", procName, 1);
pixGetDimensions(pixm, &w, &h, NULL);
pixGetDimensions(pixms, &ws, &hs, &ds);
if (w != ws || h != hs)
return ERROR_INT("pixm and pixms sizes differ", procName, 1);
if (pfpixv) {
fpixv = fpixCreate(w, h);
*pfpixv = fpixv;
wplv = fpixGetWpl(fpixv);
datav = fpixGetData(fpixv);
}
if (pfpixrv) {
fpixrv = fpixCreate(w, h);
*pfpixrv = fpixrv;
wplrv = fpixGetWpl(fpixrv);
datarv = fpixGetData(fpixrv);
}
wplm = pixGetWpl(pixm);
wplms = pixGetWpl(pixms);
datam = pixGetData(pixm);
datams = pixGetData(pixms);
for (i = 0; i < h; i++) {
linem = datam + i * wplm;
linems = datams + i * wplms;
if (pfpixv)
linev = datav + i * wplv;
if (pfpixrv)
linerv = datarv + i * wplrv;
for (j = 0; j < w; j++) {
valm = GET_DATA_BYTE(linem, j);
if (ds == 8)
valms = GET_DATA_BYTE(linems, j);
else /* ds == 32 */
valms = (l_int32)linems[j];
var = (l_float32)valms - (l_float32)valm * valm;
if (pfpixv)
linev[j] = var;
if (pfpixrv)
linerv[j] = (l_float32)sqrt(var);
}
}
return 0;
}
/*!
* \brief pixMeanSquareAccum()
*
* \param[in] pixs 8 bpp grayscale
* \return dpix 64 bit array, or NULL on error
*
* <pre>
* Notes:
* (1) Similar to pixBlockconvAccum(), this computes the
* sum of the squares of the pixel values in such a way
* that the value at (i,j) is the sum of all squares in
* the rectangle from the origin to (i,j).
* (2) The general recursion relation (v are squared pixel values) is
* a(i,j) = v(i,j) + a(i-1, j) + a(i, j-1) - a(i-1, j-1)
* For the first line, this reduces to the special case
* a(i,j) = v(i,j) + a(i, j-1)
* For the first column, the special case is
* a(i,j) = v(i,j) + a(i-1, j)
* </pre>
*/
DPIX *
pixMeanSquareAccum(PIX *pixs)
{
l_int32 i, j, w, h, wpl, wpls, val;
l_uint32 *datas, *lines;
l_float64 *data, *line, *linep;
DPIX *dpix;
PROCNAME("pixMeanSquareAccum");
if (!pixs || (pixGetDepth(pixs) != 8))
return (DPIX *)ERROR_PTR("pixs undefined or not 8 bpp", procName, NULL);
pixGetDimensions(pixs, &w, &h, NULL);
if ((dpix = dpixCreate(w, h)) == NULL)
return (DPIX *)ERROR_PTR("dpix not made", procName, NULL);
datas = pixGetData(pixs);
wpls = pixGetWpl(pixs);
data = dpixGetData(dpix);
wpl = dpixGetWpl(dpix);
lines = datas;
line = data;
for (j = 0; j < w; j++) { /* first line */
val = GET_DATA_BYTE(lines, j);
if (j == 0)
line[0] = (l_float64)(val) * val;
else
line[j] = line[j - 1] + (l_float64)(val) * val;
}
/* Do the other lines */
for (i = 1; i < h; i++) {
lines = datas + i * wpls;
line = data + i * wpl; /* current dest line */
linep = line - wpl;; /* prev dest line */
for (j = 0; j < w; j++) {
val = GET_DATA_BYTE(lines, j);
if (j == 0)
line[0] = linep[0] + (l_float64)(val) * val;
else
line[j] = line[j - 1] + linep[j] - linep[j - 1]
+ (l_float64)(val) * val;
}
}
return dpix;
}
/*----------------------------------------------------------------------*
* Binary block sum/rank *
*----------------------------------------------------------------------*/
/*!
* \brief pixBlockrank()
*
* \param[in] pixs 1 bpp
* \param[in] pixacc pix [optional] 32 bpp
* \param[in] wc, hc half width/height of block sum/rank kernel
* \param[in] rank between 0.0 and 1.0; 0.5 is median filter
* \return pixd 1 bpp
*
* <pre>
* Notes:
* (1) The full width and height of the convolution kernel
* are (2 * wc + 1) and (2 * hc + 1)
* (2) This returns a pixd where each pixel is a 1 if the
* neighborhood (2 * wc + 1) x (2 * hc + 1)) pixels
* contains the rank fraction of 1 pixels. Otherwise,
* the returned pixel is 0. Note that the special case
* of rank = 0.0 is always satisfied, so the returned
* pixd has all pixels with value 1.
* (3) If accum pix is null, make one, use it, and destroy it
* before returning; otherwise, just use the input accum pix
* (4) If both wc and hc are 0, returns a copy unless rank == 0.0,
* in which case this returns an all-ones image.
* (5) Require that w >= 2 * wc + 1 and h >= 2 * hc + 1,
* where (w,h) are the dimensions of pixs.
* </pre>
*/
PIX *
pixBlockrank(PIX *pixs,
PIX *pixacc,
l_int32 wc,
l_int32 hc,
l_float32 rank)
{
l_int32 w, h, d, thresh;
PIX *pixt, *pixd;
PROCNAME("pixBlockrank");
if (!pixs)
return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
pixGetDimensions(pixs, &w, &h, &d);
if (d != 1)
return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, NULL);
if (rank < 0.0 || rank > 1.0)
return (PIX *)ERROR_PTR("rank must be in [0.0, 1.0]", procName, NULL);
if (rank == 0.0) {
pixd = pixCreateTemplate(pixs);
pixSetAll(pixd);
return pixd;
}
if (wc < 0) wc = 0;
if (hc < 0) hc = 0;
if (w < 2 * wc + 1 || h < 2 * hc + 1) {
wc = L_MIN(wc, (w - 1) / 2);
hc = L_MIN(hc, (h - 1) / 2);
L_WARNING("kernel too large; reducing!\n", procName);
L_INFO("wc = %d, hc = %d\n", procName, wc, hc);
}
if (wc == 0 && hc == 0)
return pixCopy(NULL, pixs);
if ((pixt = pixBlocksum(pixs, pixacc, wc, hc)) == NULL)
return (PIX *)ERROR_PTR("pixt not made", procName, NULL);
/* 1 bpp block rank filter output.
* Must invert because threshold gives 1 for values < thresh,
* but we need a 1 if the value is >= thresh. */
thresh = (l_int32)(255. * rank);
pixd = pixThresholdToBinary(pixt, thresh);
pixInvert(pixd, pixd);
pixDestroy(&pixt);
return pixd;
}
/*!
* \brief pixBlocksum()
*
* \param[in] pixs 1 bpp
* \param[in] pixacc pix [optional] 32 bpp
* \param[in] wc, hc half width/height of block sum/rank kernel
* \return pixd 8 bpp
*
* <pre>
* Notes:
* (1) If accum pix is null, make one and destroy it before
* returning; otherwise, just use the input accum pix
* (2) The full width and height of the convolution kernel
* are (2 * wc + 1) and (2 * hc + 1)
* (3) Use of wc = hc = 1, followed by pixInvert() on the
* 8 bpp result, gives a nice anti-aliased, and somewhat
* darkened, result on text.
* (4) Require that w >= 2 * wc + 1 and h >= 2 * hc + 1,
* where (w,h) are the dimensions of pixs.
* (5) Returns in each dest pixel the sum of all src pixels
* that are within a block of size of the kernel, centered
* on the dest pixel. This sum is the number of src ON
* pixels in the block at each location, normalized to 255
* for a block containing all ON pixels. For pixels near
* the boundary, where the block is not entirely contained
* within the image, we then multiply by a second normalization
* factor that is greater than one, so that all results
* are normalized by the number of participating pixels
* within the block.
* </pre>
*/
PIX *
pixBlocksum(PIX *pixs,
PIX *pixacc,
l_int32 wc,
l_int32 hc)
{
l_int32 w, h, d, wplt, wpld;
l_uint32 *datat, *datad;
PIX *pixt, *pixd;
PROCNAME("pixBlocksum");
if (!pixs)
return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
pixGetDimensions(pixs, &w, &h, &d);
if (d != 1)
return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, NULL);
if (wc < 0) wc = 0;
if (hc < 0) hc = 0;
if (w < 2 * wc + 1 || h < 2 * hc + 1) {
wc = L_MIN(wc, (w - 1) / 2);
hc = L_MIN(hc, (h - 1) / 2);
L_WARNING("kernel too large; reducing!\n", procName);
L_INFO("wc = %d, hc = %d\n", procName, wc, hc);
}
if (wc == 0 && hc == 0)
return pixCopy(NULL, pixs);
if (pixacc) {
if (pixGetDepth(pixacc) != 32)
return (PIX *)ERROR_PTR("pixacc not 32 bpp", procName, NULL);
pixt = pixClone(pixacc);
} else {
if ((pixt = pixBlockconvAccum(pixs)) == NULL)
return (PIX *)ERROR_PTR("pixt not made", procName, NULL);
}
/* 8 bpp block sum output */
if ((pixd = pixCreate(w, h, 8)) == NULL) {
pixDestroy(&pixt);
return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
}
pixCopyResolution(pixd, pixs);
wpld = pixGetWpl(pixd);
wplt = pixGetWpl(pixt);
datad = pixGetData(pixd);
datat = pixGetData(pixt);
blocksumLow(datad, w, h, wpld, datat, wplt, wc, hc);
pixDestroy(&pixt);
return pixd;
}
/*!
* \brief blocksumLow()
*
* \param[in] datad of 8 bpp dest
* \param[in] w, h, wpl of 8 bpp dest
* \param[in] dataa of 32 bpp accum
* \param[in] wpla of 32 bpp accum
* \param[in] wc, hc convolution "half-width" and "half-height"
* \return void
*
* <pre>
* Notes:
* (1) The full width and height of the convolution kernel
* are (2 * wc + 1) and (2 * hc + 1).
* (2) The lack of symmetry between the handling of the
* first (hc + 1) lines and the last (hc) lines,
* and similarly with the columns, is due to fact that
* for the pixel at (x,y), the accumulator values are
* taken at (x + wc, y + hc), (x - wc - 1, y + hc),
* (x + wc, y - hc - 1) and (x - wc - 1, y - hc - 1).
* (3) Compute sums of ON pixels within the block filter size,
* normalized between 0 and 255, as if there were no reduced
* area at the boundary. This under-estimates the value
* of the boundary pixels, so we multiply them by another
* normalization factor that is greater than 1.
* (4) This second normalization is done first for the first
* hc + 1 lines; then for the last hc lines; and finally
* for the first wc + 1 and last wc columns in the intermediate
* lines.
* (5) Required constraints are: wc < w and hc < h.
* </pre>
*/
static void
blocksumLow(l_uint32 *datad,
l_int32 w,
l_int32 h,
l_int32 wpl,
l_uint32 *dataa,
l_int32 wpla,
l_int32 wc,
l_int32 hc)
{
l_int32 i, j, imax, imin, jmax, jmin;
l_int32 wn, hn, fwc, fhc, wmwc, hmhc;
l_float32 norm, normh, normw;
l_uint32 val;
l_uint32 *linemina, *linemaxa, *lined;
PROCNAME("blocksumLow");
wmwc = w - wc;
hmhc = h - hc;
if (wmwc <= 0 || hmhc <= 0) {
L_ERROR("wc >= w || hc >=h\n", procName);
return;
}
fwc = 2 * wc + 1;
fhc = 2 * hc + 1;
norm = 255. / ((l_float32)(fwc) * fhc);
/*------------------------------------------------------------*
* Compute, using b.c. only to set limits on the accum image *
*------------------------------------------------------------*/
for (i = 0; i < h; i++) {
imin = L_MAX(i - 1 - hc, 0);
imax = L_MIN(i + hc, h - 1);
lined = datad + wpl * i;
linemina = dataa + wpla * imin;
linemaxa = dataa + wpla * imax;
for (j = 0; j < w; j++) {
jmin = L_MAX(j - 1 - wc, 0);
jmax = L_MIN(j + wc, w - 1);
val = linemaxa[jmax] - linemaxa[jmin]
- linemina[jmax] + linemina[jmin];
val = (l_uint8)(norm * val);
SET_DATA_BYTE(lined, j, val);
}
}
/*------------------------------------------------------------*
* Fix normalization for boundary pixels *
*------------------------------------------------------------*/
for (i = 0; i <= hc; i++) { /* first hc + 1 lines */
hn = hc + i;
normh = (l_float32)fhc / (l_float32)hn; /* > 1 */
lined = datad + wpl * i;
for (j = 0; j <= wc; j++) {
wn = wc + j;
normw = (l_float32)fwc / (l_float32)wn; /* > 1 */
val = GET_DATA_BYTE(lined, j);
val = (l_uint8)(val * normh * normw);
SET_DATA_BYTE(lined, j, val);
}
for (j = wc + 1; j < wmwc; j++) {
val = GET_DATA_BYTE(lined, j);
val = (l_uint8)(val * normh);
SET_DATA_BYTE(lined, j, val);
}
for (j = wmwc; j < w; j++) {
wn = wc + w - j;
normw = (l_float32)fwc / (l_float32)wn; /* > 1 */
val = GET_DATA_BYTE(lined, j);
val = (l_uint8)(val * normh * normw);
SET_DATA_BYTE(lined, j, val);
}
}
for (i = hmhc; i < h; i++) { /* last hc lines */
hn = hc + h - i;
normh = (l_float32)fhc / (l_float32)hn; /* > 1 */
lined = datad + wpl * i;
for (j = 0; j <= wc; j++) {
wn = wc + j;
normw = (l_float32)fwc / (l_float32)wn; /* > 1 */
val = GET_DATA_BYTE(lined, j);
val = (l_uint8)(val * normh * normw);
SET_DATA_BYTE(lined, j, val);
}
for (j = wc + 1; j < wmwc; j++) {
val = GET_DATA_BYTE(lined, j);
val = (l_uint8)(val * normh);
SET_DATA_BYTE(lined, j, val);
}
for (j = wmwc; j < w; j++) {
wn = wc + w - j;
normw = (l_float32)fwc / (l_float32)wn; /* > 1 */
val = GET_DATA_BYTE(lined, j);
val = (l_uint8)(val * normh * normw);
SET_DATA_BYTE(lined, j, val);
}
}
for (i = hc + 1; i < hmhc; i++) { /* intermediate lines */
lined = datad + wpl * i;
for (j = 0; j <= wc; j++) { /* first wc + 1 columns */
wn = wc + j;
normw = (l_float32)fwc / (l_float32)wn; /* > 1 */
val = GET_DATA_BYTE(lined, j);
val = (l_uint8)(val * normw);
SET_DATA_BYTE(lined, j, val);
}
for (j = wmwc; j < w; j++) { /* last wc columns */
wn = wc + w - j;
normw = (l_float32)fwc / (l_float32)wn; /* > 1 */
val = GET_DATA_BYTE(lined, j);
val = (l_uint8)(val * normw);
SET_DATA_BYTE(lined, j, val);
}
}
return;
}
/*----------------------------------------------------------------------*
* Census transform *
*----------------------------------------------------------------------*/
/*!
* \brief pixCensusTransform()
*
* \param[in] pixs 8 bpp
* \param[in] halfsize of square over which neighbors are averaged
* \param[in] pixacc [optional] 32 bpp pix
* \return pixd 1 bpp
*
* <pre>
* Notes:
* (1) The Census transform was invented by Ramin Zabih and John Woodfill
* ("Non-parametric local transforms for computing visual
* correspondence", Third European Conference on Computer Vision,
* Stockholm, Sweden, May 1994); see publications at
* http://www.cs.cornell.edu/~rdz/index.htm
* This compares each pixel against the average of its neighbors,
* in a square of odd dimension centered on the pixel.
* If the pixel is greater than the average of its neighbors,
* the output pixel value is 1; otherwise it is 0.
* (2) This can be used as an encoding for an image that is
* fairly robust against slow illumination changes, with
* applications in image comparison and mosaicing.
* (3) The size of the convolution kernel is (2 * halfsize + 1)
* on a side. The halfsize parameter must be >= 1.
* (4) If accum pix is null, make one, use it, and destroy it
* before returning; otherwise, just use the input accum pix
* </pre>
*/
PIX *
pixCensusTransform(PIX *pixs,
l_int32 halfsize,
PIX *pixacc)
{
l_int32 i, j, w, h, wpls, wplv, wpld;
l_int32 vals, valv;
l_uint32 *datas, *datav, *datad, *lines, *linev, *lined;
PIX *pixav, *pixd;
PROCNAME("pixCensusTransform");
if (!pixs)
return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
if (pixGetDepth(pixs) != 8)
return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
if (halfsize < 1)
return (PIX *)ERROR_PTR("halfsize must be >= 1", procName, NULL);
/* Get the average of each pixel with its neighbors */
if ((pixav = pixBlockconvGray(pixs, pixacc, halfsize, halfsize))
== NULL)
return (PIX *)ERROR_PTR("pixav not made", procName, NULL);
/* Subtract the pixel from the average, and then compare
* the pixel value with the remaining average */
pixGetDimensions(pixs, &w, &h, NULL);
if ((pixd = pixCreate(w, h, 1)) == NULL) {
pixDestroy(&pixav);
return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
}
datas = pixGetData(pixs);
datav = pixGetData(pixav);
datad = pixGetData(pixd);
wpls = pixGetWpl(pixs);
wplv = pixGetWpl(pixav);
wpld = pixGetWpl(pixd);
for (i = 0; i < h; i++) {
lines = datas + i * wpls;
linev = datav + i * wplv;
lined = datad + i * wpld;
for (j = 0; j < w; j++) {
vals = GET_DATA_BYTE(lines, j);
valv = GET_DATA_BYTE(linev, j);
if (vals > valv)
SET_DATA_BIT(lined, j);
}
}
pixDestroy(&pixav);
return pixd;
}
/*----------------------------------------------------------------------*
* Generic convolution *
*----------------------------------------------------------------------*/
/*!
* \brief pixConvolve()
*
* \param[in] pixs 8, 16, 32 bpp; no colormap
* \param[in] kel kernel
* \param[in] outdepth of pixd: 8, 16 or 32
* \param[in] normflag 1 to normalize kernel to unit sum; 0 otherwise
* \return pixd 8, 16 or 32 bpp
*
* <pre>
* Notes:
* (1) This gives a convolution with an arbitrary kernel.
* (2) The input pixs must have only one sample/pixel.
* To do a convolution on an RGB image, use pixConvolveRGB().
* (3) The parameter %outdepth determines the depth of the result.
* If the kernel is normalized to unit sum, the output values
* can never exceed 255, so an output depth of 8 bpp is sufficient.
* If the kernel is not normalized, it may be necessary to use
* 16 or 32 bpp output to avoid overflow.
* (4) If normflag == 1, the result is normalized by scaling all
* kernel values for a unit sum. If the sum of kernel values
* is very close to zero, the kernel can not be normalized and
* the convolution will not be performed. A warning is issued.
* (5) The kernel values can be positive or negative, but the
* result for the convolution can only be stored as a positive
* number. Consequently, if it goes negative, the choices are
* to clip to 0 or take the absolute value. We're choosing
* to take the absolute value. (Another possibility would be
* to output a second unsigned image for the negative values.)
* If you want to get a clipped result, or to keep the negative
* values in the result, use fpixConvolve(), with the
* converters in fpix2.c between pix and fpix.
* (6) This uses a mirrored border to avoid special casing on
* the boundaries.
* (7) To get a subsampled output, call l_setConvolveSampling().
* The time to make a subsampled output is reduced by the
* product of the sampling factors.
* (8) The function is slow, running at about 12 machine cycles for
* each pixel-op in the convolution. For example, with a 3 GHz
* cpu, a 1 Mpixel grayscale image, and a kernel with
* (sx * sy) = 25 elements, the convolution takes about 100 msec.
* </pre>
*/
PIX *
pixConvolve(PIX *pixs,
L_KERNEL *kel,
l_int32 outdepth,
l_int32 normflag)
{
l_int32 i, j, id, jd, k, m, w, h, d, wd, hd, sx, sy, cx, cy, wplt, wpld;
l_int32 val;
l_uint32 *datat, *datad, *linet, *lined;
l_float32 sum;
L_KERNEL *keli, *keln;
PIX *pixt, *pixd;
PROCNAME("pixConvolve");
if (!pixs)
return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
if (pixGetColormap(pixs))
return (PIX *)ERROR_PTR("pixs has colormap", procName, NULL);
pixGetDimensions(pixs, &w, &h, &d);
if (d != 8 && d != 16 && d != 32)
return (PIX *)ERROR_PTR("pixs not 8, 16, or 32 bpp", procName, NULL);
if (!kel)
return (PIX *)ERROR_PTR("kel not defined", procName, NULL);
pixd = NULL;
keli = kernelInvert(kel);
kernelGetParameters(keli, &sy, &sx, &cy, &cx);
if (normflag)
keln = kernelNormalize(keli, 1.0);
else
keln = kernelCopy(keli);
if ((pixt = pixAddMirroredBorder(pixs, cx, sx - cx, cy, sy - cy)) == NULL) {
L_ERROR("pixt not made\n", procName);
goto cleanup;
}
wd = (w + ConvolveSamplingFactX - 1) / ConvolveSamplingFactX;
hd = (h + ConvolveSamplingFactY - 1) / ConvolveSamplingFactY;
pixd = pixCreate(wd, hd, outdepth);
datat = pixGetData(pixt);
datad = pixGetData(pixd);
wplt = pixGetWpl(pixt);
wpld = pixGetWpl(pixd);
for (i = 0, id = 0; id < hd; i += ConvolveSamplingFactY, id++) {
lined = datad + id * wpld;
for (j = 0, jd = 0; jd < wd; j += ConvolveSamplingFactX, jd++) {
sum = 0.0;
for (k = 0; k < sy; k++) {
linet = datat + (i + k) * wplt;
if (d == 8) {
for (m = 0; m < sx; m++) {
val = GET_DATA_BYTE(linet, j + m);
sum += val * keln->data[k][m];
}
} else if (d == 16) {
for (m = 0; m < sx; m++) {
val = GET_DATA_TWO_BYTES(linet, j + m);
sum += val * keln->data[k][m];
}
} else { /* d == 32 */
for (m = 0; m < sx; m++) {
val = *(linet + j + m);
sum += val * keln->data[k][m];
}
}
}
if (sum < 0.0) sum = -sum; /* make it non-negative */
if (outdepth == 8)
SET_DATA_BYTE(lined, jd, (l_int32)(sum + 0.5));
else if (outdepth == 16)
SET_DATA_TWO_BYTES(lined, jd, (l_int32)(sum + 0.5));
else /* outdepth == 32 */
*(lined + jd) = (l_uint32)(sum + 0.5);
}
}
cleanup:
kernelDestroy(&keli);
kernelDestroy(&keln);
pixDestroy(&pixt);
return pixd;
}
/*!
* \brief pixConvolveSep()
*
* \param[in] pixs 8, 16, 32 bpp; no colormap
* \param[in] kelx x-dependent kernel
* \param[in] kely y-dependent kernel
* \param[in] outdepth of pixd: 8, 16 or 32
* \param[in] normflag 1 to normalize kernel to unit sum; 0 otherwise
* \return pixd 8, 16 or 32 bpp
*
* <pre>
* Notes:
* (1) This does a convolution with a separable kernel that is
* is a sequence of convolutions in x and y. The two
* one-dimensional kernel components must be input separately;
* the full kernel is the product of these components.
* The support for the full kernel is thus a rectangular region.
* (2) The input pixs must have only one sample/pixel.
* To do a convolution on an RGB image, use pixConvolveSepRGB().
* (3) The parameter %outdepth determines the depth of the result.
* If the kernel is normalized to unit sum, the output values
* can never exceed 255, so an output depth of 8 bpp is sufficient.
* If the kernel is not normalized, it may be necessary to use
* 16 or 32 bpp output to avoid overflow.
* (2) The %normflag parameter is used as in pixConvolve().
* (4) The kernel values can be positive or negative, but the
* result for the convolution can only be stored as a positive
* number. Consequently, if it goes negative, the choices are
* to clip to 0 or take the absolute value. We're choosing
* the former for now. Another possibility would be to output
* a second unsigned image for the negative values.
* (5) Warning: if you use l_setConvolveSampling() to get a
* subsampled output, and the sampling factor is larger than
* the kernel half-width, it is faster to use the non-separable
* version pixConvolve(). This is because the first convolution
* here must be done on every raster line, regardless of the
* vertical sampling factor. If the sampling factor is smaller
* than kernel half-width, it's faster to use the separable
* convolution.
* (6) This uses mirrored borders to avoid special casing on
* the boundaries.
* </pre>
*/
PIX *
pixConvolveSep(PIX *pixs,
L_KERNEL *kelx,
L_KERNEL *kely,
l_int32 outdepth,
l_int32 normflag)
{
l_int32 d, xfact, yfact;
L_KERNEL *kelxn, *kelyn;
PIX *pixt, *pixd;
PROCNAME("pixConvolveSep");
if (!pixs)
return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
d = pixGetDepth(pixs);
if (d != 8 && d != 16 && d != 32)
return (PIX *)ERROR_PTR("pixs not 8, 16, or 32 bpp", procName, NULL);
if (!kelx)
return (PIX *)ERROR_PTR("kelx not defined", procName, NULL);
if (!kely)
return (PIX *)ERROR_PTR("kely not defined", procName, NULL);
xfact = ConvolveSamplingFactX;
yfact = ConvolveSamplingFactY;
if (normflag) {
kelxn = kernelNormalize(kelx, 1000.0);
kelyn = kernelNormalize(kely, 0.001);
l_setConvolveSampling(xfact, 1);
pixt = pixConvolve(pixs, kelxn, 32, 0);
l_setConvolveSampling(1, yfact);
pixd = pixConvolve(pixt, kelyn, outdepth, 0);
l_setConvolveSampling(xfact, yfact); /* restore */
kernelDestroy(&kelxn);
kernelDestroy(&kelyn);
} else { /* don't normalize */
l_setConvolveSampling(xfact, 1);
pixt = pixConvolve(pixs, kelx, 32, 0);
l_setConvolveSampling(1, yfact);
pixd = pixConvolve(pixt, kely, outdepth, 0);
l_setConvolveSampling(xfact, yfact);
}
pixDestroy(&pixt);
return pixd;
}
/*!
* \brief pixConvolveRGB()
*
* \param[in] pixs 32 bpp rgb
* \param[in] kel kernel
* \return pixd 32 bpp rgb
*
* <pre>
* Notes:
* (1) This gives a convolution on an RGB image using an
* arbitrary kernel (which we normalize to keep each
* component within the range [0 ... 255].
* (2) The input pixs must be RGB.
* (3) The kernel values can be positive or negative, but the
* result for the convolution can only be stored as a positive
* number. Consequently, if it goes negative, we clip the
* result to 0.
* (4) To get a subsampled output, call l_setConvolveSampling().
* The time to make a subsampled output is reduced by the
* product of the sampling factors.
* (5) This uses a mirrored border to avoid special casing on
* the boundaries.
* </pre>
*/
PIX *
pixConvolveRGB(PIX *pixs,
L_KERNEL *kel)
{
PIX *pixt, *pixr, *pixg, *pixb, *pixd;
PROCNAME("pixConvolveRGB");
if (!pixs)
return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
if (pixGetDepth(pixs) != 32)
return (PIX *)ERROR_PTR("pixs is not 32 bpp", procName, NULL);
if (!kel)
return (PIX *)ERROR_PTR("kel not defined", procName, NULL);
pixt = pixGetRGBComponent(pixs, COLOR_RED);
pixr = pixConvolve(pixt, kel, 8, 1);
pixDestroy(&pixt);
pixt = pixGetRGBComponent(pixs, COLOR_GREEN);
pixg = pixConvolve(pixt, kel, 8, 1);
pixDestroy(&pixt);
pixt = pixGetRGBComponent(pixs, COLOR_BLUE);
pixb = pixConvolve(pixt, kel, 8, 1);
pixDestroy(&pixt);
pixd = pixCreateRGBImage(pixr, pixg, pixb);
pixDestroy(&pixr);
pixDestroy(&pixg);
pixDestroy(&pixb);
return pixd;
}
/*!
* \brief pixConvolveRGBSep()
*
* \param[in] pixs 32 bpp rgb
* \param[in] kelx x-dependent kernel
* \param[in] kely y-dependent kernel
* \return pixd 32 bpp rgb
*
* <pre>
* Notes:
* (1) This does a convolution on an RGB image using a separable
* kernel that is a sequence of convolutions in x and y. The two
* one-dimensional kernel components must be input separately;
* the full kernel is the product of these components.
* The support for the full kernel is thus a rectangular region.
* (2) The kernel values can be positive or negative, but the
* result for the convolution can only be stored as a positive
* number. Consequently, if it goes negative, we clip the
* result to 0.
* (3) To get a subsampled output, call l_setConvolveSampling().
* The time to make a subsampled output is reduced by the
* product of the sampling factors.
* (4) This uses a mirrored border to avoid special casing on
* the boundaries.
* </pre>
*/
PIX *
pixConvolveRGBSep(PIX *pixs,
L_KERNEL *kelx,
L_KERNEL *kely)
{
PIX *pixt, *pixr, *pixg, *pixb, *pixd;
PROCNAME("pixConvolveRGBSep");
if (!pixs)
return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
if (pixGetDepth(pixs) != 32)
return (PIX *)ERROR_PTR("pixs is not 32 bpp", procName, NULL);
if (!kelx || !kely)
return (PIX *)ERROR_PTR("kelx, kely not both defined", procName, NULL);
pixt = pixGetRGBComponent(pixs, COLOR_RED);
pixr = pixConvolveSep(pixt, kelx, kely, 8, 1);
pixDestroy(&pixt);
pixt = pixGetRGBComponent(pixs, COLOR_GREEN);
pixg = pixConvolveSep(pixt, kelx, kely, 8, 1);
pixDestroy(&pixt);
pixt = pixGetRGBComponent(pixs, COLOR_BLUE);
pixb = pixConvolveSep(pixt, kelx, kely, 8, 1);
pixDestroy(&pixt);
pixd = pixCreateRGBImage(pixr, pixg, pixb);
pixDestroy(&pixr);
pixDestroy(&pixg);
pixDestroy(&pixb);
return pixd;
}
/*----------------------------------------------------------------------*
* Generic convolution with float array *
*----------------------------------------------------------------------*/
/*!
* \brief fpixConvolve()
*
* \param[in] fpixs 32 bit float array
* \param[in] kel kernel
* \param[in] normflag 1 to normalize kernel to unit sum; 0 otherwise
* \return fpixd 32 bit float array
*
* <pre>
* Notes:
* (1) This gives a float convolution with an arbitrary kernel.
* (2) If normflag == 1, the result is normalized by scaling all
* kernel values for a unit sum. If the sum of kernel values
* is very close to zero, the kernel can not be normalized and
* the convolution will not be performed. A warning is issued.
* (3) With the FPix, there are no issues about negative
* array or kernel values. The convolution is performed
* with single precision arithmetic.
* (4) To get a subsampled output, call l_setConvolveSampling().
* The time to make a subsampled output is reduced by the
* product of the sampling factors.
* (5) This uses a mirrored border to avoid special casing on
* the boundaries.
* </pre>
*/
FPIX *
fpixConvolve(FPIX *fpixs,
L_KERNEL *kel,
l_int32 normflag)
{
l_int32 i, j, id, jd, k, m, w, h, wd, hd, sx, sy, cx, cy, wplt, wpld;
l_float32 val;
l_float32 *datat, *datad, *linet, *lined;
l_float32 sum;
L_KERNEL *keli, *keln;
FPIX *fpixt, *fpixd;
PROCNAME("fpixConvolve");
if (!fpixs)
return (FPIX *)ERROR_PTR("fpixs not defined", procName, NULL);
if (!kel)
return (FPIX *)ERROR_PTR("kel not defined", procName, NULL);
fpixd = NULL;
keli = kernelInvert(kel);
kernelGetParameters(keli, &sy, &sx, &cy, &cx);
if (normflag)
keln = kernelNormalize(keli, 1.0);
else
keln = kernelCopy(keli);
fpixGetDimensions(fpixs, &w, &h);
fpixt = fpixAddMirroredBorder(fpixs, cx, sx - cx, cy, sy - cy);
if (!fpixt) {
L_ERROR("fpixt not made\n", procName);
goto cleanup;
}
wd = (w + ConvolveSamplingFactX - 1) / ConvolveSamplingFactX;
hd = (h + ConvolveSamplingFactY - 1) / ConvolveSamplingFactY;
fpixd = fpixCreate(wd, hd);
datat = fpixGetData(fpixt);
datad = fpixGetData(fpixd);
wplt = fpixGetWpl(fpixt);
wpld = fpixGetWpl(fpixd);
for (i = 0, id = 0; id < hd; i += ConvolveSamplingFactY, id++) {
lined = datad + id * wpld;
for (j = 0, jd = 0; jd < wd; j += ConvolveSamplingFactX, jd++) {
sum = 0.0;
for (k = 0; k < sy; k++) {
linet = datat + (i + k) * wplt;
for (m = 0; m < sx; m++) {
val = *(linet + j + m);
sum += val * keln->data[k][m];
}
}
*(lined + jd) = sum;
}
}
cleanup:
kernelDestroy(&keli);
kernelDestroy(&keln);
fpixDestroy(&fpixt);
return fpixd;
}
/*!
* \brief fpixConvolveSep()
*
* \param[in] fpixs 32 bit float array
* \param[in] kelx x-dependent kernel
* \param[in] kely y-dependent kernel
* \param[in] normflag 1 to normalize kernel to unit sum; 0 otherwise
* \return fpixd 32 bit float array
*
* <pre>
* Notes:
* (1) This does a convolution with a separable kernel that is
* is a sequence of convolutions in x and y. The two
* one-dimensional kernel components must be input separately;
* the full kernel is the product of these components.
* The support for the full kernel is thus a rectangular region.
* (2) The normflag parameter is used as in fpixConvolve().
* (3) Warning: if you use l_setConvolveSampling() to get a
* subsampled output, and the sampling factor is larger than
* the kernel half-width, it is faster to use the non-separable
* version pixConvolve(). This is because the first convolution
* here must be done on every raster line, regardless of the
* vertical sampling factor. If the sampling factor is smaller
* than kernel half-width, it's faster to use the separable
* convolution.
* (4) This uses mirrored borders to avoid special casing on
* the boundaries.
* </pre>
*/
FPIX *
fpixConvolveSep(FPIX *fpixs,
L_KERNEL *kelx,
L_KERNEL *kely,
l_int32 normflag)
{
l_int32 xfact, yfact;
L_KERNEL *kelxn, *kelyn;
FPIX *fpixt, *fpixd;
PROCNAME("fpixConvolveSep");
if (!fpixs)
return (FPIX *)ERROR_PTR("pixs not defined", procName, NULL);
if (!kelx)
return (FPIX *)ERROR_PTR("kelx not defined", procName, NULL);
if (!kely)
return (FPIX *)ERROR_PTR("kely not defined", procName, NULL);
xfact = ConvolveSamplingFactX;
yfact = ConvolveSamplingFactY;
if (normflag) {
kelxn = kernelNormalize(kelx, 1.0);
kelyn = kernelNormalize(kely, 1.0);
l_setConvolveSampling(xfact, 1);
fpixt = fpixConvolve(fpixs, kelxn, 0);
l_setConvolveSampling(1, yfact);
fpixd = fpixConvolve(fpixt, kelyn, 0);
l_setConvolveSampling(xfact, yfact); /* restore */
kernelDestroy(&kelxn);
kernelDestroy(&kelyn);
} else { /* don't normalize */
l_setConvolveSampling(xfact, 1);
fpixt = fpixConvolve(fpixs, kelx, 0);
l_setConvolveSampling(1, yfact);
fpixd = fpixConvolve(fpixt, kely, 0);
l_setConvolveSampling(xfact, yfact);
}
fpixDestroy(&fpixt);
return fpixd;
}
/*------------------------------------------------------------------------*
* Convolution with bias (for non-negative output) *
*------------------------------------------------------------------------*/
/*!
* \brief pixConvolveWithBias()
*
* \param[in] pixs 8 bpp; no colormap
* \param[in] kel1
* \param[in] kel2 can be null; use if separable
* \param[in] force8 if 1, force output to 8 bpp; otherwise, determine
* output depth by the dynamic range of pixel values
* \param[out] pbias applied bias
* \return pixd 8 or 16 bpp
*
* <pre>
* Notes:
* (1) This does a convolution with either a single kernel or
* a pair of separable kernels, and automatically applies whatever
* bias (shift) is required so that the resulting pixel values
* are non-negative.
* (2) The kernel is always normalized. If there are no negative
* values in the kernel, a standard normalized convolution is
* performed, with 8 bpp output. If the sum of kernel values is
* very close to zero, the kernel can not be normalized and
* the convolution will not be performed. An error message results.
* (3) If there are negative values in the kernel, the pix is
* converted to an fpix, the convolution is done on the fpix, and
* a bias (shift) may need to be applied.
* (4) If force8 == TRUE and the range of values after the convolution
* is > 255, the output values will be scaled to fit in [0 ... 255].
* If force8 == FALSE, the output will be either 8 or 16 bpp,
* to accommodate the dynamic range of output values without scaling.
* </pre>
*/
PIX *
pixConvolveWithBias(PIX *pixs,
L_KERNEL *kel1,
L_KERNEL *kel2,
l_int32 force8,
l_int32 *pbias)
{
l_int32 outdepth;
l_float32 min1, min2, min, minval, maxval, range;
FPIX *fpix1, *fpix2;
PIX *pixd;
PROCNAME("pixConvolveWithBias");
if (!pbias)
return (PIX *)ERROR_PTR("&bias not defined", procName, NULL);
*pbias = 0;
if (!pixs || pixGetDepth(pixs) != 8)
return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", procName, NULL);
if (pixGetColormap(pixs))
return (PIX *)ERROR_PTR("pixs has colormap", procName, NULL);
if (!kel1)
return (PIX *)ERROR_PTR("kel1 not defined", procName, NULL);
/* Determine if negative values can be produced in the convolution */
kernelGetMinMax(kel1, &min1, NULL);
min2 = 0.0;
if (kel2)
kernelGetMinMax(kel2, &min2, NULL);
min = L_MIN(min1, min2);
if (min >= 0.0) {
if (!kel2)
return pixConvolve(pixs, kel1, 8, 1);
else
return pixConvolveSep(pixs, kel1, kel2, 8, 1);
}
/* Bias may need to be applied; convert to fpix and convolve */
fpix1 = pixConvertToFPix(pixs, 1);
if (!kel2)
fpix2 = fpixConvolve(fpix1, kel1, 1);
else
fpix2 = fpixConvolveSep(fpix1, kel1, kel2, 1);
fpixDestroy(&fpix1);
/* Determine the bias and the dynamic range.
* If the dynamic range is <= 255, just shift the values by the
* bias, if any.
* If the dynamic range is > 255, there are two cases:
* (1) the output depth is not forced to 8 bpp
* ==> apply the bias without scaling; outdepth = 16
* (2) the output depth is forced to 8
* ==> linearly map the pixel values to [0 ... 255]. */
fpixGetMin(fpix2, &minval, NULL, NULL);
fpixGetMax(fpix2, &maxval, NULL, NULL);
range = maxval - minval;
*pbias = (minval < 0.0) ? -minval : 0.0;
fpixAddMultConstant(fpix2, *pbias, 1.0); /* shift: min val ==> 0 */
if (range <= 255 || !force8) { /* no scaling of output values */
outdepth = (range > 255) ? 16 : 8;
} else { /* scale output values to fit in 8 bpp */
fpixAddMultConstant(fpix2, 0.0, (255.0 / range));
outdepth = 8;
}
/* Convert back to pix; it won't do any clipping */
pixd = fpixConvertToPix(fpix2, outdepth, L_CLIP_TO_ZERO, 0);
fpixDestroy(&fpix2);
return pixd;
}
/*------------------------------------------------------------------------*
* Set parameter for convolution subsampling *
*------------------------------------------------------------------------*/
/*!
* \brief l_setConvolveSampling()
*
* \param[in] xfact, yfact integer >= 1
* \return void
*
* <pre>
* Notes:
* (1) This sets the x and y output subsampling factors for generic pix
* and fpix convolution. The default values are 1 (no subsampling).
* </pre>
*/
void
l_setConvolveSampling(l_int32 xfact,
l_int32 yfact)
{
if (xfact < 1) xfact = 1;
if (yfact < 1) yfact = 1;
ConvolveSamplingFactX = xfact;
ConvolveSamplingFactY = yfact;
}
/*------------------------------------------------------------------------*
* Additive gaussian noise *
*------------------------------------------------------------------------*/
/*!
* \brief pixAddGaussianNoise()
*
* \param[in] pixs 8 bpp gray or 32 bpp rgb; no colormap
* \param[in] stdev of noise
* \return pixd 8 or 32 bpp, or NULL on error
*
* <pre>
* Notes:
* (1) This adds noise to each pixel, taken from a normal
* distribution with zero mean and specified standard deviation.
* </pre>
*/
PIX *
pixAddGaussianNoise(PIX *pixs,
l_float32 stdev)
{
l_int32 i, j, w, h, d, wpls, wpld, val, rval, gval, bval;
l_uint32 pixel;
l_uint32 *datas, *datad, *lines, *lined;
PIX *pixd;
PROCNAME("pixAddGaussianNoise");
if (!pixs)
return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
if (pixGetColormap(pixs))
return (PIX *)ERROR_PTR("pixs has colormap", procName, NULL);
pixGetDimensions(pixs, &w, &h, &d);
if (d != 8 && d != 32)
return (PIX *)ERROR_PTR("pixs not 8 or 32 bpp", procName, NULL);
pixd = pixCreateTemplateNoInit(pixs);
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++) {
if (d == 8) {
val = GET_DATA_BYTE(lines, j);
val += (l_int32)(stdev * gaussDistribSampling() + 0.5);
val = L_MIN(255, L_MAX(0, val));
SET_DATA_BYTE(lined, j, val);
} else { /* d = 32 */
pixel = *(lines + j);
extractRGBValues(pixel, &rval, &gval, &bval);
rval += (l_int32)(stdev * gaussDistribSampling() + 0.5);
rval = L_MIN(255, L_MAX(0, rval));
gval += (l_int32)(stdev * gaussDistribSampling() + 0.5);
gval = L_MIN(255, L_MAX(0, gval));
bval += (l_int32)(stdev * gaussDistribSampling() + 0.5);
bval = L_MIN(255, L_MAX(0, bval));
composeRGBPixel(rval, gval, bval, lined + j);
}
}
}
return pixd;
}
/*!
* \brief gaussDistribSampling()
*
* \return gaussian distributed variable with zero mean and unit stdev
*
* <pre>
* Notes:
* (1) For an explanation of the Box-Muller method for generating
* a normally distributed random variable with zero mean and
* unit standard deviation, see Numerical Recipes in C,
* 2nd edition, p. 288ff.
* (2) This can be called sequentially to get samples that can be
* used for adding noise to each pixel of an image, for example.
* </pre>
*/
l_float32
gaussDistribSampling()
{
static l_int32 select = 0; /* flips between 0 and 1 on successive calls */
static l_float32 saveval;
l_float32 frand, xval, yval, rsq, factor;
if (select == 0) {
while (1) { /* choose a point in a 2x2 square, centered at origin */
frand = (l_float32)rand() / (l_float32)RAND_MAX;
xval = 2.0 * frand - 1.0;
frand = (l_float32)rand() / (l_float32)RAND_MAX;
yval = 2.0 * frand - 1.0;
rsq = xval * xval + yval * yval;
if (rsq > 0.0 && rsq < 1.0) /* point is inside the unit circle */
break;
}
factor = sqrt(-2.0 * log(rsq) / rsq);
saveval = xval * factor;
select = 1;
return yval * factor;
}
else {
select = 0;
return saveval;
}
}