blob: d77199fc916bae3b70ca4804f1ca62976c74c557 [file] [log] [blame]
// Copyright 2006 Google Inc.
// All Rights Reserved.
// Author: <renn@google.com> (Marius Renn)
// Local includes
#include "helium_cluster.h"
#include "debugging.h"
#include "heliumbinarizer.h"
#include "helium_image.h"
#include "mathfunctions.h"
#include "shape.h"
#include <math.h>
using namespace helium;
const int kMaxInt = ~(1 << (sizeof(int)*8 - 1));
const float kVarianceFactor = 1.5;
inline bool IsForeground(Color pixel, Color reference, Color variance) {
//Color variation = ColorDifference(pixel, reference);
unsigned distance = EuclideanDistance(pixel, reference);
int max_diff = Lightness(variance)*4;
if (max_diff > 128) max_diff = 128;
return distance < max_diff;
}
HeliumBinarizer::HeliumBinarizer(const Image& image)
: Binarizer(image),
areas_(4),
extension_clusters_(2),
current_index_(0),
number_of_clusters_(0) {
ASSERT(image_.Valid());
}
bool HeliumBinarizer::CheckBounds(const Box& bounds) const {
return (bounds.right() < image_.width())
&& (bounds.bottom() < image_.height())
&& (bounds.top() >= 0)
&& (bounds.left() >= 0)
&& (bounds.width() > 0)
&& (bounds.height() > 0);
}
void HeliumBinarizer::AddCluster(const Cluster& cluster) {
number_of_clusters_++;
// Add area left of first cluster
ExtendEnd(*(cluster.first()), number_of_clusters_, false);
for (const Shape* cur_shape = cluster.first();
cur_shape;
cur_shape = cur_shape->right_neighbor()) {
// Add area right of last cluster
if (!cur_shape->right_neighbor())
ExtendEnd(*cur_shape, number_of_clusters_, true);
else
AddInBetweenArea(*cur_shape,
*cur_shape->right_neighbor(),
number_of_clusters_);
// Add cluster area
AddShapeArea(*cur_shape, number_of_clusters_);
}
}
void HeliumBinarizer::AddClusters(const ClusterArray& clusters) {
for (unsigned i = 0; i < clusters.size(); i++)
AddCluster(*(clusters.ValueAt(i)));
}
void HeliumBinarizer::AddShapeArea(const Shape& shape, uint8 id) {
Area area(shape.bounds(),
id,
shape.histogram().Expected(),
shape.histogram().Variance());
areas_.Add(area);
}
void HeliumBinarizer::AddInBetweenArea(const Shape& left,
const Shape& right,
uint8 id) {
int x_0 = left.bounds().right();
int x_1 = right.bounds().left();
int y_0 = (left.bounds().top() + right.bounds().top()) / 2;
int y_1 = (left.bounds().bottom() + right.bounds().bottom()) / 2;
if ((x_1 <= x_0) || (y_1 <= y_0)) return;
Box bounds(x_0, y_0, x_1 - x_0, y_1 - y_0);
Area area(bounds,
id,
left.histogram().Expected(), // NOTE: Not correct, mix l and r!
left.histogram().Variance()); // NOTE: Same here!
areas_.Add(area);
}
bool HeliumBinarizer::ScanHorizontal(const Area& area, const Point& p) const {
Color* image_ptr = image_.Access(p);
for (unsigned x = 0; x < area.bounds.width(); x++)
if (IsForeground(*(image_ptr++), area.expected, area.variance))
return true;
return false;
}
bool HeliumBinarizer::ScanVertical(const Area& area, const Point& p) const {
const int step = image_.width();
Color* image_ptr = image_.Access(p);
for (unsigned y = 0; y < area.bounds.height(); y++)
if (IsForeground(*(image_ptr += step), area.expected, area.variance))
return true;
return false;
}
bool HeliumBinarizer::ScanLeft(const Area& area) const {
Point left(area.bounds.left() + 1, area.bounds.top());
return ScanVertical(area, left);
}
bool HeliumBinarizer::ScanTop(const Area& area) const {
Point top(area.bounds.left(), area.bounds.top() + 1);
return ScanHorizontal(area, top);
}
bool HeliumBinarizer::ScanRight(const Area& area) const {
Point right(area.bounds.right() - 1, area.bounds.top());
return ScanVertical(area, right);
}
bool HeliumBinarizer::ScanBottom(const Area& area) const {
Point bottom(area.bounds.left(), area.bounds.bottom() - 1);
return ScanHorizontal(area, bottom);
}
bool HeliumBinarizer::ScanVMiddle(const Area& area) const {
Point middle(area.bounds.left(), area.bounds.center().y);
return ScanVertical(area, middle);
}
bool HeliumBinarizer::ScanHMiddle(const Area& area) const {
Point middle(area.bounds.center().x, area.bounds.top());
return ScanVertical(area, middle);
}
void HeliumBinarizer::ExtendTopEndRec(const Area& area, Array<Box>* extensions) {
if (ScanBottom(area)) {
const Box& box = area.bounds;
extensions->Add(box);
// Create new extension area below
Box ext_bounds(box.left(), box.top() - box.height() / 2,
box.width(), box.height() / 2);
if(!CheckBounds(ext_bounds)) return;
// Create new area
Area extension(ext_bounds, area.id, area.expected, area.variance);
// Recursively call extending algorithm
ExtendTopEndRec(extension, extensions);
}
}
void HeliumBinarizer::ExtendBottomEndRec(const Area& area, Array<Box>* extensions) {
if (ScanTop(area)) {
const Box& box = area.bounds;
extensions->Add(box);
// Create new extension area below
Box ext_bounds(box.left(), box.bottom(), box.width(), box.height() / 2);
if(!CheckBounds(ext_bounds)) return;
// Create new area
Area extension(ext_bounds, area.id, area.expected, area.variance);
// Recursively call extending algorithm
ExtendBottomEndRec(extension, extensions);
}
}
void HeliumBinarizer::ExtendEndRec(const Area& area,
Array<Box>* extensions,
bool to_right) {
const Box& box = area.bounds;
if ((ScanLeft(area) && to_right)
|| ScanHMiddle(area)
|| (ScanRight(area) && !to_right)) {
extensions->Add(box);
// Create new extension area to the right or left
Box ext_bounds(to_right ? box.right() : box.left() - box.width(),
box.top(), box.width(), box.height());
if(!CheckBounds(ext_bounds)) return;
Area extension(ext_bounds, area.id, area.expected, area.variance);
// Recursively call extending algorithm
ExtendEndRec(extension, extensions, to_right);
// Also try to extend up...
Box up_bounds(box.left(), box.top() - box.height() / 2,
box.width(), box.height() / 2);
if(CheckBounds(up_bounds)) {
Area up_area(up_bounds, area.id, area.expected, area.variance);
ExtendTopEndRec(up_area, extensions);
}
// ...And down
Box down_bounds(box.left(), box.bottom(), box.width(), box.height() / 2);
if(CheckBounds(down_bounds)) {
Area down_area(down_bounds, area.id, area.expected, area.variance);
ExtendBottomEndRec(down_area, extensions);
}
}
}
void HeliumBinarizer::ExtendEnd(const Shape& shape, uint8 id, bool right_end) {
Array<Box>* extensions = new Array<Box>(2);
const Box& box = shape.bounds();
const Color& expected = shape.histogram().Expected();
const Color& variance = shape.histogram().Variance();
Box ext_bounds(right_end ? box.right() : box.left() - box.width() / 2,
box.top(), box.width() / 2, box.height());
// Make sure we don't end up having thousands of very small extensions
if (ext_bounds.width() < 8) ext_bounds.set_width(8);
Area first(ext_bounds, id, expected, variance);
ExtendEndRec(first, extensions, right_end);
if (extensions->size() > 0) {
Box total_bounds = MinEnclosingBox(*extensions);
if (total_bounds.height() / box.height() < 2) {
AreaCluster new_cluster(extensions, total_bounds, id, expected, variance);
extension_clusters_.Add(new_cluster);
return;
}
}
delete extensions;
}
void HeliumBinarizer::MergeOverlaps(const AreaCluster& cluster) {
const Box& bounds = cluster.bounds;
for (unsigned i = 0; i < areas_.size(); i++) {
const Area& cur_area = areas_.ValueAt(i);
if (cur_area.id == cluster.id) continue;
const unsigned smaller_height = cur_area.bounds.height() > bounds.height()
? bounds.height()
: cur_area.bounds.height();
Box overlap = Intersection(bounds, cur_area.bounds);
if (!CheckBounds(overlap)) continue;
uint8 color_distance = EuclideanDistance(cur_area.expected,
cluster.expected);
// Overlapping height must be atleast half the size of the height of the
// smaller area.
if ((smaller_height / overlap.height() <= 1) && (color_distance < 32))
cluster_classes_.Unify(cluster.id, cur_area.id);
}
}
void HeliumBinarizer::FindAreasOfClass(int class_id,
Array<Area>& areas,
Array<Area>& found) {
for (unsigned i = 0; i < areas.size(); i++) {
if (cluster_classes_.Find(areas.ValueAt(i).id) == class_id) {
found.Add(areas.ValueAt(i));
areas.ValueAt(i).binarized = true;
}
}
}
void HeliumBinarizer::FindAreasOfClassInClusters(int class_id,
Array<AreaCluster>& clusters,
Array<Area>& found) {
for (unsigned i = 0; i < extension_clusters_.size(); i++) {
const AreaCluster& cur_cluster = extension_clusters_.ValueAt(i);
if (cluster_classes_.Find(cur_cluster.id) == class_id) {
for (unsigned j = 0; j < cur_cluster.boxes->size(); j++) {
Area cluster_area(cur_cluster.boxes->ValueAt(j),
cur_cluster.id,
cur_cluster.expected,
cur_cluster.variance);
found.Add(cluster_area);
}
}
}
}
void HeliumBinarizer::BinarizeAreaToMask(const Area& area,
Mask& mask,
const Point& mask_origin) {
// Assert that area lies within mask bounds
ASSERT(area.bounds.origin().x >= mask_origin.x);
ASSERT(area.bounds.origin().y >= mask_origin.y);
// Calculate where we need to start in mask
Point mask_start(area.bounds.origin().x - mask_origin.x + 1,
area.bounds.origin().y - mask_origin.y + 1);
// Setup pointers
Color* image_ptr = image_.Access(area.bounds.origin());
bool* mask_ptr = mask.Access(mask_start);
// Setup steps in vetrical direction
int mask_vstep = mask.width() - area.bounds.width();
int image_vstep = image_.width() - area.bounds.width();
// Copy loop
Point p;
Color expected = area.expected;
Color variance = area.variance;
for (p.y = area.bounds.top(); p.y <= area.bounds.bottom(); p.y++) {
for (p.x = area.bounds.left(); p.x <= area.bounds.right(); p.x++) {
if (IsForeground(*image_ptr, expected, variance)) *mask_ptr = 1;
mask_ptr++;
image_ptr++;
}
mask_ptr += mask_vstep;
image_ptr += image_vstep;
}
}
// NOTE: Kind of ugly mask creation: Maybe return Mask instead
bool HeliumBinarizer::GetNextMask(Mask& out_mask, Box& out_bounds) {
if (current_index_ == 0) {
// First time here? Label roots in UnionFind tree
cluster_classes_.LabelRoots();
// And merge all extension areas with existing areas
for (unsigned i = 0; i < extension_clusters_.size(); i++)
MergeOverlaps(extension_clusters_.ValueAt(i));
}
// Loop to find all relevant masks for the current text line
while (current_index_ < areas_.size()) {
const Area& cur_area = areas_.ValueAt(current_index_);
current_index_++;
if (cur_area.binarized) continue;
int cur_class = cluster_classes_.Find(cur_area.id);
Array<Area> cur_areas(4);
// Find all areas of this class id in main area list...
FindAreasOfClass(cur_class, areas_, cur_areas);
// ...and in the clusters
FindAreasOfClassInClusters(cur_class, extension_clusters_, cur_areas);
// Create mask to hold all areas of these classes
Box cur_bounds = BoundsOfAreas(cur_areas);
out_mask = Mask(cur_bounds.width() + 2, cur_bounds.height() + 2);
out_mask.Clear();
// Copy all binarized areas to output mask
for (unsigned i = 0; i < cur_areas.size(); i++) {
Area area = cur_areas.ValueAt(i);
BinarizeAreaToMask(area, out_mask, cur_bounds.origin());
}
out_bounds = cur_bounds;
return true;
}
return false;
}
Box HeliumBinarizer::BoundsOfAreas(const Array<Area>& areas) {
ASSERT(areas.size() > 0);
Box bounds = areas.ValueAt(0).bounds;
for (unsigned i = 1; i < areas.size(); i++)
bounds = MinEnclosingBox(bounds, areas.ValueAt(i).bounds);
return bounds;
}