C++: binarizer updates
authorflyashi <flyashi@59b500cc-1b3d-0410-9834-0bbf25fbcc57>
Mon, 19 Jul 2010 20:56:17 +0000 (20:56 +0000)
committerflyashi <flyashi@59b500cc-1b3d-0410-9834-0bbf25fbcc57>
Mon, 19 Jul 2010 20:56:17 +0000 (20:56 +0000)
 - ported the HybridBinarizer from Java for improved 2D binarization
 - updated BinaryBitmap to report if it supports cropping and rotation
 - test binary updates to compare both binarizers like it used to

git-svn-id: http://zxing.googlecode.com/svn/trunk@1489 59b500cc-1b3d-0410-9834-0bbf25fbcc57

cpp/blackboxtest.sh
cpp/core/src/zxing/BinaryBitmap.cpp
cpp/core/src/zxing/common/HybridBinarizer.cpp [new file with mode: 0644]
cpp/core/src/zxing/common/HybridBinarizer.h [new file with mode: 0644]
cpp/magick/src/MagickBitmapSource.cpp
cpp/magick/src/MagickBitmapSource.h
cpp/magick/src/main.cpp

index b93fefe..b6be6d1 100755 (executable)
@@ -2,14 +2,18 @@
 
 blackboxpath="../core/test/data/blackbox"
 
-formats="ean13 ean8 upce upca qrcode"
+if [ "$*" != "" ]; then
+       formats="$*"
+else
+       formats="ean13 ean8 upce upca qrcode"
+fi
 
 passed=0;
 failed=0;
 oldcat="";
 
 for format in $formats; do
-       for pic in `ls ${blackboxpath}/${format}-*/*.{jpg,JPG} 2>/dev/null`; do
+       for pic in `ls ${blackboxpath}/${format}-*/*.{jpg,JPG,gif,GIF,png,PNG} 2>/dev/null | sort -n`; do
                category=${pic%/*};
                category=${category##*/};
                if [ "$oldcat" != "$category" ]; then
@@ -19,8 +23,14 @@ for format in $formats; do
                        failed=0;
                fi
                echo -n "Processing: $pic ... "
-               tmp="${pic%JPG}";
-               txt="${tmp%jpg}txt";
+               tmp="${pic}"
+               tmp="${tmp%JPG}";
+               tmp="${tmp%jpg}";
+               tmp="${tmp%gif}";
+               tmp="${tmp%GIF}";
+               tmp="${tmp%png}";
+               tmp="${tmp%PNG}";
+               txt="${tmp}txt";
                expected=`cat "$txt"`;
                actual=`build/zxing $pic`;
                if [ "$expected" == "$actual" ]; then
index ad5a8cd..a1c68db 100644 (file)
@@ -2,9 +2,7 @@
  *  BinaryBitmap.cpp
  *  zxing
  *
- *  Created by Ralf Kistner on 19/10/2009.
- *  Copyright 2008 ZXing authors All rights reserved.
- *  Modified by Lukasz Warchol on 02/02/2010.
+ *  Copyright 2010 ZXing authors All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -50,4 +48,20 @@ namespace zxing {
                return binarizer_->getLuminanceSource();
        }
        
+
+       bool BinaryBitmap::isCropSupported() const {
+         return getLuminanceSource()->isCropSupported();
+       }
+
+       Ref<BinaryBitmap> BinaryBitmap::crop(int left, int top, int width, int height) {
+         return Ref<BinaryBitmap> (new BinaryBitmap(binarizer_->createBinarizer(getLuminanceSource()->crop(left, top, width, height))));
+       }
+
+       bool BinaryBitmap::isRotateSupported() const {
+         return getLuminanceSource()->isRotateSupported();
+       }
+
+       Ref<BinaryBitmap> BinaryBitmap::rotateCounterClockwise() {
+         return Ref<BinaryBitmap> (new BinaryBitmap(binarizer_->createBinarizer(getLuminanceSource()->rotateCounterClockwise())));
+       }
 }
