Esempio n. 1
0
// Returns the maximum strokewidth in the given binary image by doubling
// the maximum of the distance function.
static int MaxStrokeWidth(Pix* pix) {
  Pix* dist_pix = pixDistanceFunction(pix, 4, 8, L_BOUNDARY_BG);
  int width = pixGetWidth(dist_pix);
  int height = pixGetHeight(dist_pix);
  int wpl = pixGetWpl(dist_pix);
  l_uint32* data = pixGetData(dist_pix);
  // Find the maximum value in the distance image.
  int max_dist = 0;
  for (int y = 0; y < height; ++y) {
    for (int x = 0; x < width; ++x) {
      int pixel = GET_DATA_BYTE(data, x);
      if (pixel > max_dist)
        max_dist = pixel;
    }
    data += wpl;
  }
  pixDestroy(&dist_pix);
  return max_dist * 2;
}
Esempio n. 2
0
/*!
 * \brief   pixFindStrokeWidth()
 *
 * \param[in]    pixs 1 bpp
 * \param[in]    thresh  fractional count threshold relative to distance 1
 * \param[in]    tab8  [optional] table for counting fg pixels; can be NULL
 * \param[out]  *pwidth  estimated width of the strokes
 * \param[out]  *pnahisto  [optional] histo of pixel distances from bg
 * \return  0 if OK, 1 on error
 *
 * <pre>
 * Notes:
 *      (1) This uses two methods to estimate the stroke width:
 *          (a) half the fg boundary length
 *          (b) a value derived from the histogram of the fg distance transform
 *      (2) Distance is measured in 8-connected
 *      (3) %thresh is the minimum fraction N(dist=d)/N(dist=1) of pixels
 *          required to determine if the pixels at distance d are above
 *          the noise. It is typically about 0.15.
 * </pre>
 */
