123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199 |
- /*
- * Copyright 2012 ZXing authors
- *
- * 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.
- */
-
- #import "ZXGlobalHistogramBinarizer.h"
- #import "ZXBitArray.h"
- #import "ZXBitMatrix.h"
- #import "ZXByteArray.h"
- #import "ZXErrors.h"
- #import "ZXIntArray.h"
- #import "ZXLuminanceSource.h"
-
- const int ZX_LUMINANCE_BITS = 5;
- const int ZX_LUMINANCE_SHIFT = 8 - ZX_LUMINANCE_BITS;
- const int ZX_LUMINANCE_BUCKETS = 1 << ZX_LUMINANCE_BITS;
-
- @interface ZXGlobalHistogramBinarizer ()
-
- @property (nonatomic, strong) ZXByteArray *luminances;
- @property (nonatomic, strong) ZXIntArray *buckets;
-
- @end
-
- @implementation ZXGlobalHistogramBinarizer
-
- - (id)initWithSource:(ZXLuminanceSource *)source {
- if (self = [super initWithSource:source]) {
- _luminances = [[ZXByteArray alloc] initWithLength:0];
- _buckets = [[ZXIntArray alloc] initWithLength:ZX_LUMINANCE_BUCKETS];
- }
-
- return self;
- }
-
- - (ZXBitArray *)blackRow:(int)y row:(ZXBitArray *)row error:(NSError **)error {
- ZXLuminanceSource *source = self.luminanceSource;
- int width = source.width;
- if (row == nil || row.size < width) {
- row = [[ZXBitArray alloc] initWithSize:width];
- } else {
- [row clear];
- }
-
- [self initArrays:width];
- ZXByteArray *localLuminances = [source rowAtY:y row:self.luminances];
- ZXIntArray *localBuckets = self.buckets;
- for (int x = 0; x < width; x++) {
- int pixel = localLuminances.array[x] & 0xff;
- localBuckets.array[pixel >> ZX_LUMINANCE_SHIFT]++;
- }
- int blackPoint = [self estimateBlackPoint:localBuckets];
- if (blackPoint == -1) {
- if (error) *error = ZXNotFoundErrorInstance();
- return nil;
- }
-
- int left = localLuminances.array[0] & 0xff;
- int center = localLuminances.array[1] & 0xff;
- for (int x = 1; x < width - 1; x++) {
- int right = localLuminances.array[x + 1] & 0xff;
- // A simple -1 4 -1 box filter with a weight of 2.
- int luminance = ((center * 4) - left - right) >> 1;
- if (luminance < blackPoint) {
- [row set:x];
- }
- left = center;
- center = right;
- }
-
- return row;
- }
-
- - (ZXBitMatrix *)blackMatrixWithError:(NSError **)error {
- ZXLuminanceSource *source = self.luminanceSource;
- int width = source.width;
- int height = source.height;
- ZXBitMatrix *matrix = [[ZXBitMatrix alloc] initWithWidth:width height:height];
-
- // Quickly calculates the histogram by sampling four rows from the image. This proved to be
- // more robust on the blackbox tests than sampling a diagonal as we used to do.
- [self initArrays:width];
-
- // We delay reading the entire image luminance until the black point estimation succeeds.
- // Although we end up reading four rows twice, it is consistent with our motto of
- // "fail quickly" which is necessary for continuous scanning.
- ZXIntArray *localBuckets = self.buckets;
- for (int y = 1; y < 5; y++) {
- int row = height * y / 5;
- ZXByteArray *localLuminances = [source rowAtY:row row:self.luminances];
- int right = (width * 4) / 5;
- for (int x = width / 5; x < right; x++) {
- int pixel = localLuminances.array[x] & 0xff;
- localBuckets.array[pixel >> ZX_LUMINANCE_SHIFT]++;
- }
- }
- int blackPoint = [self estimateBlackPoint:localBuckets];
- if (blackPoint == -1) {
- if (error) *error = ZXNotFoundErrorInstance();
- return nil;
- }
-
- ZXByteArray *localLuminances = source.matrix;
- for (int y = 0; y < height; y++) {
- int offset = y * width;
- for (int x = 0; x < width; x++) {
- int pixel = localLuminances.array[offset + x] & 0xff;
- if (pixel < blackPoint) {
- [matrix setX:x y:y];
- }
- }
- }
-
- return matrix;
- }
-
- - (ZXBinarizer *)createBinarizer:(ZXLuminanceSource *)source {
- return [[ZXGlobalHistogramBinarizer alloc] initWithSource:source];
- }
-
- - (void)initArrays:(int)luminanceSize {
- if (self.luminances.length < luminanceSize) {
- self.luminances = [[ZXByteArray alloc] initWithLength:luminanceSize];
- }
-
- for (int x = 0; x < ZX_LUMINANCE_BUCKETS; x++) {
- self.buckets.array[x] = 0;
- }
- }
-
- - (int)estimateBlackPoint:(ZXIntArray *)buckets {
- // Find the tallest peak in the histogram.
- int numBuckets = buckets.length;
- int maxBucketCount = 0;
- int firstPeak = 0;
- int firstPeakSize = 0;
- for (int x = 0; x < numBuckets; x++) {
- if (buckets.array[x] > firstPeakSize) {
- firstPeak = x;
- firstPeakSize = buckets.array[x];
- }
- if (buckets.array[x] > maxBucketCount) {
- maxBucketCount = buckets.array[x];
- }
- }
-
- // Find the second-tallest peak which is somewhat far from the tallest peak.
- int secondPeak = 0;
- int secondPeakScore = 0;
- for (int x = 0; x < numBuckets; x++) {
- int distanceToBiggest = x - firstPeak;
- // Encourage more distant second peaks by multiplying by square of distance.
- int score = buckets.array[x] * distanceToBiggest * distanceToBiggest;
- if (score > secondPeakScore) {
- secondPeak = x;
- secondPeakScore = score;
- }
- }
-
- // Make sure firstPeak corresponds to the black peak.
- if (firstPeak > secondPeak) {
- int temp = firstPeak;
- firstPeak = secondPeak;
- secondPeak = temp;
- }
-
- // If there is too little contrast in the image to pick a meaningful black point, throw rather
- // than waste time trying to decode the image, and risk false positives.
- if (secondPeak - firstPeak <= numBuckets / 16) {
- return -1;
- }
-
- // Find a valley between them that is low and closer to the white peak.
- int bestValley = secondPeak - 1;
- int bestValleyScore = -1;
- for (int x = secondPeak - 1; x > firstPeak; x--) {
- int fromFirst = x - firstPeak;
- int score = fromFirst * fromFirst * (secondPeak - x) * (maxBucketCount - buckets.array[x]);
- if (score > bestValleyScore) {
- bestValley = x;
- bestValleyScore = score;
- }
- }
-
- return bestValley << ZX_LUMINANCE_SHIFT;
- }
-
- @end
|