| /*====================================================================* |
| - 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. |
| *====================================================================*/ |
| |
| /* |
| * convolve.c |
| * |
| * Top level grayscale or color block convolution |
| * PIX *pixBlockconv() |
| * |
| * Grayscale block convolution |
| * PIX *pixBlockconvGray() |
| * |
| * Accumulator for 1, 8 and 32 bpp convolution |
| * PIX *pixBlockconvAccum() |
| * |
| * Un-normalized grayscale block convolution |
| * PIX *pixBlockconvGrayUnnormalized() |
| * |
| * Tiled grayscale or color block convolution |
| * PIX *pixBlockconvTiled() |
| * PIX *pixBlockconvGrayTile() |
| * |
| * Convolution for average in specified window |
| * PIX *pixWindowedMean() |
| * |
| * Convolution for average square value in specified window |
| * PIX *pixWindowedMeanSquare() |
| * DPIX *pixMeanSquareAccum() |
| * |
| * Binary block sum and rank filter |
| * PIX *pixBlockrank() |
| * PIX *pixBlocksum() |
| * |
| * Woodfill transform |
| * PIX *pixWoodfillTransform() |
| * |
| * Generic convolution (with Pix) |
| * PIX *pixConvolve() |
| * PIX *pixConvolveSep() |
| * |
| * Generic convolution (with float arrays) |
| * FPIX *fpixConvolve() |
| * FPIX *fpixConvolveSep() |
| */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include "allheaders.h" |
| |
| |
| /*----------------------------------------------------------------------* |
| * Top-level grayscale or color block convolution * |
| *----------------------------------------------------------------------*/ |
| /*! |
| * pixBlockconv() |
| * |
| * Input: pix (8 or 32 bpp; or 2, 4 or 8 bpp with colormap) |
| * wc, hc (half width/height of convolution kernel) |
| * Return: pixd, or null on error |
| * |
| * 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. |
| */ |
| 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!", procName); |
| L_INFO_INT2("wc = %d, hc = %d", 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", 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 * |
| *----------------------------------------------------------------------*/ |
| /*! |
| * pixBlockconvGray() |
| * |
| * Input: pix (8 bpp) |
| * accum pix (32 bpp; can be null) |
| * wc, hc (half width/height of convolution kernel) |
| * Return: pix (8 bpp), or null on error |
| * |
| * 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. |
| */ |
| 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!", procName); |
| L_INFO_INT2("wc = %d, hc = %d", 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", 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) |
| 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; |
| } |
| |
| |
| /*----------------------------------------------------------------------* |
| * Accumulator for 1, 8 and 32 bpp convolution * |
| *----------------------------------------------------------------------*/ |
| /*! |
| * pixBlockconvAccum() |
| * |
| * Input: pixs (1, 8 or 32 bpp) |
| * Return: accum pix (32 bpp), or null on error. |
| * |
| * 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) |
| */ |
| 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; |
| } |
| |
| |
| /*----------------------------------------------------------------------* |
| * Un-normalized grayscale block convolution * |
| *----------------------------------------------------------------------*/ |
| /*! |
| * pixBlockconvGrayUnnormalized() |
| * |
| * Input: pixs (8 bpp) |
| * 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 |
| * |
| * 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. |
| */ |
| 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!", procName); |
| L_INFO_INT2("wc = %d, hc = %d", 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); |
| if ((pixacc = pixBlockconvAccum(pixsb)) == NULL) |
| return (PIX *)ERROR_PTR("pixacc not made", procName, NULL); |
| pixDestroy(&pixsb); |
| if ((pixd = pixCreate(w, h, 32)) == NULL) |
| 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 * |
| *----------------------------------------------------------------------*/ |
| /*! |
| * pixBlockconvTiled() |
| * |
| * Input: pix (8 or 32 bpp; or 2, 4 or 8 bpp with colormap) |
| * wc, hc (half width/height of convolution kernel) |
| * nx, ny (subdivision into tiles) |
| * Return: pixd, or null on error |
| * |
| * 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. |
| */ |
| 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!", procName); |
| L_INFO_INT2("wc = %d, hc = %d", 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_INT("tile width too small; nx reduced to %d", procName, nx); |
| } |
| if (yrat < hc + 2) { |
| ny = h / (hc + 2); |
| L_WARNING_INT("tile height too small; ny reduced to %d", procName, ny); |
| } |
| |
| /* Remove colormap if necessary */ |
| if ((d == 2 || d == 4 || d == 8) && pixGetColormap(pix)) { |
| L_WARNING("pix has colormap; removing", 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(). */ |
| pixd = pixCreateTemplateNoInit(pixs); |
| 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; |
| } |
| |
| |
| /*! |
| * pixBlockconvGrayTile() |
| * |
| * Input: pixs (8 bpp gray) |
| * pixacc (32 bpp accum pix) |
| * wc, hc (half width/height of convolution kernel) |
| * Return: pixd, or null on error |
| * |
| * 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. |
| */ |
| 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!", procName); |
| L_INFO_INT2("wc = %d, hc = %d", 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", 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) |
| 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 average in specified window * |
| *----------------------------------------------------------------------*/ |
| /*! |
| * pixWindowedMean() |
| * |
| * Input: pixs (8 or 32 bpp grayscale) |
| * wc, hc (half width/height of convolution kernel) |
| * 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) |
| * |
| * 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, is included in pixs. |
| * The output pixd (after convolution) has this border removed. |
| * (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. |
| */ |
| PIX * |
| pixWindowedMean(PIX *pixs, |
| l_int32 wc, |
| l_int32 hc, |
| 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 *pixc, *pixd; |
| |
| PROCNAME("pixWindowedMean"); |
| |
| if (!pixs) |
| return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); |
| pixGetDimensions(pixs, &w, &h, &d); |
| 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); |
| |
| /* Strip off wc + 1 border pixels from each side and |
| * hc + 1 border pixels from top and bottom. */ |
| wd = w - 2 * (wc + 1); |
| hd = h - 2 * (hc + 1); |
| if (wd < 2 || hd < 2) |
| return (PIX *)ERROR_PTR("w or h too small for kernel", procName, NULL); |
| if ((pixd = pixCreate(wd, hd, d)) == NULL) |
| return (PIX *)ERROR_PTR("pixd not made", procName, NULL); |
| |
| /* Make the accumulator pix */ |
| if ((pixc = pixBlockconvAccum(pixs)) == NULL) |
| return (PIX *)ERROR_PTR("pixc not made", procName, NULL); |
| wplc = pixGetWpl(pixc); |
| wpld = pixGetWpl(pixd); |
| datad = pixGetData(pixd); |
| datac = pixGetData(pixc); |
| |
| wincr = 2 * wc + 1; |
| hincr = 2 * hc + 1; |
| norm = 1.0; /* use this for sum-in-window */ |
| if (normflag) |
| norm = 1.0 / (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; |
| } |
| } |
| } |
| |
| pixDestroy(&pixc); |
| return pixd; |
| } |
| |
| |
| /*----------------------------------------------------------------------* |
| * Convolution for average square value in specified window * |
| *----------------------------------------------------------------------*/ |
| /*! |
| * pixWindowedMeanSquare() |
| * |
| * Input: pixs (8 bpp grayscale) |
| * size (halfwidth of convolution kernel) |
| * Return: pixd (32 bpp, average over window of size (2 * size + 1)) |
| * |
| * Notes: |
| * (1) A set of border pixels of width (size + 1) is included |
| * in pixs. The output pixd (after convolution) has this |
| * border removed. |
| * (2) The advantage is that we are unaffected by the boundary, and |
| * it is not necessary to treat pixels within @size 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 (@size + 1), when |
| * we only need @size 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. |
| */ |
| PIX * |
| pixWindowedMeanSquare(PIX *pixs, |
| l_int32 size) |
| { |
| l_int32 i, j, w, h, wd, hd, wpl, wpld, incr; |
| l_uint32 ival; |
| l_uint32 *datad, *lined; |
| l_float64 norm; |
| l_float64 val; |
| l_float64 *data, *line1, *line2; |
| DPIX *dpix; |
| PIX *pixd; |
| |
| PROCNAME("pixWindowedMeanSquare"); |
| |
| |
| if (!pixs || (pixGetDepth(pixs) != 8)) |
| return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", procName, NULL); |
| pixGetDimensions(pixs, &w, &h, NULL); |
| if (size < 2) |
| return (PIX *)ERROR_PTR("size not >= 2", procName, NULL); |
| |
| if ((dpix = pixMeanSquareAccum(pixs)) == NULL) |
| return (PIX *)ERROR_PTR("dpix not made", procName, NULL); |
| wpl = dpixGetWpl(dpix); |
| data = dpixGetData(dpix); |
| |
| /* Strip off 2 * (size + 1) border pixels */ |
| wd = w - 2 * (size + 1); |
| hd = h - 2 * (size + 1); |
| if ((pixd = pixCreate(wd, hd, 32)) == NULL) |
| return (PIX *)ERROR_PTR("pixd not made", procName, NULL); |
| wpld = pixGetWpl(pixd); |
| datad = pixGetData(pixd); |
| |
| incr = 2 * size + 1; |
| norm = 1.0 / (incr * incr); |
| for (i = 0; i < hd; i++) { |
| line1 = data + i * wpl; |
| line2 = data + (i + incr) * wpl; |
| lined = datad + i * wpld; |
| for (j = 0; j < wd; j++) { |
| val = line2[j + incr] - line2[j] - line1[j + incr] + line1[j]; |
| ival = (l_uint32)(norm * val); |
| lined[j] = ival; |
| } |
| } |
| |
| dpixDestroy(&dpix); |
| return pixd; |
| } |
| |
| |
| /*! |
| * pixMeanSquareAccum() |
| * |
| * Input: pixs (8 bpp grayscale) |
| * Return: dpix (64 bit array), or null on error |
| * |
| * 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) |
| */ |
| 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] = val * val; |
| else |
| line[j] = line[j - 1] + 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] + val * val; |
| else |
| line[j] = line[j - 1] + linep[j] - linep[j - 1] + val * val; |
| } |
| } |
| |
| return dpix; |
| } |
| |
| |
| |
| /*----------------------------------------------------------------------* |
| * Binary block sum/rank * |
| *----------------------------------------------------------------------*/ |
| /*! |
| * pixBlockrank() |
| * |
| * Input: pixs (1 bpp) |
| * accum pix (<optional> 32 bpp) |
| * wc, hc (half width/height of block sum/rank kernel) |
| * rank (between 0.0 and 1.0; 0.5 is median filter) |
| * Return: pixd (1 bpp) |
| * |
| * 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. |
| */ |
| 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); |
| } |
| 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!", procName); |
| L_INFO_INT2("wc = %d, hc = %d", 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; |
| } |
| |
| |
| /*! |
| * pixBlocksum() |
| * |
| * Input: pixs (1 bpp) |
| * accum pix (<optional> 32 bpp) |
| * wc, hc (half width/height of block sum/rank kernel) |
| * Return: pixd (8 bpp) |
| * |
| * 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. |
| */ |
| 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!", procName); |
| L_INFO_INT2("wc = %d, hc = %d", 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) |
| 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; |
| } |
| |
| |
| /*----------------------------------------------------------------------* |
| * Woodfill transform * |
| *----------------------------------------------------------------------*/ |
| /*! |
| * pixWoodfillTransform() |
| * |
| * Input: pixs (8 bpp) |
| * halfsize (of square over which neighbors are averaged) |
| * accum pix (<optional> 32 bpp) |
| * Return: pixd (1 bpp) |
| * |
| * Notes: |
| * (1) The Woodfill transform 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 |
| */ |
| PIX * |
| pixWoodfillTransform(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("pixWoodfillTransform"); |
| |
| 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) |
| 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 * |
| *----------------------------------------------------------------------*/ |
| /*! |
| * pixConvolve() |
| * |
| * Input: pixs (8, 16, 32 bpp; no colormap) |
| * kernel |
| * outdepth (of pixd: 8, 16 or 32) |
| * normflag (1 to normalize kernel to unit sum; 0 otherwise) |
| * Return: pixd (8, 16 or 32 bpp) |
| * |
| * Notes: |
| * (1) This gives a convolution with an arbitrary kernel. |
| * (2) The parameter @outdepth determines the depth of the result. |
| * (3) If normflag == 1, the result is normalized by scaling |
| * all kernel values for a unit sum. Do not normalize if |
| * the kernel has null sum, such as a DoG. |
| * (4) 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. |
| * (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 |
| * the former for now. Another possibility would be to output |
| * a second unsigned image for the negative values. |
| * (6) This uses a mirrored border to avoid special casing on |
| * the boundaries. |
| * (7) 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. |
| */ |
| PIX * |
| pixConvolve(PIX *pixs, |
| L_KERNEL *kel, |
| l_int32 outdepth, |
| l_int32 normflag) |
| { |
| l_int32 i, j, k, m, w, h, d, 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); |
| |
| 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) |
| return (PIX *)ERROR_PTR("pixt not made", procName, NULL); |
| |
| pixd = pixCreate(w, h, outdepth); |
| datat = pixGetData(pixt); |
| datad = pixGetData(pixd); |
| wplt = pixGetWpl(pixt); |
| wpld = pixGetWpl(pixd); |
| for (i = 0; i < h; i++) { |
| lined = datad + i * wpld; |
| for (j = 0; j < w; j++) { |
| 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 1 |
| if (sum < 0.0) sum = -sum; /* make it non-negative */ |
| #endif |
| if (outdepth == 8) |
| SET_DATA_BYTE(lined, j, (l_int32)(sum + 0.5)); |
| else if (outdepth == 16) |
| SET_DATA_TWO_BYTES(lined, j, (l_int32)(sum + 0.5)); |
| else /* outdepth == 32 */ |
| *(lined + j) = (l_uint32)(sum + 0.5); |
| } |
| } |
| |
| kernelDestroy(&keli); |
| kernelDestroy(&keln); |
| pixDestroy(&pixt); |
| return pixd; |
| } |
| |
| |
| /*! |
| * pixConvolveSep() |
| * |
| * Input: pixs (8 bpp) |
| * kelx (x-dependent kernel) |
| * kely (y-dependent kernel) |
| * outdepth (of pixd: 8, 16 or 32) |
| * normflag (1 to normalize kernel to unit sum; 0 otherwise) |
| * Return: pixd (8, 16 or 32 bpp) |
| * |
| * 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 @outdepth and @normflag parameters are used as in |
| * pixConvolve(). |
| * (3) 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) 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) This uses mirrored borders to avoid special casing on |
| * the boundaries. |
| */ |
| PIX * |
| pixConvolveSep(PIX *pixs, |
| L_KERNEL *kelx, |
| L_KERNEL *kely, |
| l_int32 outdepth, |
| l_int32 normflag) |
| { |
| l_int32 w, h, d; |
| L_KERNEL *kelxn, *kelyn; |
| PIX *pixt, *pixd; |
| |
| PROCNAME("pixConvolveSep"); |
| |
| if (!pixs) |
| return (PIX *)ERROR_PTR("pixs not defined", 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 (!kelx) |
| return (PIX *)ERROR_PTR("kelx not defined", procName, NULL); |
| if (!kely) |
| return (PIX *)ERROR_PTR("kely not defined", procName, NULL); |
| |
| if (normflag) { |
| kelxn = kernelNormalize(kelx, 1000.0); |
| kelyn = kernelNormalize(kely, 0.001); |
| pixt = pixConvolve(pixs, kelxn, 32, 0); |
| pixd = pixConvolve(pixt, kelyn, outdepth, 0); |
| kernelDestroy(&kelxn); |
| kernelDestroy(&kelyn); |
| } |
| else { /* don't normalize */ |
| pixt = pixConvolve(pixs, kelx, 32, 0); |
| pixd = pixConvolve(pixt, kely, outdepth, 0); |
| } |
| |
| pixDestroy(&pixt); |
| return pixd; |
| } |
| |
| |
| /*----------------------------------------------------------------------* |
| * Generic convolution with float array * |
| *----------------------------------------------------------------------*/ |
| /*! |
| * fpixConvolve() |
| * |
| * Input: fpixs (32 bit float array) |
| * kernel |
| * normflag (1 to normalize kernel to unit sum; 0 otherwise) |
| * Return: fpixd (32 bit float array) |
| * |
| * 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. Do not normalize if |
| * the kernel has null sum, such as a DoG. |
| * (3) With the FPix, there are no issues about negative |
| * array or kernel values. The convolution is performed |
| * with single precision arithmetic. |
| * (4) This uses a mirrored border to avoid special casing on |
| * the boundaries. |
| */ |
| FPIX * |
| fpixConvolve(FPIX *fpixs, |
| L_KERNEL *kel, |
| l_int32 normflag) |
| { |
| l_int32 i, j, k, m, w, h, 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); |
| |
| 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) |
| return (FPIX *)ERROR_PTR("fpixt not made", procName, NULL); |
| |
| fpixd = fpixCreate(w, h); |
| datat = fpixGetData(fpixt); |
| datad = fpixGetData(fpixd); |
| wplt = fpixGetWpl(fpixt); |
| wpld = fpixGetWpl(fpixd); |
| for (i = 0; i < h; i++) { |
| lined = datad + i * wpld; |
| for (j = 0; j < w; j++) { |
| 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 + j) = sum; |
| } |
| } |
| |
| kernelDestroy(&keli); |
| kernelDestroy(&keln); |
| fpixDestroy(&fpixt); |
| return fpixd; |
| } |
| |
| |
| /*! |
| * fpixConvolveSep() |
| * |
| * Input: fpixs (32 bit float array) |
| * kelx (x-dependent kernel) |
| * kely (y-dependent kernel) |
| * normflag (1 to normalize kernel to unit sum; 0 otherwise) |
| * Return: fpixd (32 bit float array) |
| * |
| * 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) This uses mirrored borders to avoid special casing on |
| * the boundaries. |
| */ |
| FPIX * |
| fpixConvolveSep(FPIX *fpixs, |
| L_KERNEL *kelx, |
| L_KERNEL *kely, |
| l_int32 normflag) |
| { |
| 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); |
| |
| if (normflag) { |
| kelxn = kernelNormalize(kelx, 1.0); |
| kelyn = kernelNormalize(kely, 1.0); |
| fpixt = fpixConvolve(fpixs, kelxn, 0); |
| fpixd = fpixConvolve(fpixt, kelyn, 0); |
| kernelDestroy(&kelxn); |
| kernelDestroy(&kelyn); |
| } |
| else { /* don't normalize */ |
| fpixt = fpixConvolve(fpixs, kelx, 0); |
| fpixd = fpixConvolve(fpixt, kely, 0); |
| } |
| |
| fpixDestroy(&fpixt); |
| return fpixd; |
| } |
| |
| |
| |