l_int32
pixFindStrokeWidth(PIX        *pixs,
                   l_float32   thresh,
                   l_int32    *tab8,
                   l_float32  *pwidth,
                   NUMA      **pnahisto)
{
l_int32     i, n, count, length, first, last;
l_int32    *tab;
l_float32   width1, width2, ratio, extra;
l_float32  *fa;
NUMA       *na1, *na2;
PIX        *pix1;

    PROCNAME("pixFindStrokeWidth");

    if (!pwidth)
        return ERROR_INT("&width not defined", procName, 1);
    *pwidth = 0;
    if (!pixs)
        return ERROR_INT("pixs not defined", procName, 1);

    tab = (tab8) ? tab8 : makePixelSumTab8();

    /* ------- Method 1: via boundary length ------- */
        /* The computed stroke length is a bit larger than that actual
         * length, because of the addition of the 'caps' at the
         * stroke ends.  Therefore the computed width is a bit
         * smaller than the average width. */
    pixFindStrokeLength(pixs, tab8, &length);
    pixCountPixels(pixs, &count, tab8);
    width1 = (l_float32)count / (l_float32)length;

    /* ------- Method 2: via distance transform ------- */
        /* First get the histogram of distances */
    pix1 = pixDistanceFunction(pixs, 8, 8, L_BOUNDARY_BG);
    na1 = pixGetGrayHistogram(pix1, 1);
    pixDestroy(&pix1);
    numaGetNonzeroRange(na1, 0.1, &first, &last);
    na2 = numaClipToInterval(na1, 0, last);
    numaWriteStream(stderr, na2);

        /* Find the bucket with the largest distance whose contents
         * exceed the threshold. */
    fa = numaGetFArray(na2, L_NOCOPY);
    n = numaGetCount(na2);
    for (i = n - 1; i > 0; i--) {
        ratio = fa[i] / fa[1];
        if (ratio > thresh) break;
    }
        /* Let the last skipped bucket contribute to the stop bucket.
         * This is the 'extra' term below.  The result may be a slight
         * over-correction, so the computed width may be a bit larger
         * than the average width. */
    extra = (i < n - 1) ? fa[i + 1] / fa[1] : 0;
    width2 = 2.0 * (i - 1.0 + ratio + extra);
    fprintf(stderr, "width1 = %5.2f, width2 = %5.2f\n", width1, width2);

        /* Average the two results */
    *pwidth = (width1 + width2) / 2.0;

    if (!tab8) LEPT_FREE(tab);
    numaDestroy(&na1);
    if (pnahisto)
        *pnahisto = na2;
    else
        numaDestroy(&na2);
    return 0;
}
static void
TestDistance(PIXA         *pixa,
             PIX          *pixs,
             l_int32       conn,
             l_int32       depth,
             l_int32       bc,
             l_int32      *pcount,
             L_REGPARAMS  *rp)
{
PIX  *pixt1, *pixt2, *pixt3, *pixt4, *pixt5;

        /* Test the distance function and display */
    pixInvert(pixs, pixs);
    pixt1 = pixDistanceFunction(pixs, conn, depth, bc);
    regTestWritePixAndCheck(pixt1, IFF_PNG, pcount, rp);
    pixSaveTiled(pixt1, pixa, 1, 1, 20, 0);
    pixInvert(pixs, pixs);
    pixt2 = pixMaxDynamicRange(pixt1, L_LOG_SCALE);
    regTestWritePixAndCheck(pixt2, IFF_JFIF_JPEG, pcount, rp);
    pixSaveTiled(pixt2, pixa, 1, 0, 20, 0);
    pixDestroy(&pixt1);
    pixDestroy(&pixt2);

	/* Test the distance function and display with contour rendering */
    pixInvert(pixs, pixs);
    pixt1 = pixDistanceFunction(pixs, conn, depth, bc);
    regTestWritePixAndCheck(pixt1, IFF_PNG, pcount, rp);
    pixSaveTiled(pixt1, pixa, 1, 1, 20, 0);
    pixInvert(pixs, pixs);
    pixt2 = pixRenderContours(pixt1, 2, 4, 1);  /* binary output */
    regTestWritePixAndCheck(pixt2, IFF_PNG, pcount, rp);
    pixSaveTiled(pixt2, pixa, 1, 0, 20, 0);
    pixt3 = pixRenderContours(pixt1, 2, 4, depth);
    pixt4 = pixMaxDynamicRange(pixt3, L_LINEAR_SCALE);
    regTestWritePixAndCheck(pixt4, IFF_JFIF_JPEG, pcount, rp);
    pixSaveTiled(pixt4, pixa, 1, 0, 20, 0);
    pixt5 = pixMaxDynamicRange(pixt3, L_LOG_SCALE);
    regTestWritePixAndCheck(pixt5, IFF_JFIF_JPEG, pcount, rp);
    pixSaveTiled(pixt5, pixa, 1, 0, 20, 0);
    pixDestroy(&pixt1);
    pixDestroy(&pixt2);
    pixDestroy(&pixt3);
    pixDestroy(&pixt4);
    pixDestroy(&pixt5);

	/* Label all pixels in each c.c. with a color equal to the
         * max distance of any pixel within that c.c. from the bg.
         * Note that we've normalized so the dynamic range extends
         * to 255.  For the image here, each unit of distance is
         * represented by about 21 grayscale units.  The largest
         * distance is 12.  */
    if (depth == 8) {
        pixt1 = pixDistanceFunction(pixs, conn, depth, bc);
        pixt4 = pixMaxDynamicRange(pixt1, L_LOG_SCALE);
        regTestWritePixAndCheck(pixt4, IFF_JFIF_JPEG, pcount, rp);
        pixSaveTiled(pixt4, pixa, 1, 1, 20, 0);
        pixt2 = pixCreateTemplate(pixt1);
        pixSetMasked(pixt2, pixs, 255);
        regTestWritePixAndCheck(pixt2, IFF_JFIF_JPEG, pcount, rp);
        pixSaveTiled(pixt2, pixa, 1, 0, 20, 0);
        pixSeedfillGray(pixt1, pixt2, 4);
        pixt3 = pixMaxDynamicRange(pixt1, L_LINEAR_SCALE);
        regTestWritePixAndCheck(pixt3, IFF_JFIF_JPEG, pcount, rp);
        pixSaveTiled(pixt3, pixa, 1, 0, 20, 0);
        pixDestroy(&pixt1);
        pixDestroy(&pixt2);
        pixDestroy(&pixt3);
        pixDestroy(&pixt4);
    }

    return;
}
Esempio n. 4
0
/**********************************************************************
 * SetBlobStrokeWidth
 *
 * Set the horizontal and vertical stroke widths in the blob.
 **********************************************************************/