diff --git a/cpp/core/src/zxing/common/HybridBinarizer.cpp b/cpp/core/src/zxing/common/HybridBinarizer.cpp
new file mode 100644 (file)
index 0000000..65d03ee
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+ *  HybridBinarizer.cpp
+ *  zxing
+ *
+ *  Copyright 2010 ZXing authors All rights reserved.
+ *
+ * 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.
+ */
+
+#include <zxing/common/HybridBinarizer.h>
+
+#include <zxing/common/IllegalArgumentException.h>
+
+namespace zxing {
+using namespace std;
+
+static const int MINIMUM_DIMENSION = 40;
+
+static const int LUMINANCE_BITS = 5;
+static const int LUMINANCE_SHIFT = 8 - LUMINANCE_BITS;
+static const int LUMINANCE_BUCKETS = 1 << LUMINANCE_BITS;
+
+HybridBinarizer::HybridBinarizer(Ref<LuminanceSource> source) :
+  GlobalHistogramBinarizer(source), cached_matrix_(NULL), cached_row_(NULL), cached_row_num_(-1) {
+
+}
+
+HybridBinarizer::~HybridBinarizer() {
+}
+
+
+Ref<BitMatrix> HybridBinarizer::getBlackMatrix() {
+  binarizeEntireImage();
+  return cached_matrix_;
+}
+
+Ref<Binarizer> HybridBinarizer::createBinarizer(Ref<LuminanceSource> source) {
+  return Ref<Binarizer> (new HybridBinarizer(source));
+}
+
+void HybridBinarizer::binarizeEntireImage() {
+  if (cached_matrix_ == NULL) {
+    Ref<LuminanceSource> source = getLuminanceSource();
+    if (source->getWidth() >= MINIMUM_DIMENSION && source->getHeight() >= MINIMUM_DIMENSION) {
+      unsigned char* luminances = source->getMatrix();
+      int width = source->getWidth();
+      int height = source->getHeight();
+      int subWidth = width >> 3;
+      int subHeight = height >> 3;
+      int *blackPoints = calculateBlackPoints(luminances, subWidth, subHeight, width);
+      cached_matrix_.reset(new BitMatrix(width,height));
+      calculateThresholdForBlock(luminances, subWidth, subHeight, width, blackPoints, cached_matrix_);
+      delete [] blackPoints;
+    } else {
+      // If the image is too small, fall back to the global histogram approach.
+      cached_matrix_.reset(GlobalHistogramBinarizer::getBlackMatrix());
+    }
+  }
+}
+
+void HybridBinarizer::calculateThresholdForBlock(unsigned char* luminances, int subWidth, int subHeight,
+    int stride, int blackPoints[], Ref<BitMatrix> matrix) {
+  for (int y = 0; y < subHeight; y++) {
+    for (int x = 0; x < subWidth; x++) {
+      int left = (x > 1) ? x : 2;
+      left = (left < subWidth - 2) ? left : subWidth - 3;
+      int top = (y > 1) ? y : 2;
+      top = (top < subHeight - 2) ? top : subHeight - 3;
+      int sum = 0;
+      for (int z = -2; z <= 2; z++) {
+        int *blackRow = &blackPoints[(top + z) * subWidth];
+        sum += blackRow[left - 2];
+        sum += blackRow[left - 1];
+        sum += blackRow[left];
+        sum += blackRow[left + 1];
+        sum += blackRow[left + 2];
+      }
+      int average = sum / 25;
+      threshold8x8Block(luminances, x << 3, y << 3, average, stride, matrix);
+    }
+  }
+}
+
+void HybridBinarizer::threshold8x8Block(unsigned char* luminances, int xoffset, int yoffset, int threshold,
+    int stride, Ref<BitMatrix> matrix) {
+  for (int y = 0; y < 8; y++) {
+    int offset = (yoffset + y) * stride + xoffset;
+    for (int x = 0; x < 8; x++) {
+      int pixel = luminances[offset + x] & 0xff;
+      if (pixel < threshold) {
+        matrix->set(xoffset + x, yoffset + y);
+      }
+    }
+  }
+}
+
+int* HybridBinarizer::calculateBlackPoints(unsigned char* luminances, int subWidth, int subHeight,
+    int stride) {
+  int *blackPoints = new int[subHeight * subWidth];
+  for (int y = 0; y < subHeight; y++) {
+    for (int x = 0; x < subWidth; x++) {
+      int sum = 0;
+      int min = 255;
+      int max = 0;
+      for (int yy = 0; yy < 8; yy++) {
+        int offset = ((y << 3) + yy) * stride + (x << 3);
+        for (int xx = 0; xx < 8; xx++) {
+          int pixel = luminances[offset + xx] & 0xff;
+          sum += pixel;
+          if (pixel < min) {
+            min = pixel;
+          }
+          if (pixel > max) {
+            max = pixel;
+          }
+        }
+      }
+
+      // If the contrast is inadequate, use half the minimum, so that this block will be
+      // treated as part of the white background, but won't drag down neighboring blocks
+      // too much.
+      int average = (max - min > 24) ? (sum >> 6) : (min >> 1);
+      blackPoints[y * subWidth + x] = average;
+    }
+  }
+  return blackPoints;
+}
+
+} // namespace zxing
+
diff --git a/cpp/core/src/zxing/common/HybridBinarizer.h b/cpp/core/src/zxing/common/HybridBinarizer.h
new file mode 100644 (file)
index 0000000..17aca8b
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ *  HybridBinarizer.h
+ *  zxing
+ *
+ *  Copyright 2010 ZXing authors All rights reserved.
+ *
+ * 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.
+ */
+
+#ifndef HYBRIDBINARIZER_H_
+#define HYBRIDBINARIZER_H_
+
+#include <vector>
+#include <zxing/Binarizer.h>
+#include <zxing/common/GlobalHistogramBinarizer.h>
+#include <zxing/common/BitArray.h>
+#include <zxing/common/BitMatrix.h>
+
+namespace zxing {
+       
+       class HybridBinarizer : public GlobalHistogramBinarizer {
+        private:
+    Ref<BitMatrix> cached_matrix_;
+         Ref<BitArray> cached_row_;
+         int cached_row_num_;
+
+       public:
+               HybridBinarizer(Ref<LuminanceSource> source);
+               virtual ~HybridBinarizer();
+               
+               virtual Ref<BitMatrix> getBlackMatrix();
+               Ref<Binarizer> createBinarizer(Ref<LuminanceSource> source);
+  private:
+    void binarizeEntireImage();
+    // We'll be using one-D arrays because C++ can't dynamically allocate 2D arrays
+    int* calculateBlackPoints(unsigned char* luminances, int subWidth, int subHeight,
+      int stride);
+    void calculateThresholdForBlock(unsigned char* luminances, int subWidth, int subHeight,
+      int stride, int blackPoints[], Ref<BitMatrix> matrix);
+    void threshold8x8Block(unsigned char* luminances, int xoffset, int yoffset, int threshold,
+      int stride, Ref<BitMatrix> matrix);
+       };
+
+}
+
+#endif /* GLOBALHISTOGRAMBINARIZER_H_ */
index 71b775a..fd2980a 100644 (file)
@@ -2,8 +2,7 @@
  *  MagickBitmapSource.cpp
  *  zxing
  *
