2 * GlobalHistogramBinarizer.cpp
5 * Created by Ralf Kistner on 16/10/2009.
6 * Copyright 2008 ZXing authors All rights reserved.
7 * Modified by Lukasz Warchol on 02/02/2010.
9 * Licensed under the Apache License, Version 2.0 (the "License");
10 * you may not use this file except in compliance with the License.
11 * You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing, software
16 * distributed under the License is distributed on an "AS IS" BASIS,
17 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 * See the License for the specific language governing permissions and
19 * limitations under the License.
22 #include <zxing/common/GlobalHistogramBinarizer.h>
24 #include <zxing/common/IllegalArgumentException.h>
29 const int LUMINANCE_BITS = 5;
30 const int LUMINANCE_SHIFT = 8 - LUMINANCE_BITS;
31 const int LUMINANCE_BUCKETS = 1 << LUMINANCE_BITS;
33 GlobalHistogramBinarizer::GlobalHistogramBinarizer(Ref<LuminanceSource> source) :
34 Binarizer(source), cached_matrix_(NULL), cached_row_(NULL), cached_row_num_(-1) {
38 GlobalHistogramBinarizer::~GlobalHistogramBinarizer() {
42 Ref<BitArray> GlobalHistogramBinarizer::getBlackRow(int y, Ref<BitArray> row) {
43 if (y == cached_row_num_) {
44 if (cached_row_ != NULL) {
47 throw IllegalArgumentException("Too little dynamic range in luminance");
51 vector<int> histogram(LUMINANCE_BUCKETS, 0);
52 LuminanceSource& source = *getLuminanceSource();
53 int width = source.getWidth();
54 if (row == NULL || static_cast<int>(row->getSize()) < width) {
55 row = new BitArray(width);
60 //TODO(flyashi): cache this instead of allocating and deleting per row
61 unsigned char* row_pixels = NULL;
63 row_pixels = new unsigned char[width];
64 row_pixels = source.getRow(y, row_pixels);
65 for (int x = 0; x < width; x++) {
66 histogram[row_pixels[x] >> LUMINANCE_SHIFT]++;
68 int blackPoint = estimate(histogram) << LUMINANCE_SHIFT;
70 BitArray& array = *row;
71 int left = row_pixels[0];
72 int center = row_pixels[1];
73 for (int x = 1; x < width - 1; x++) {
74 int right = row_pixels[x + 1];
75 // A simple -1 4 -1 box filter with a weight of 2.
76 int luminance = ((center << 2) - left - right) >> 1;
77 if (luminance < blackPoint) {
88 } catch (IllegalArgumentException const& iae) {
89 // Cache the fact that this row failed.
97 Ref<BitMatrix> GlobalHistogramBinarizer::getBlackMatrix() {
98 if (cached_matrix_ != NULL) {
99 return cached_matrix_;
102 // Faster than working with the reference
103 LuminanceSource& source = *getLuminanceSource();
104 int width = source.getWidth();
105 int height = source.getHeight();
106 vector<int> histogram(LUMINANCE_BUCKETS, 0);
108 // Quickly calculates the histogram by sampling four rows from the image.
109 // This proved to be more robust on the blackbox tests than sampling a
110 // diagonal as we used to do.
111 unsigned char* row = new unsigned char[width];
112 for (int y = 1; y < 5; y++) {
113 int rownum = height * y / 5;
114 int right = (width << 2) / 5;
116 row = source.getRow(rownum, row);
117 for (int x = width / 5; x < right; x++) {
118 histogram[row[x] >> LUMINANCE_SHIFT]++;
119 sdf = histogram[row[x] >> LUMINANCE_SHIFT];
123 int blackPoint = estimate(histogram) << LUMINANCE_SHIFT;
125 Ref<BitMatrix> matrix_ref(new BitMatrix(width, height));
126 BitMatrix& matrix = *matrix_ref;
127 for (int y = 0; y < height; y++) {
128 row = source.getRow(y, row);
129 for (int x = 0; x < width; x++) {
130 if (row[x] <= blackPoint)
135 cached_matrix_ = matrix_ref;
140 int GlobalHistogramBinarizer::estimate(vector<int> &histogram) {
141 int numBuckets = histogram.size();
142 int maxBucketCount = 0;
144 // Find tallest peak in histogram
146 int firstPeakSize = 0;
147 for (int i = 0; i < numBuckets; i++) {
148 if (histogram[i] > firstPeakSize) {
150 firstPeakSize = histogram[i];
152 if (histogram[i] > maxBucketCount) {
153 maxBucketCount = histogram[i];
157 // Find second-tallest peak -- well, another peak that is tall and not
158 // so close to the first one
160 int secondPeakScore = 0;
161 for (int i = 0; i < numBuckets; i++) {
162 int distanceToBiggest = i - firstPeak;
163 // Encourage more distant second peaks by multiplying by square of distance
164 int score = histogram[i] * distanceToBiggest * distanceToBiggest;
165 if (score > secondPeakScore) {
167 secondPeakScore = score;
171 // Put firstPeak first
172 if (firstPeak > secondPeak) {
173 int temp = firstPeak;
174 firstPeak = secondPeak;
178 // Kind of arbitrary; if the two peaks are very close, then we figure there is
179 // so little dynamic range in the image, that discriminating black and white
180 // is too error-prone.
181 // Decoding the image/line is either pointless, or may in some cases lead to
182 // a false positive for 1D formats, which are relatively lenient.
183 // We arbitrarily say "close" is
184 // "<= 1/16 of the total histogram buckets apart"
185 if (secondPeak - firstPeak <= numBuckets >> 4) {
186 throw IllegalArgumentException("Too little dynamic range in luminance");
189 // Find a valley between them that is low and closer to the white peak
190 int bestValley = secondPeak - 1;
191 int bestValleyScore = -1;
192 for (int i = secondPeak - 1; i > firstPeak; i--) {
193 int fromFirst = i - firstPeak;
194 // Favor a "valley" that is not too close to either peak -- especially not
195 // the black peak -- and that has a low value of course
196 int score = fromFirst * fromFirst * (secondPeak - i) *
197 (maxBucketCount - histogram[i]);
198 if (score > bestValleyScore) {
200 bestValleyScore = score;
207 Ref<Binarizer> GlobalHistogramBinarizer::createBinarizer(Ref<LuminanceSource> source) {
208 return Ref<Binarizer> (new GlobalHistogramBinarizer(source));