tesseract/textord/imagefind.cpp

339 lines
14 KiB
C++

///////////////////////////////////////////////////////////////////////
// File: imagefind.cpp
// Description: Function to find image and drawing regions in an image
// and create a corresponding list of empty blobs.
// Author: Ray Smith
// Created: Thu Mar 20 09:49:01 PDT 2008
//
// (C) Copyright 2008, Google Inc.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
///////////////////////////////////////////////////////////////////////
#ifdef _MSC_VER
#pragma warning(disable:4244) // Conversion warnings
#endif
#include "imagefind.h"
#include "params.h"
// This entire file is dependent upon leptonica. If you don't have it,
// you don't get this functionality.
#ifdef HAVE_CONFIG_H
#include "config_auto.h"
#endif
#include "allheaders.h"
BOOL_VAR(textord_tabfind_show_images, false, "Show image blobs");
namespace tesseract {
// Fraction of width or height of on pixels that can be discarded from a
// roughly rectangular image.
const double kMinRectangularFraction = 0.125;
// Fraction of width or height to consider image completely used.
const double kMaxRectangularFraction = 0.75;
// Fraction of width or height to allow transition from kMinRectangularFraction
// to kMaxRectangularFraction, equivalent to a dy/dx skew.
const double kMaxRectangularGradient = 0.1; // About 6 degrees.
// Minimum image size to be worth looking for images on.
const int kMinImageFindSize = 100;
// Finds image regions within the source pix (page image) and returns
// the image regions as a Boxa, Pixa pair, analgous to pixConnComp.
// The returned boxa, pixa may be NULL, meaning no images found.
// If not NULL, they must be destroyed by the caller.
void ImageFinder::FindImages(Pix* pix, Boxa** boxa, Pixa** pixa) {
*boxa = NULL;
*pixa = NULL;
if (pixGetWidth(pix) < kMinImageFindSize ||
pixGetHeight(pix) < kMinImageFindSize)
return; // Not worth looking at small images.
// Reduce by factor 2.
Pix *pixr = pixReduceRankBinaryCascade(pix, 1, 0, 0, 0);
pixDisplayWrite(pixr, textord_tabfind_show_images);
// Get the halftone mask directly from Leptonica.
Pix *pixht2 = pixGenHalftoneMask(pixr, NULL, NULL,
textord_tabfind_show_images);
pixDestroy(&pixr);
if (pixht2 == NULL)
return;
// Expand back up again.
Pix *pixht = pixExpandReplicate(pixht2, 2);
pixDisplayWrite(pixht, textord_tabfind_show_images);
pixDestroy(&pixht2);
// Fill to capture pixels near the mask edges that were missed
Pix *pixt = pixSeedfillBinary(NULL, pixht, pix, 8);
pixOr(pixht, pixht, pixt);
pixDestroy(&pixt);
// Eliminate lines and bars that may be joined to images.
Pix* pixfinemask = pixReduceRankBinaryCascade(pixht, 1, 1, 3, 3);
pixDilateBrick(pixfinemask, pixfinemask, 5, 5);
pixDisplayWrite(pixfinemask, textord_tabfind_show_images);
Pix* pixreduced = pixReduceRankBinaryCascade(pixht, 1, 1, 1, 1);
Pix* pixreduced2 = pixReduceRankBinaryCascade(pixreduced, 3, 3, 3, 0);
pixDestroy(&pixreduced);
pixDilateBrick(pixreduced2, pixreduced2, 5, 5);
Pix* pixcoarsemask = pixExpandReplicate(pixreduced2, 8);
pixDestroy(&pixreduced2);
pixDisplayWrite(pixcoarsemask, textord_tabfind_show_images);
// Combine the coarse and fine image masks.
pixAnd(pixcoarsemask, pixcoarsemask, pixfinemask);
pixDestroy(&pixfinemask);
// Dilate a bit to make sure we get everything.
pixDilateBrick(pixcoarsemask, pixcoarsemask, 3, 3);
Pix* pixmask = pixExpandReplicate(pixcoarsemask, 16);
pixDestroy(&pixcoarsemask);
pixDisplayWrite(pixmask, textord_tabfind_show_images);
// And the image mask with the line and bar remover.
pixAnd(pixht, pixht, pixmask);
pixDestroy(&pixmask);
pixDisplayWrite(pixht, textord_tabfind_show_images);
// Find the individual image regions in the mask image.
*boxa = pixConnComp(pixht, pixa, 8);
pixDestroy(&pixht);
// Rectangularize the individual images. If a sharp edge in vertical and/or
// horizontal occupancy can be found, it indicates a probably rectangular
// image with unwanted bits merged on, so clip to the approximate rectangle.
int npixes = pixaGetCount(*pixa);
for (int i = 0; i < npixes; ++i) {
int x_start, x_end, y_start, y_end;
Pix* img_pix = pixaGetPix(*pixa, i, L_CLONE);
pixDisplayWrite(img_pix, textord_tabfind_show_images);
if (pixNearlyRectangular(img_pix, kMinRectangularFraction,
kMaxRectangularFraction,
kMaxRectangularGradient,
&x_start, &y_start, &x_end, &y_end)) {
// Add 1 to the size as a kludgy flag to indicate to the later stages
// of processing that it is a clipped rectangular image .
Pix* simple_pix = pixCreate(pixGetWidth(img_pix) + 1,
pixGetHeight(img_pix), 1);
pixDestroy(&img_pix);
pixRasterop(simple_pix, x_start, y_start, x_end - x_start,
y_end - y_start, PIX_SET, NULL, 0, 0);
// pixaReplacePix takes ownership of the simple_pix.
pixaReplacePix(*pixa, i, simple_pix, NULL);
img_pix = pixaGetPix(*pixa, i, L_CLONE);
}
// Subtract the pix from the correct location in the master image.
l_int32 x, y, width, height;
pixDisplayWrite(img_pix, textord_tabfind_show_images);
boxaGetBoxGeometry(*boxa, i, &x, &y, &width, &height);
pixRasterop(pix, x, y, width, height, PIX_NOT(PIX_SRC) & PIX_DST,
img_pix, 0, 0);
pixDestroy(&img_pix);
}
}
// Scans horizontally on x=[x_start,x_end), starting with y=*y_start,
// stepping y+=y_step, until y=y_end. *ystart is input/output.
// If the number of black pixels in a row, pix_count fits this pattern:
// 0 or more rows with pix_count < min_count then
// <= mid_width rows with min_count <= pix_count <= max_count then
// a row with pix_count > max_count then
// true is returned, and *y_start = the first y with pix_count >= min_count.
static bool HScanForEdge(uinT32* data, int wpl, int x_start, int x_end,
int min_count, int mid_width, int max_count,
int y_end, int y_step, int* y_start) {
int mid_rows = 0;
for (int y = *y_start; y != y_end; y += y_step) {
// Need pixCountPixelsInRow(pix, y, &pix_count, NULL) to count in a subset.
int pix_count = 0;
uinT32* line = data + wpl * y;
for (int x = x_start; x < x_end; ++x) {
if (GET_DATA_BIT(line, x))
++pix_count;
}
if (mid_rows == 0 && pix_count < min_count)
continue; // In the min phase.
if (mid_rows == 0)
*y_start = y; // Save the y_start where we came out of the min phase.
if (pix_count > max_count)
return true; // Found the pattern.
++mid_rows;
if (mid_rows > mid_width)
break; // Middle too big.
}
return false; // Never found max_count.
}
// Scans vertically on y=[y_start,y_end), starting with x=*x_start,
// stepping x+=x_step, until x=x_end. *x_start is input/output.
// If the number of black pixels in a column, pix_count fits this pattern:
// 0 or more cols with pix_count < min_count then
// <= mid_width cols with min_count <= pix_count <= max_count then
// a column with pix_count > max_count then
// true is returned, and *x_start = the first x with pix_count >= min_count.
static bool VScanForEdge(uinT32* data, int wpl, int y_start, int y_end,
int min_count, int mid_width, int max_count,
int x_end, int x_step, int* x_start) {
int mid_cols = 0;
for (int x = *x_start; x != x_end; x += x_step) {
int pix_count = 0;
uinT32* line = data + y_start * wpl;
for (int y = y_start; y < y_end; ++y, line += wpl) {
if (GET_DATA_BIT(line, x))
++pix_count;
}
if (mid_cols == 0 && pix_count < min_count)
continue; // In the min phase.
if (mid_cols == 0)
*x_start = x; // Save the place where we came out of the min phase.
if (pix_count > max_count)
return true; // found the pattern.
++mid_cols;
if (mid_cols > mid_width)
break; // Middle too big.
}
return false; // Never found max_count.
}
// Returns true if there is a rectangle in the source pix, such that all
// pixel rows and column slices outside of it have less than
// min_fraction of the pixels black, and within max_skew_gradient fraction
// of the pixels on the inside, there are at least max_fraction of the
// pixels black. In other words, the inside of the rectangle looks roughly
// rectangular, and the outside of it looks like extra bits.
// On return, the rectangle is defined by x_start, y_start, x_end and y_end.
// Note: the algorithm is iterative, allowing it to slice off pixels from
// one edge, allowing it to then slice off more pixels from another edge.
bool ImageFinder::pixNearlyRectangular(Pix* pix,
double min_fraction, double max_fraction,
double max_skew_gradient,
int* x_start, int* y_start,
int* x_end, int* y_end) {
*x_start = 0;
*x_end = pixGetWidth(pix);
*y_start = 0;
*y_end = pixGetHeight(pix);
uinT32* data = pixGetData(pix);
int wpl = pixGetWpl(pix);
bool any_cut = false;
bool left_done = false;
bool right_done = false;
bool top_done = false;
bool bottom_done = false;
do {
any_cut = false;
// Find the top/bottom edges.
int width = *x_end - *x_start;
int min_count = static_cast<int>(width * min_fraction);
int max_count = static_cast<int>(width * max_fraction);
int edge_width = static_cast<int>(width * max_skew_gradient);
if (!top_done && HScanForEdge(data, wpl, *x_start, *x_end,
min_count, edge_width, max_count,
*y_end, 1, y_start)) {
top_done = true;
any_cut = true;
}
--(*y_end);
if (!bottom_done && HScanForEdge(data, wpl, *x_start, *x_end,
min_count, edge_width, max_count,
*y_start, -1, y_end)) {
bottom_done = true;
any_cut = true;
}
++(*y_end);
// Find the left/right edges.
int height = *y_end - *y_start;
min_count = static_cast<int>(height * min_fraction);
max_count = static_cast<int>(height * max_fraction);
edge_width = static_cast<int>(height * max_skew_gradient);
if (!left_done && VScanForEdge(data, wpl, *y_start, *y_end,
min_count, edge_width, max_count,
*x_end, 1, x_start)) {
left_done = true;
any_cut = true;
}
--(*x_end);
if (!right_done && VScanForEdge(data, wpl, *y_start, *y_end,
min_count, edge_width, max_count,
*x_start, -1, x_end)) {
right_done = true;
any_cut = true;
}
++(*x_end);
} while (any_cut);
// All edges must satisfy the condition of sharp gradient in pixel density
// in order for the full rectangle to be present.
return left_done && right_done && top_done && bottom_done;
}
// Scanning rows horizontally on x=[x_start, x_end), returns the first y row
// starting at y_start, stepping by y_step to y_end in which there is
// any black pixel.
static int HScanForBlack(uinT32* data, int wpl, int x_start, int x_end,
int y_start, int y_end, int y_step) {
for (int y = y_start; y != y_end; y += y_step) {
uinT32* line = data + wpl * y;
for (int x = x_start; x < x_end; ++x) {
if (GET_DATA_BIT(line, x))
return y;
}
}
return y_end;
}
// Scanning columns vertically on y=[y_start, y_end), returns the first x
// colum starting at x_start, stepping by x_step to x_end in which there is
// any black pixel.
static int VScanForBlack(uinT32* data, int wpl, int x_start, int x_end,
int y_start, int y_end, int x_step) {
for (int x = x_start; x != x_end; x += x_step) {
uinT32* line = data + y_start * wpl;
for (int y = y_start; y < y_end; ++y, line += wpl) {
if (GET_DATA_BIT(line, x))
return x;
}
}
return x_end;
}
// Given an input pix, and a bounding rectangle, the sides of the rectangle
// are shrunk inwards until they bound any black pixels found within the
// original rectangle.
void ImageFinder::BoundsWithinRect(Pix* pix, int* x_start, int* y_start,
int* x_end, int* y_end) {
// This can probably be done with a lot less code using pixClipRect and
// pixConnComp, but this code is probably a lot faster, given that most
// uses will be applied to a solid black region.
int width = pixGetWidth(pix);
int height = pixGetHeight(pix);
if (*x_start < 0) *x_start = 0;
if (*x_end > width) *x_end = width;
if (*y_start < 0) *y_start = 0;
if (*y_end > height) *y_end = height;
if (*y_end <= *y_start || *x_end <= *x_start)
return; // Nothing to do.
uinT32* data = pixGetData(pix);
int wpl = pixGetWpl(pix);
*y_start = HScanForBlack(data, wpl, *x_start, *x_end, *y_start, *y_end, 1);
if (*y_end <= *y_start)
return;
*y_end = HScanForBlack(data, wpl, *x_start, *x_end,
*y_end - 1, *y_start - 1, -1) + 1;
*x_start = VScanForBlack(data, wpl, *x_start, *x_end, *y_start, *y_end, 1);
*x_end = VScanForBlack(data, wpl, *x_end - 1, *x_start - 1,
*y_start, *y_end, -1) + 1;
}
} // namespace tesseract.