- *  Created by Ralf Kistner on 16/10/2009.
- *  Copyright 2008 ZXing authors All rights reserved.
+ *  Copyright 2010 ZXing authors All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -75,5 +74,23 @@ unsigned char* MagickBitmapSource::getMatrix() {
   }
   return matrix;
 }
+
+bool MagickBitmapSource::isRotateSupported() const {
+  return false;
+}
+
+Ref<LuminanceSource> MagickBitmapSource::rotateCounterClockwise() {
+    //TODO(flyashi): add rotated image support.
+  /* this segfaults. I tried a few things, none seemed to work. Perhaps the problem is elsewhere? */
+  /*
+  Magick::Image rotated(image_);
+  rotated.modifyImage();
+  rotated.rotate(90); // Image::rotate takes CCW degrees as an argument
+  rotated.syncPixels();
+  return Ref<MagickBitmapSource> (new MagickBitmapSource(rotated));
+  */
+  return Ref<MagickBitmapSource> (NULL);
+}
+
 }
 
index 2bc3b96..c19ec09 100644 (file)
@@ -2,8 +2,7 @@
  *  MagickBitmapSource.h
  *  zxing
  *
- *  Created by Ralf Kistner on 16/10/2009.
- *  Copyright 2008 ZXing authors All rights reserved.
+ *  Copyright 2010 ZXing authors All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -42,6 +41,8 @@ public:
   int getHeight() const;
   unsigned char* getRow(int y, unsigned char* row);
   unsigned char* getMatrix();
+  bool isRotateSupported() const;
+  Ref<LuminanceSource> rotateCounterClockwise();
 };
 
 }
index 06d9dd1..2773c33 100644 (file)
@@ -2,9 +2,7 @@
  *  main.cpp
  *  zxing
  *
- *  Created by Ralf Kistner on 16/10/2009.
- *  Copyright 2008 ZXing authors All rights reserved.
- *  Modified by Yakov Okshtein (flyashi@gmail.com) to add 1D barcode support.
+ *  Copyright 2010 ZXing authors All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -31,7 +29,7 @@
 #include <zxing/Result.h>
 #include <zxing/ReaderException.h>
 #include <zxing/common/GlobalHistogramBinarizer.h>
-//#include <zxing/common/LocalBlockBinarizer.h>
+#include <zxing/common/HybridBinarizer.h>
 #include <exception>
 #include <zxing/Exception.h>
 #include <zxing/common/IllegalArgumentException.h>
@@ -46,13 +44,17 @@ using namespace std;
 using namespace zxing;
 //using namespace zxing::qrcode;
 
+static bool raw_dump = false;
+
+static const int MAX_EXPECTED = 1024;
+
 Ref<Result> decode(Ref<BinaryBitmap> image) {
   Ref<Reader> reader(new MultiFormatReader);
   return Ref<Result> (new Result(*reader->decode(image)));
 }
 
 
