aboutsummaryrefslogtreecommitdiff
path: root/helium/heliumbinarizer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'helium/heliumbinarizer.cpp')
-rw-r--r--helium/heliumbinarizer.cpp388
1 files changed, 388 insertions, 0 deletions
diff --git a/helium/heliumbinarizer.cpp b/helium/heliumbinarizer.cpp
new file mode 100644
index 0000000..eae5d92
--- /dev/null
+++ b/helium/heliumbinarizer.cpp
@@ -0,0 +1,388 @@
+// Copyright 2006 Google Inc.
+// All Rights Reserved.
+// Author: <renn@google.com> (Marius Renn)
+
+// Local includes
+#include "cluster.h"
+#include "debugging.h"
+#include "heliumbinarizer.h"
+#include "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;
+}