| /*====================================================================* |
| - 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. |
| *====================================================================*/ |
| |
| /* |
| * rank.c |
| * |
| * Rank filter (gray and rgb) |
| * PIX *pixRankFilter() |
| * PIX *pixRankFilterRGB() |
| * PIX *pixRankFilterGray() |
| * |
| * Median filter |
| * PIX *pixMedianFilter() |
| * |
| * What is a brick rank filter? |
| * |
| * A brick rank order filter evaluates, for every pixel in the image, |
| * a rectangular set of n = wf x hf pixels in its neighborhood (where the |
| * pixel in question is at the "center" of the rectangle and is |
| * included in the evaluation). It determines the value of the |
| * neighboring pixel that is the r-th smallest in the set, |
| * where r is some integer between 1 and n. The input rank parameter |
| * is a fraction between 0.0 and 1.0, where 0.0 represents the |
| * smallest value (r = 1) and 1.0 represents the largest value (r = n). |
| * A median filter is a rank filter where rank = 0.5. |
| * |
| * It is important to note that grayscale erosion is equivalent |
| * to rank = 0.0, and grayscale dilation is equivalent to rank = 1.0. |
| * These are much easier to calculate than the general rank value, |
| * thanks to the van Herk/Gil-Werman algorithm: |
| * http://www.leptonica.com/grayscale-morphology.html |
| * so you should use pixErodeGray() and pixDilateGray() for |
| * rank 0.0 and 1.0, rsp. See notes below in the function header. |
| * |
| * How is a rank filter implemented efficiently on an image? |
| * |
| * Sorting will not work. |
| * |
| * * The best sort algorithms are O(n*logn), where n is the number |
| * of values to be sorted (the area of the filter). For large |
| * filters this is an impractically large number. |
| * |
| * * Selection of the rank value is O(n). (To understand why it's not |
| * O(n*logn), see Numerical Recipes in C, 2nd edition, 1992, p. 355ff). |
| * This also still far too much computation for large filters. |
| * |
| * * Suppose we get clever. We really only need to do an incremental |
| * selection or sorting, because, for example, moving the filter |
| * down by one pixel causes one filter width of pixels to be added |
| * and another to be removed. Can we do this incrementally in |
| * an efficient way? Unfortunately, no. The sorted values will be |
| * in an array. Even if the filter width is 1, we can expect to |
| * have to move O(n) pixels, because insertion and deletion can happen |
| * anywhere in the array. By comparison, heapsort is excellent for |
| * incremental sorting, where the cost for insertion or deletion |
| * is O(logn), because the array itself doesn't need to |
| * be sorted into strictly increasing order. However, heapsort |
| * only gives the max (or min) value, not the general rank value. |
| * |
| * This leaves histograms. |
| * |
| * * Represented as an array. The problem with an array of 256 |
| * bins is that, in general, a significant fraction of the |
| * entire histogram must be summed to find the rank value bin. |
| * Suppose the filter size is 5x5. You spend most of your time |
| * adding zeroes. Ouch! |
| * |
| * * Represented as a linked list. This would overcome the |
| * summing-over-empty-bin problem, but you lose random access |
| * for insertions and deletions. No way. |
| * |
| * * Two histogram solution. Maintain two histograms with |
| * bin sizes of 1 and 16. Proceed from coarse to fine. |
| * First locate the coarse bin for the given rank, of which |
| * there are only 16. Then, in the 256 entry (fine) histogram, |
| * you need look at a maximum of 16 bins. For each output |
| * pixel, the average number of bins summed over, both in the |
| * coarse and fine histograms, is thus 16. |
| * |
| * If someone has a better method, please let me know! |
| */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include "allheaders.h" |
| |
| |
| /*----------------------------------------------------------------------* |
| * Rank order filter * |
| *----------------------------------------------------------------------*/ |
| /*! |
| * pixRankFilter() |
| * |
| * Input: pixs (8 or 32 bpp; no colormap) |
| * wf, hf (width and height of filter; each is >= 1) |
| * rank (in [0.0 ... 1.0]) |
| * Return: pixd (of rank values), or null on error |
| * |
| * Notes: |
| * (1) This defines, for each pixel in pixs, a neighborhood of |
| * pixels given by a rectangle "centered" on the pixel. |
| * This set of wf*hf pixels has a distribution of values. |
| * For each component, if the values are sorted in increasing |
| * order, we choose the component such that rank*(wf*hf-1) |
| * pixels have a lower or equal value and |
| * (1-rank)*(wf*hf-1) pixels have an equal or greater value. |
| * (2) See notes in pixRankFilterGray() for further details. |
| */ |
| PIX * |
| pixRankFilter(PIX *pixs, |
| l_int32 wf, |
| l_int32 hf, |
| l_float32 rank) |
| { |
| l_int32 d; |
| |
| PROCNAME("pixRankFilter"); |
| |
| if (!pixs) |
| return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); |
| if (pixGetColormap(pixs) != NULL) |
| return (PIX *)ERROR_PTR("pixs has colormap", procName, NULL); |
| d = pixGetDepth(pixs); |
| if (d != 8 && d != 32) |
| return (PIX *)ERROR_PTR("pixs not 8 or 32 bpp", procName, NULL); |
| if (wf < 1 || hf < 1) |
| return (PIX *)ERROR_PTR("wf < 1 || hf < 1", procName, NULL); |
| if (rank < 0.0 || rank > 1.0) |
| return (PIX *)ERROR_PTR("rank must be in [0.0, 1.0]", procName, NULL); |
| if (wf == 1 && hf == 1) /* no-op */ |
| return pixCopy(NULL, pixs); |
| |
| if (d == 8) |
| return pixRankFilterGray(pixs, wf, hf, rank); |
| else /* d == 32 */ |
| return pixRankFilterRGB(pixs, wf, hf, rank); |
| } |
| |
| |
| /*! |
| * pixRankFilterRGB() |
| * |
| * Input: pixs (32 bpp) |
| * wf, hf (width and height of filter; each is >= 1) |
| * rank (in [0.0 ... 1.0]) |
| * Return: pixd (of rank values), or null on error |
| * |
| * Notes: |
| * (1) This defines, for each pixel in pixs, a neighborhood of |
| * pixels given by a rectangle "centered" on the pixel. |
| * This set of wf*hf pixels has a distribution of values. |
| * For each component, if the values are sorted in increasing |
| * order, we choose the component such that rank*(wf*hf-1) |
| * pixels have a lower or equal value and |
| * (1-rank)*(wf*hf-1) pixels have an equal or greater value. |
| * (2) Apply gray rank filtering to each component independently. |
| * (3) See notes in pixRankFilterGray() for further details. |
| */ |
| PIX * |
| pixRankFilterRGB(PIX *pixs, |
| l_int32 wf, |
| l_int32 hf, |
| l_float32 rank) |
| { |
| PIX *pixr, *pixg, *pixb, *pixrf, *pixgf, *pixbf, *pixd; |
| |
| PROCNAME("pixRankFilterRGB"); |
| |
| if (!pixs) |
| return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); |
| if (pixGetDepth(pixs) != 32) |
| return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL); |
| if (wf < 1 || hf < 1) |
| return (PIX *)ERROR_PTR("wf < 1 || hf < 1", procName, NULL); |
| if (rank < 0.0 || rank > 1.0) |
| return (PIX *)ERROR_PTR("rank must be in [0.0, 1.0]", procName, NULL); |
| if (wf == 1 && hf == 1) /* no-op */ |
| return pixCopy(NULL, pixs); |
| |
| pixr = pixGetRGBComponent(pixs, COLOR_RED); |
| pixg = pixGetRGBComponent(pixs, COLOR_GREEN); |
| pixb = pixGetRGBComponent(pixs, COLOR_BLUE); |
| |
| pixrf = pixRankFilterGray(pixr, wf, hf, rank); |
| pixgf = pixRankFilterGray(pixg, wf, hf, rank); |
| pixbf = pixRankFilterGray(pixb, wf, hf, rank); |
| |
| pixd = pixCreateRGBImage(pixrf, pixgf, pixbf); |
| pixDestroy(&pixr); |
| pixDestroy(&pixg); |
| pixDestroy(&pixb); |
| pixDestroy(&pixrf); |
| pixDestroy(&pixgf); |
| pixDestroy(&pixbf); |
| return pixd; |
| } |
| |
| |
| /*! |
| * pixRankFilterGray() |
| * |
| * Input: pixs (8 bpp; no colormap) |
| * wf, hf (width and height of filter; each is >= 1) |
| * rank (in [0.0 ... 1.0]) |
| * Return: pixd (of rank values), or null on error |
| * |
| * Notes: |
| * (1) This defines, for each pixel in pixs, a neighborhood of |
| * pixels given by a rectangle "centered" on the pixel. |
| * This set of wf*hf pixels has a distribution of values, |
| * and if they are sorted in increasing order, we choose |
| * the pixel such that rank*(wf*hf-1) pixels have a lower |
| * or equal value and (1-rank)*(wf*hf-1) pixels have an equal |
| * or greater value. |
| * (2) By this definition, the rank = 0.0 pixel has the lowest |
| * value, and the rank = 1.0 pixel has the highest value. |
| * (3) We add mirrored boundary pixels to avoid boundary effects, |
| * and put the filter center at (0, 0). |
| * (4) This dispatches to grayscale erosion or dilation if the |
| * filter dimensions are odd and the rank is 0.0 or 1.0, rsp. |
| * (5) Returns a copy if both wf and hf are 1. |
| * (6) Uses row-major or column-major incremental updates to the |
| * histograms depending on whether hf > wf or hv <= wf, rsp. |
| */ |
| PIX * |
| pixRankFilterGray(PIX *pixs, |
| l_int32 wf, |
| l_int32 hf, |
| l_float32 rank) |
| { |
| l_int32 w, h, d, i, j, k, m, n, rankloc, wplt, wpld, val, sum; |
| l_int32 *histo, *histo16; |
| l_uint32 *datat, *linet, *datad, *lined; |
| PIX *pixt, *pixd; |
| |
| PROCNAME("pixRankFilterGray"); |
| |
| if (!pixs) |
| return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); |
| if (pixGetColormap(pixs) != NULL) |
| return (PIX *)ERROR_PTR("pixs has colormap", procName, NULL); |
| pixGetDimensions(pixs, &w, &h, &d); |
| if (d != 8) |
| return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL); |
| if (wf < 1 || hf < 1) |
| return (PIX *)ERROR_PTR("wf < 1 || hf < 1", procName, NULL); |
| if (rank < 0.0 || rank > 1.0) |
| return (PIX *)ERROR_PTR("rank must be in [0.0, 1.0]", procName, NULL); |
| if (wf == 1 && hf == 1) /* no-op */ |
| return pixCopy(NULL, pixs); |
| |
| /* For rank = 0.0, this is a grayscale erosion, and for rank = 1.0, |
| * a dilation. Grayscale morphology operations are implemented |
| * for filters of odd dimension, so we dispatch to grayscale |
| * morphology if both wf and hf are odd. Otherwise, we |
| * slightly adjust the rank (to get the correct behavior) and |
| * use the slower rank filter here. */ |
| if (wf % 2 && hf % 2) { |
| if (rank == 0.0) |
| return pixErodeGray(pixs, wf, hf); |
| else if (rank == 1.0) |
| return pixDilateGray(pixs, wf, hf); |
| } |
| if (rank == 0.0) rank = 0.0001; |
| if (rank == 1.0) rank = 0.9999; |
| |
| /* Add wf/2 to each side, and hf/2 to top and bottom of the |
| * image, mirroring for accuracy and to avoid special-casing |
| * the boundary. */ |
| if ((pixt = pixAddMirroredBorder(pixs, wf / 2, wf / 2, hf / 2, hf / 2)) |
| == NULL) |
| return (PIX *)ERROR_PTR("pixt not made", procName, NULL); |
| |
| /* Set up the two histogram arrays. */ |
| histo = (l_int32 *)CALLOC(256, sizeof(l_int32)); |
| histo16 = (l_int32 *)CALLOC(16, sizeof(l_int32)); |
| rankloc = (l_int32)(rank * wf * hf); |
| |
| /* Place the filter center at (0, 0). This is just a |
| * convenient location, because it allows us to perform |
| * the rank filter over x:(0 ... w - 1) and y:(0 ... h - 1). */ |
| pixd = pixCreateTemplate(pixs); |
| datat = pixGetData(pixt); |
| wplt = pixGetWpl(pixt); |
| datad = pixGetData(pixd); |
| wpld = pixGetWpl(pixd); |
| |
| /* If hf > wf, it's more efficient to use row-major scanning. |
| * Otherwise, traverse the image in use column-major order. */ |
| if (hf > wf) { |
| for (j = 0; j < w; j++) { /* row-major */ |
| /* Start each column with clean histogram arrays. */ |
| for (n = 0; n < 256; n++) |
| histo[n] = 0; |
| for (n = 0; n < 16; n++) |
| histo16[n] = 0; |
| |
| for (i = 0; i < h; i++) { /* fast scan on columns */ |
| /* Update the histos for the new location */ |
| lined = datad + i * wpld; |
| if (i == 0) { /* do full histo */ |
| for (k = 0; k < hf; k++) { |
| linet = datat + (i + k) * wplt; |
| for (m = 0; m < wf; m++) { |
| val = GET_DATA_BYTE(linet, j + m); |
| histo[val]++; |
| histo16[val >> 4]++; |
| } |
| } |
| } else { /* incremental update */ |
| linet = datat + (i - 1) * wplt; |
| for (m = 0; m < wf; m++) { /* remove top line */ |
| val = GET_DATA_BYTE(linet, j + m); |
| histo[val]--; |
| histo16[val >> 4]--; |
| } |
| linet = datat + (i + hf - 1) * wplt; |
| for (m = 0; m < wf; m++) { /* add bottom line */ |
| val = GET_DATA_BYTE(linet, j + m); |
| histo[val]++; |
| histo16[val >> 4]++; |
| } |
| } |
| |
| /* Find the rank value */ |
| sum = 0; |
| for (n = 0; n < 16; n++) { /* search over coarse histo */ |
| sum += histo16[n]; |
| if (sum > rankloc) { |
| sum -= histo16[n]; |
| break; |
| } |
| } |
| k = 16 * n; /* starting value in fine histo */ |
| for (m = 0; m < 16; m++) { |
| sum += histo[k]; |
| if (sum > rankloc) { |
| SET_DATA_BYTE(lined, j, k); |
| break; |
| } |
| k++; |
| } |
| } |
| } |
| } else { /* wf >= hf */ |
| for (i = 0; i < h; i++) { /* column-major */ |
| /* Start each row with clean histogram arrays. */ |
| for (n = 0; n < 256; n++) |
| histo[n] = 0; |
| for (n = 0; n < 16; n++) |
| histo16[n] = 0; |
| lined = datad + i * wpld; |
| for (j = 0; j < w; j++) { /* fast scan on rows */ |
| /* Update the histos for the new location */ |
| if (j == 0) { /* do full histo */ |
| for (k = 0; k < hf; k++) { |
| linet = datat + (i + k) * wplt; |
| for (m = 0; m < wf; m++) { |
| val = GET_DATA_BYTE(linet, j + m); |
| histo[val]++; |
| histo16[val >> 4]++; |
| } |
| } |
| } else { /* incremental update at left and right sides */ |
| for (k = 0; k < hf; k++) { |
| linet = datat + (i + k) * wplt; |
| val = GET_DATA_BYTE(linet, j - 1); |
| histo[val]--; |
| histo16[val >> 4]--; |
| val = GET_DATA_BYTE(linet, j + wf - 1); |
| histo[val]++; |
| histo16[val >> 4]++; |
| } |
| } |
| |
| /* Find the rank value */ |
| sum = 0; |
| for (n = 0; n < 16; n++) { /* search over coarse histo */ |
| sum += histo16[n]; |
| if (sum > rankloc) { |
| sum -= histo16[n]; |
| break; |
| } |
| } |
| k = 16 * n; /* starting value in fine histo */ |
| for (m = 0; m < 16; m++) { |
| sum += histo[k]; |
| if (sum > rankloc) { |
| SET_DATA_BYTE(lined, j, k); |
| break; |
| } |
| k++; |
| } |
| } |
| } |
| } |
| |
| pixDestroy(&pixt); |
| FREE(histo); |
| FREE(histo16); |
| return pixd; |
| } |
| |
| |
| /*----------------------------------------------------------------------* |
| * Median filter * |
| *----------------------------------------------------------------------*/ |
| /*! |
| * pixMedianFilter() |
| * |
| * Input: pixs (8 or 32 bpp; no colormap) |
| * wf, hf (width and height of filter; each is >= 1) |
| * Return: pixd (of median values), or null on error |
| */ |
| PIX * |
| pixMedianFilter(PIX *pixs, |
| l_int32 wf, |
| l_int32 hf) |
| { |
| PROCNAME("pixMedianFilter"); |
| |
| if (!pixs) |
| return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); |
| return pixRankFilter(pixs, wf, hf, 0.5); |
| } |
| |
| |