-int test_image(Image& image, bool localized) {
+int test_image(Image& image, bool hybrid, string expected = "") {
 
   string cell_result;
   int res = -1;
@@ -64,8 +66,8 @@ int test_image(Image& image, bool localized) {
   try {
     Ref<MagickBitmapSource> source(new MagickBitmapSource(image));
 
-    if (localized) {
-      //binarizer = new LocalBlockBinarizer(source);
+    if (hybrid) {
+      binarizer = new HybridBinarizer(source);
     } else {
       binarizer = new GlobalHistogramBinarizer(source);
     }
@@ -88,33 +90,88 @@ int test_image(Image& image, bool localized) {
     res = -5;
   }
 
-  cout << cell_result;
+  if (cell_result.compare(expected)) {
+    res = -6;
+    if (!raw_dump) {
+        cout << (hybrid ? "Hybrid" : "Global") << " binarizer failed:\n";
+        if (expected.length() >= 0) {
+          cout << "  Expected: " << expected << "\n";
+        }
+        cout << "  Detected: " << cell_result << endl;
+    }
+  }
+
+
+  if (raw_dump && !hybrid) /* don't print twice, and global is a bit better */
+    cout << cell_result << endl;
+
   return res;
 }
 
-int test_image_local(Image& image) {
-  return test_image(image, true);
+int test_image_hybrid(Image& image, string expected = "") {
+  return test_image(image, true, expected);
 }
 
-int test_image_global(Image& image) {
-  return test_image(image, false);
+int test_image_global(Image& image, string expected = "") {
+  return test_image(image, false, expected);
+}
+
+string get_expected(string imagefilename) {
+  string textfilename = imagefilename;
+  int dotpos = textfilename.rfind(".");
+  textfilename.replace(dotpos+1, textfilename.length() - dotpos - 1, "txt");
+  char data[MAX_EXPECTED];
+  FILE *fp = fopen(textfilename.data(), "rb");
+    
+  // get file size
+  fseek(fp, 0, SEEK_END);
+  int toread = ftell(fp);
+  rewind(fp);
+  
+  if (toread > MAX_EXPECTED) {
+       cerr << "MAX_EXPECTED = " << MAX_EXPECTED << " but file '" << textfilename << "' has " << toread
+            << " bytes! Skipping..." << endl;
+    fclose(fp);
+    return "";
+  }
+  
+  int nread = fread(data, sizeof(char), toread, fp);
+  if (nread != toread) {
+    cerr << "Could not read entire contents of file '" << textfilename << "'! Skipping..." << endl;
+    fclose(fp);
+    return "";
+  }
+  fclose(fp);
+  data[nread] = '\0';
+  string expected(data);
+  return expected;
 }
 
 int main(int argc, char** argv) {
   if (argc <= 1) {
-    cout << "Usage: " << argv[0] << " <filename1> [<filename2> ...]" << endl;
+    cout << "Usage: " << argv[0] << " [--dump-raw] <filename1> [<filename2> ...]" << endl;
     return 1;
   }
 
// int total = argc - 2;
 int total = 0;
   int gonly = 0;
-  int lonly = 0;
+  int honly = 0;
   int both = 0;
   int neither = 0;
 
+  if (argc == 2) raw_dump = true;
+
   for (int i = 1; i < argc; i++) {
     string infilename = argv[i];
-//    cerr << "Processing: " << infilename << endl;
+    if (infilename.substr(infilename.length()-3,3).compare("txt") == 0) {
+      continue;
+    }
+    if (infilename.compare("--dump-raw") == 0) {
+      raw_dump = true;
+      continue;
+    }
+    if (!raw_dump)
+      cerr << "Processing: " << infilename << endl;
     Image image;
     try {
       image.read(infilename);
@@ -123,21 +180,31 @@ int main(int argc, char** argv) {
       continue;
     }
 
+    string expected;
+    expected = get_expected(infilename);
+
     int gresult = 1;
-    int lresult = 1;
+    int hresult = 1;
 
-    gresult = test_image_global(image);
-//    lresult = test_image_local(image);
+    hresult = test_image_hybrid(image, expected);
+    gresult = test_image_global(image, expected);
 
     gresult = gresult == 0;
- //   lresult = lresult == 0;
-
-    gonly += gresult && !lresult;
-    lonly += lresult && !gresult;
-    both += gresult && lresult;
-    neither += !gresult && !lresult;
+    hresult = hresult == 0;
 
+    gonly += gresult && !hresult;
+    honly += hresult && !gresult;
+    both += gresult && hresult;
+    neither += !gresult && !hresult;
+    total = total + 1;
   }
+
+  if (!raw_dump)
+    cout << (honly+both)  << " passed hybrid, " << (gonly+both) << " passed global, "
+      << both << " pass both, " << neither << " pass neither, " << honly
+      << " passed only hybrid, " << gonly << " passed only global, of " << total
+      << " total." << endl;
+
   return 0;
 }