void SetBlobStrokeWidth(Pix* pix, BLOBNBOX* blob) {
  // Cut the blob rectangle into a Pix.
  int pix_height = pixGetHeight(pix);
  const TBOX& box = blob->bounding_box();
  int width = box.width();
  int height = box.height();
  Box* blob_pix_box = boxCreate(box.left(), pix_height - box.top(),
                                width, height);
  Pix* pix_blob = pixClipRectangle(pix, blob_pix_box, nullptr);
  boxDestroy(&blob_pix_box);
  Pix* dist_pix = pixDistanceFunction(pix_blob, 4, 8, L_BOUNDARY_BG);
  pixDestroy(&pix_blob);
  // Compute the stroke widths.
  uint32_t* data = pixGetData(dist_pix);
  int wpl = pixGetWpl(dist_pix);
  // Horizontal width of stroke.
  STATS h_stats(0, width + 1);
  for (int y = 0; y < height; ++y) {
    uint32_t* pixels = data + y*wpl;
    int prev_pixel = 0;
    int pixel = GET_DATA_BYTE(pixels, 0);
    for (int x = 1; x < width; ++x) {
      int next_pixel = GET_DATA_BYTE(pixels, x);
      // We are looking for a pixel that is equal to its vertical neighbours,
      // yet greater than its left neighbour.
      if (prev_pixel < pixel &&
          (y == 0 || pixel == GET_DATA_BYTE(pixels - wpl, x - 1)) &&
          (y == height - 1 || pixel == GET_DATA_BYTE(pixels + wpl, x - 1))) {
        if (pixel > next_pixel) {
          // Single local max, so an odd width.
          h_stats.add(pixel * 2 - 1, 1);
        } else if (pixel == next_pixel && x + 1 < width &&
                 pixel > GET_DATA_BYTE(pixels, x + 1)) {
          // Double local max, so an even width.
          h_stats.add(pixel * 2, 1);
        }
      }
      prev_pixel = pixel;
      pixel = next_pixel;
    }
  }
  // Vertical width of stroke.
  STATS v_stats(0, height + 1);
  for (int x = 0; x < width; ++x) {
    int prev_pixel = 0;
    int pixel = GET_DATA_BYTE(data, x);
    for (int y = 1; y < height; ++y) {
      uint32_t* pixels = data + y*wpl;
      int next_pixel = GET_DATA_BYTE(pixels, x);
      // We are looking for a pixel that is equal to its horizontal neighbours,
      // yet greater than its upper neighbour.
      if (prev_pixel < pixel &&
          (x == 0 || pixel == GET_DATA_BYTE(pixels - wpl, x - 1)) &&
          (x == width - 1 || pixel == GET_DATA_BYTE(pixels - wpl, x + 1))) {
        if (pixel > next_pixel) {
          // Single local max, so an odd width.
          v_stats.add(pixel * 2 - 1, 1);
        } else if (pixel == next_pixel && y + 1 < height &&
                 pixel > GET_DATA_BYTE(pixels + wpl, x)) {
          // Double local max, so an even width.
          v_stats.add(pixel * 2, 1);
        }
      }
      prev_pixel = pixel;
      pixel = next_pixel;
    }
  }
  pixDestroy(&dist_pix);
  // Store the horizontal and vertical width in the blob, keeping both
  // widths if there is enough information, otherwse only the one with
  // the most samples.
  // If there are insufficient samples, store zero, rather than using
  // 2*area/perimeter, as the numbers that gives do not match the numbers
  // from the distance method.
  if (h_stats.get_total() >= (width + height) / 4) {
    blob->set_horz_stroke_width(h_stats.ile(0.5f));
    if (v_stats.get_total() >= (width + height) / 4)
      blob->set_vert_stroke_width(v_stats.ile(0.5f));
    else
      blob->set_vert_stroke_width(0.0f);
  } else {
    if (v_stats.get_total() >= (width + height) / 4 ||
        v_stats.get_total() > h_stats.get_total()) {
      blob->set_horz_stroke_width(0.0f);
      blob->set_vert_stroke_width(v_stats.ile(0.5f));
    } else {
      blob->set_horz_stroke_width(h_stats.get_total() > 2 ? h_stats.ile(0.5f)
                                                          : 0.0f);
      blob->set_vert_stroke_width(0.0f);
    }
  }
}
Esempio n. 5
0
/**********************************************************************
 * SetBlobStrokeWidth
 *
 * Set the horizontal and vertical stroke widths in the blob.
 **********************************************************************/
