// Rotate the grid by rotation, keeping cell contents. // rotation must be a multiple of 90 degrees. // NOTE: due to partial cells, cell coverage in the rotated grid will be // inexact. This is why there is no Rotate for the generic BBGrid. // TODO(rays) investigate fixing this inaccuracy by moving the origin after // rotation. void IntGrid::Rotate(const FCOORD& rotation) { ASSERT_HOST(rotation.x() == 0.0f || rotation.y() == 0.0f); ICOORD old_bleft(bleft()); ICOORD old_tright(tright()); int old_width = gridwidth(); int old_height = gridheight(); TBOX box(bleft(), tright()); box.rotate(rotation); int* old_grid = grid_; grid_ = NULL; Init(gridsize(), box.botleft(), box.topright()); // Iterate over the old grid, copying data to the rotated position in the new. int oldi = 0; FCOORD x_step(rotation); x_step *= gridsize(); for (int oldy = 0; oldy < old_height; ++oldy) { FCOORD line_pos(old_bleft.x(), old_bleft.y() + gridsize() * oldy); line_pos.rotate(rotation); for (int oldx = 0; oldx < old_width; ++oldx, line_pos += x_step, ++oldi) { int grid_x, grid_y; GridCoords(static_cast<int>(line_pos.x() + 0.5), static_cast<int>(line_pos.y() + 0.5), &grid_x, &grid_y); grid_[grid_y * gridwidth() + grid_x] = old_grid[oldi]; } } delete [] old_grid; }
// Returns a full-resolution binary pix in which each cell over the given // threshold is filled as a black square. pixDestroy after use. // Edge cells, which have a zero 4-neighbour, are not marked. Pix* IntGrid::ThresholdToPix(int threshold) const { Pix* pix = pixCreate(tright().x() - bleft().x(), tright().y() - bleft().y(), 1); int cellsize = gridsize(); for (int y = 0; y < gridheight(); ++y) { for (int x = 0; x < gridwidth(); ++x) { if (GridCellValue(x, y) > threshold && GridCellValue(x - 1, y) > 0 && GridCellValue(x + 1, y) > 0 && GridCellValue(x, y - 1) > 0 && GridCellValue(x, y + 1) > 0) { pixRasterop(pix, x * cellsize, tright().y() - ((y + 1) * cellsize), cellsize, cellsize, PIX_SET, NULL, 0, 0); } } } return pix; }
// Computes and returns the noise_density IntGrid, at the same gridsize as // this by summing the number of small elements in a 3x3 neighbourhood of // each grid cell. good_grid is filled with blobs that are considered most // likely good text, and this is filled with small and medium blobs that are // more likely non-text. // The photo_map is used to bias the decision towards non-text, rather than // supplying definite decision. IntGrid* CCNonTextDetect::ComputeNoiseDensity(bool debug, Pix* photo_map, BlobGrid* good_grid) { IntGrid* noise_counts = CountCellElements(); IntGrid* noise_density = noise_counts->NeighbourhoodSum(); IntGrid* good_counts = good_grid->CountCellElements(); // Now increase noise density in photo areas, to bias the decision and // minimize hallucinated text on image, but trim the noise_density where // there are good blobs and the original count is low in non-photo areas, // indicating that most of the result came from neighbouring cells. int height = pixGetHeight(photo_map); int photo_offset = IntCastRounded(max_noise_count_ * kPhotoOffsetFraction); for (int y = 0; y < gridheight(); ++y) { for (int x = 0; x < gridwidth(); ++x) { int noise = noise_density->GridCellValue(x, y); if (max_noise_count_ < noise + photo_offset && noise <= max_noise_count_) { // Test for photo. int left = x * gridsize(); int right = left + gridsize(); int bottom = height - y * gridsize(); int top = bottom - gridsize(); if (ImageFind::BoundsWithinRect(photo_map, &left, &top, &right, &bottom)) { noise_density->SetGridCell(x, y, noise + photo_offset); } } if (debug && noise > max_noise_count_ && good_counts->GridCellValue(x, y) > 0) { tprintf("At %d, %d, noise = %d, good=%d, orig=%d, thr=%d\n", x * gridsize(), y * gridsize(), noise_density->GridCellValue(x, y), good_counts->GridCellValue(x, y), noise_counts->GridCellValue(x, y), max_noise_count_); } if (noise > max_noise_count_ && good_counts->GridCellValue(x, y) > 0 && noise_counts->GridCellValue(x, y) * kOriginalNoiseMultiple <= max_noise_count_) { noise_density->SetGridCell(x, y, 0); } } } delete noise_counts; delete good_counts; return noise_density; }
// Returns a new IntGrid containing values equal to the sum of all the // neighbouring cells. The returned grid must be deleted after use. // For ease of implementation, edge cells are double counted, to make them // have the same range as the non-edge cells. IntGrid* IntGrid::NeighbourhoodSum() const { IntGrid* sumgrid = new IntGrid(gridsize(), bleft(), tright()); for (int y = 0; y < gridheight(); ++y) { for (int x = 0; x < gridwidth(); ++x) { int cell_count = 0; for (int yoffset = -1; yoffset <= 1; ++yoffset) { for (int xoffset = -1; xoffset <= 1; ++xoffset) { int grid_x = x + xoffset; int grid_y = y + yoffset; ClipGridCoords(&grid_x, &grid_y); cell_count += GridCellValue(grid_x, grid_y); } } if (GridCellValue(x, y) > 1) sumgrid->SetGridCell(x, y, cell_count); } } return sumgrid; }