void SetBlobStrokeWidth(bool debug, BLOBNBOX* blob) {
#ifdef HAVE_LIBLEPT
  // Cut the blob rectangle into a Pix.
  // TODO(rays) make the page_image a Pix so this is more direct.
  const TBOX& box = blob->bounding_box();
  IMAGE blob_im;
  int width = box.width();
  int height = box.height();
  blob_im.create(width, height, 1);
  copy_sub_image(&page_image, box.left(), box.bottom(), width, height,
                 &blob_im, 0, 0, false);
  Pix* pix = blob_im.ToPix();
  Pix* dist_pix = pixDistanceFunction(pix, 4, 8, L_BOUNDARY_BG);
  if (debug) {
    pixWrite("cutpix.png", pix, IFF_PNG);
    pixWrite("distpix.png", dist_pix, IFF_PNG);
  }
  pixDestroy(&pix);
  // Compute the stroke widths.
  uinT32* data = pixGetData(dist_pix);
  int wpl = pixGetWpl(dist_pix);
  // Horizontal width of stroke.
  STATS h_stats(0, width + 1);
  for (int y = 0; y < height; ++y) {
    uinT32* pixels = data + y*wpl;
    int prev_pixel = 0;
    int pixel = GET_DATA_BYTE(pixels, 0);
    for (int x = 1; x < width; ++x) {
      int next_pixel = GET_DATA_BYTE(pixels, x);
      // We are looking for a pixel that is equal to its vertical neighbours,
      // yet greater than its left neighbour.
      if (prev_pixel < pixel &&
          (y == 0 || pixel == GET_DATA_BYTE(pixels - wpl, x - 1)) &&
          (y == height - 1 || pixel == GET_DATA_BYTE(pixels + wpl, x - 1))) {
        if (pixel > next_pixel) {
          // Single local max, so an odd width.
          h_stats.add(pixel * 2 - 1, 1);
        } else if (pixel == next_pixel && x + 1 < width &&
                 pixel > GET_DATA_BYTE(pixels, x + 1)) {
          // Double local max, so an even width.
          h_stats.add(pixel * 2, 1);
        }
      }
      prev_pixel = pixel;
      pixel = next_pixel;
    }
  }
  if (debug) {
    h_stats.print(stderr, true);
  }
  // Vertical width of stroke.
  STATS v_stats(0, height + 1);
  for (int x = 0; x < width; ++x) {
    int prev_pixel = 0;
    int pixel = GET_DATA_BYTE(data, x);
    for (int y = 1; y < height; ++y) {
      uinT32* pixels = data + y*wpl;
      int next_pixel = GET_DATA_BYTE(pixels, x);
      // We are looking for a pixel that is equal to its horizontal neighbours,
      // yet greater than its upper neighbour.
      if (prev_pixel < pixel &&
          (x == 0 || pixel == GET_DATA_BYTE(pixels - wpl, x - 1)) &&
          (x == width - 1 || pixel == GET_DATA_BYTE(pixels - wpl, x + 1))) {
        if (pixel > next_pixel) {
          // Single local max, so an odd width.
          v_stats.add(pixel * 2 - 1, 1);
        } else if (pixel == next_pixel && y + 1 < height &&
                 pixel > GET_DATA_BYTE(pixels + wpl, x)) {
          // Double local max, so an even width.
          v_stats.add(pixel * 2, 1);
        }
      }
      prev_pixel = pixel;
      pixel = next_pixel;
    }
  }
  if (debug) {
    v_stats.print(stderr, true);
  }
  pixDestroy(&dist_pix);
  // Store the horizontal and vertical width in the blob, keeping both
  // widths if there is enough information, otherwse only the one with
  // the most samples.
  // If there are insufficent samples, store zero, rather than using
  // 2*area/perimeter, as the numbers that gives do not match the numbers
  // from the distance method.
  if (debug) {
    tprintf("box=%d,%d->%d,%d, hcount=%d, vcount=%d, target=%d\n",
            box.left(), box.bottom(), box.right(), box.top(),
            h_stats.get_total(), v_stats.get_total(), (width+height) /4);
    tprintf("hstats median=%f, lq=%f, uq=%f, sd=%f\n",
            h_stats.median(), h_stats.ile(0.25f), h_stats.ile(0.75f),
            h_stats.sd());
    tprintf("vstats median=%f, lq=%f, uq=%f, sd=%f\n",
            v_stats.median(), v_stats.ile(0.25f), v_stats.ile(0.75f),
            v_stats.sd());

  }
  if (h_stats.get_total() >= (width + height) / 4) {
    blob->set_horz_stroke_width(h_stats.ile(0.5f));
    if (v_stats.get_total() >= (width + height) / 4)
      blob->set_vert_stroke_width(v_stats.ile(0.5f));
    else
      blob->set_vert_stroke_width(0.0f);
  } else {
    if (v_stats.get_total() >= (width + height) / 4 ||
        v_stats.get_total() > h_stats.get_total()) {
      blob->set_horz_stroke_width(0.0f);
      blob->set_vert_stroke_width(v_stats.ile(0.5f));
    } else {
      blob->set_horz_stroke_width(h_stats.get_total() > 2 ? h_stats.ile(0.5f)
                                                          : 0.0f);
      blob->set_vert_stroke_width(0.0f);
    }
  }
#else
  // Without leptonica present, use the 2*area/perimeter as an approximation.
  float width = 2.0f * blob->cblob()->area();
  width /= blob->cblob()->perimeter();
  blob->set_horz_stroke_width(width);
  blob->set_vert_stroke_width(width);
#endif
}