Merge pull request #3497 from mshabunin:barcode-move

Moved barcode module to opencv/objdetect
pull/3486/head
Alexander Smorkalov 2 years ago committed by GitHub
commit 7a4c0dfa86
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      modules/README.md
  2. 2
      modules/barcode/CMakeLists.txt
  3. 9
      modules/barcode/README.md
  4. 28
      modules/barcode/doc/barcode.bib
  5. 101
      modules/barcode/include/opencv2/barcode.hpp
  6. 1
      modules/barcode/misc/java/filelist_common
  7. 12
      modules/barcode/misc/java/gen_dict.json
  8. 28
      modules/barcode/misc/java/src/cpp/barcode_converters.cpp
  9. 20
      modules/barcode/misc/java/src/cpp/barcode_converters.hpp
  10. 51
      modules/barcode/misc/java/test/BarcodeDetectorTest.java
  11. 22
      modules/barcode/misc/python/pyopencv_barcode.hpp
  12. 30
      modules/barcode/misc/python/test/test_barcode_detector.py
  13. 113
      modules/barcode/perf/perf_barcode.cpp
  14. 9
      modules/barcode/perf/perf_main.cpp
  15. 11
      modules/barcode/perf/perf_precomp.hpp
  16. 298
      modules/barcode/samples/barcode.cpp
  17. 272
      modules/barcode/src/barcode.cpp
  18. 118
      modules/barcode/src/decoder/abs_decoder.cpp
  19. 73
      modules/barcode/src/decoder/abs_decoder.hpp
  20. 195
      modules/barcode/src/decoder/common/hybrid_binarizer.cpp
  21. 22
      modules/barcode/src/decoder/common/hybrid_binarizer.hpp
  22. 75
      modules/barcode/src/decoder/common/super_scale.cpp
  23. 35
      modules/barcode/src/decoder/common/super_scale.hpp
  24. 36
      modules/barcode/src/decoder/common/utils.cpp
  25. 25
      modules/barcode/src/decoder/common/utils.hpp
  26. 92
      modules/barcode/src/decoder/ean13_decoder.cpp
  27. 31
      modules/barcode/src/decoder/ean13_decoder.hpp
  28. 79
      modules/barcode/src/decoder/ean8_decoder.cpp
  29. 32
      modules/barcode/src/decoder/ean8_decoder.hpp
  30. 290
      modules/barcode/src/decoder/upcean_decoder.cpp
  31. 67
      modules/barcode/src/decoder/upcean_decoder.hpp
  32. 410
      modules/barcode/src/detector/bardetect.cpp
  33. 63
      modules/barcode/src/detector/bardetect.hpp
  34. 16
      modules/barcode/src/precomp.hpp
  35. 79
      modules/barcode/test/test_barcode.cpp
  36. 6
      modules/barcode/test/test_main.cpp
  37. 11
      modules/barcode/test/test_precomp.hpp
  38. 59
      modules/barcode/test/utils.hpp
  39. 73
      modules/barcode/tutorials/barcode_detect_and_decode/barcode_detect_and_decode.markdown
  40. BIN
      modules/barcode/tutorials/barcode_detect_and_decode/images/4_barcodes.jpg
  41. BIN
      modules/barcode/tutorials/barcode_detect_and_decode/images/result.jpg
  42. 6
      modules/barcode/tutorials/table_of_content_barcode.markdown

@ -12,8 +12,6 @@ $ cmake -D OPENCV_EXTRA_MODULES_PATH=<opencv_contrib>/modules -D BUILD_opencv_<r
- **aruco**: ArUco and ChArUco Markers -- Augmented reality ArUco marker and "ChARUco" markers where ArUco markers embedded inside the white areas of the checker board.
- **barcode**: Barcode detecting and decoding methods.
- **bgsegm**: Background segmentation algorithm combining statistical background image estimation and per-pixel Bayesian segmentation.
- **bioinspired**: Biological Vision -- Biologically inspired vision model: minimize noise and luminance variance, transient event segmentation, high dynamic range tone mapping methods.

@ -1,2 +0,0 @@
set(the_description "Barcode Detection and Decoding")
ocv_define_module(barcode opencv_core opencv_imgproc opencv_dnn WRAP python java)

@ -1,9 +0,0 @@
1D Barcode Detect and Decode
======================
This module is focused on detecting and decoding barcode from image. It is mainly designed for scanning the images, locating barcode, decoding barcode and outputting its decoded result.
1. Support 1-dimension bar code detection of any angle tilting.
2. Support multi-scale detection.
3. Support EAN-13, EAN-8 and UPC-A decode yet.
4. With x86 CPU, it achieves 50FPS averagely.

@ -1,28 +0,0 @@
@mastersthesis{Xiangmin2015research,
title={Research on Barcode Recognition Technology In a Complex Background},
author={Xiangmin, Wang},
year={2015},
school={Huazhong University of Science and Technology}
}
@article{bazen2002systematic,
title={Systematic methods for the computation of the directional fields and singular points of fingerprints},
author={Bazen, Asker M and Gerez, Sabih H},
journal={IEEE transactions on pattern analysis and machine intelligence},
volume={24},
number={7},
pages={905--919},
year={2002},
publisher={IEEE}
}
@article{kass1987analyzing,
title={Analyzing oriented patterns},
author={Kass, Michael and Witkin, Andrew},
journal={Computer vision, graphics, and image processing},
volume={37},
number={3},
pages={362--385},
year={1987},
publisher={Elsevier}
}

@ -1,101 +0,0 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
// Copyright (c) 2020-2021 darkliang wangberlinT Certseeds
#ifndef __OPENCV_BARCODE_HPP__
#define __OPENCV_BARCODE_HPP__
#include <opencv2/core.hpp>
#include <ostream>
/** @defgroup barcode Barcode detecting and decoding methods
*/
namespace cv {
namespace barcode {
//! @addtogroup barcode
//! @{
enum BarcodeType
{
NONE, EAN_8, EAN_13, UPC_A, UPC_E, UPC_EAN_EXTENSION
};
static inline std::ostream &operator<<(std::ostream &out, const BarcodeType &barcode_type)
{
switch (barcode_type)
{
case BarcodeType::EAN_8:
out << "EAN_8";
break;
case BarcodeType::EAN_13:
out << "EAN_13";
break;
case BarcodeType::UPC_E:
out << "UPC_E";
break;
case BarcodeType::UPC_A:
out << "UPC_A";
break;
case BarcodeType::UPC_EAN_EXTENSION:
out << "UPC_EAN_EXTENSION";
break;
default:
out << "NONE";
}
return out;
}
class CV_EXPORTS_W BarcodeDetector
{
public:
/**
* @brief Initialize the BarcodeDetector.
* @param prototxt_path prototxt file path for the super resolution model
* @param model_path model file path for the super resolution model
*/
CV_WRAP BarcodeDetector(const std::string &prototxt_path = "", const std::string &model_path = "");
~BarcodeDetector();
/** @brief Detects Barcode in image and returns the rectangle(s) containing the code.
*
* @param img grayscale or color (BGR) image containing (or not) Barcode.
* @param points Output vector of vector of vertices of the minimum-area rotated rectangle containing the codes.
* For N detected barcodes, the dimensions of this array should be [N][4].
* Order of four points in vector< Point2f> is bottomLeft, topLeft, topRight, bottomRight.
*/
CV_WRAP bool detect(InputArray img, OutputArray points) const;
/** @brief Decodes barcode in image once it's found by the detect() method.
*
* @param img grayscale or color (BGR) image containing bar code.
* @param points vector of rotated rectangle vertices found by detect() method (or some other algorithm).
* For N detected barcodes, the dimensions of this array should be [N][4].
* Order of four points in vector<Point2f> is bottomLeft, topLeft, topRight, bottomRight.
* @param decoded_info UTF8-encoded output vector of string or empty vector of string if the codes cannot be decoded.
* @param decoded_type vector of BarcodeType, specifies the type of these barcodes
*/
CV_WRAP bool decode(InputArray img, InputArray points, CV_OUT std::vector<std::string> &decoded_info, CV_OUT
std::vector<BarcodeType> &decoded_type) const;
/** @brief Both detects and decodes barcode
* @param img grayscale or color (BGR) image containing barcode.
* @param decoded_info UTF8-encoded output vector of string(s) or empty vector of string if the codes cannot be decoded.
* @param decoded_type vector of BarcodeType, specifies the type of these barcodes
* @param points optional output vector of vertices of the found barcode rectangle. Will be empty if not found.
*/
CV_WRAP bool detectAndDecode(InputArray img, CV_OUT std::vector<std::string> &decoded_info, CV_OUT
std::vector<BarcodeType> &decoded_type, OutputArray points = noArray()) const;
protected:
struct Impl;
Ptr<Impl> p;
};
//! @}
}
} // cv::barcode::
#endif //__OPENCV_BARCODE_HPP__

@ -1 +0,0 @@
misc/java/src/cpp/barcode_converters.hpp

@ -1,12 +0,0 @@
{
"type_dict": {
"vector_BarcodeType": {
"j_type": "List<Integer>",
"jn_type": "List<Integer>",
"jni_type": "jobject",
"jni_var": "std::vector< cv::barcode::BarcodeType > %(n)s",
"suffix": "Ljava_util_List",
"v_type": "vector_BarcodeType"
}
}
}

@ -1,28 +0,0 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
// Author: darkliang
#include "barcode_converters.hpp"
#define LOG_TAG "org.opencv.barcode"
void Copy_vector_BarcodeType_to_List(JNIEnv* env, std::vector<cv::barcode::BarcodeType>& vs, jobject list)
{
static jclass juArrayList = ARRAYLIST(env);
jmethodID m_add = LIST_ADD(env, juArrayList);
jmethodID m_clear = LIST_CLEAR(env, juArrayList);
env->CallVoidMethod(list, m_clear);
jclass jInteger = env->FindClass("java/lang/Integer");
jmethodID m_create_Integer = CONSTRUCTOR(env, jInteger);
for (size_t i = 0; i < vs.size(); ++i)
{
jobject element = env->NewObject(jInteger, m_create_Integer, vs[i]);
env->CallBooleanMethod(list, m_add, element);
env->DeleteLocalRef(element);
}
}

@ -1,20 +0,0 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
// Author: darkliang
#ifndef BARCODE_CONVERTERS_HPP
#define BARCODE_CONVERTERS_HPP
#include <jni.h>
#include "opencv_java.hpp"
#include "opencv2/core.hpp"
#include "opencv2/barcode.hpp"
using namespace cv::barcode;
void Copy_vector_BarcodeType_to_List(JNIEnv* env, std::vector<cv::barcode::BarcodeType>& vs, jobject list);
#endif /* BARCODE_CONVERTERS_HPP */

@ -1,51 +0,0 @@
package org.opencv.test.barcode;
import java.util.List;
import org.opencv.core.Mat;
import org.opencv.barcode.BarcodeDetector;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.test.OpenCVTestCase;
import java.util.ArrayList;
import static org.opencv.barcode.Barcode.EAN_13;
public class BarcodeDetectorTest extends OpenCVTestCase {
private final static String ENV_OPENCV_TEST_DATA_PATH = "OPENCV_TEST_DATA_PATH";
private String testDataPath;
@Override
protected void setUp() throws Exception {
super.setUp();
testDataPath = System.getenv(ENV_OPENCV_TEST_DATA_PATH);
if (testDataPath == null)
throw new Exception(ENV_OPENCV_TEST_DATA_PATH + " has to be defined!");
}
public void testDetectAndDecode() {
Mat img = Imgcodecs.imread(testDataPath + "/cv/barcode/multiple/4_barcodes.jpg");
assertFalse(img.empty());
BarcodeDetector detector = new BarcodeDetector();
assertNotNull(detector);
List < String > infos = new ArrayList< String >();
List < Integer > types = new ArrayList< Integer >();
boolean result = detector.detectAndDecode(img, infos, types);
assertTrue(result);
assertEquals(infos.size(), 4);
assertEquals(types.size(), 4);
final String[] correctResults = {"9787122276124", "9787118081473", "9787564350840", "9783319200064"};
for (int i = 0; i < 4; i++) {
assertEquals(types.get(i).intValue(), EAN_13);
result = false;
for (int j = 0; j < 4; j++) {
if (correctResults[j].equals(infos.get(i))) {
result = true;
break;
}
}
assertTrue(result);
}
}
}

@ -1,22 +0,0 @@
#ifdef HAVE_OPENCV_BARCODE
typedef std::vector<cv::barcode::BarcodeType> vector_BarcodeType;
template<> struct pyopencvVecConverter<cv::barcode::BarcodeType>
{
static bool to(PyObject* obj, std::vector<cv::barcode::BarcodeType>& value, const ArgInfo& info)
{
return pyopencv_to_generic_vec(obj, value, info);
}
static PyObject* from(const std::vector<cv::barcode::BarcodeType>& value)
{
return pyopencv_from_generic_vec(value);
}
};
template<>
bool pyopencv_to(PyObject *o, std::vector<cv::barcode::BarcodeType>& types, const ArgInfo& info)
{
return pyopencvVecConverter<cv::barcode::BarcodeType>::to(o, types, info);
}
#endif // HAVE_OPENCV_BARCODE

@ -1,30 +0,0 @@
#!/usr/bin/env python
'''
===============================================================================
Barcode detect and decode pipeline.
===============================================================================
'''
import os
import numpy as np
import cv2 as cv
from tests_common import NewOpenCVTests
class barcode_detector_test(NewOpenCVTests):
def test_detect(self):
img = cv.imread(os.path.join(self.extraTestDataPath, 'cv/barcode/multiple/4_barcodes.jpg'))
self.assertFalse(img is None)
detector = cv.barcode_BarcodeDetector()
retval, corners = detector.detect(img)
self.assertTrue(retval)
self.assertEqual(corners.shape, (4, 4, 2))
def test_detect_and_decode(self):
img = cv.imread(os.path.join(self.extraTestDataPath, 'cv/barcode/single/book.jpg'))
self.assertFalse(img is None)
detector = cv.barcode_BarcodeDetector()
retval, decoded_info, decoded_type, corners = detector.detectAndDecode(img)
self.assertEqual(decoded_info[0], "9787115279460")
self.assertEqual(decoded_type[0], cv.barcode.EAN_13)
self.assertEqual(corners.shape, (1, 4, 2))

@ -1,113 +0,0 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#include "perf_precomp.hpp"
namespace opencv_test{namespace{
typedef ::perf::TestBaseWithParam< tuple<string, cv::Size> > Perf_Barcode_multi;
typedef ::perf::TestBaseWithParam< tuple<string, cv::Size> > Perf_Barcode_single;
PERF_TEST_P_(Perf_Barcode_multi, detect)
{
const string root = "cv/barcode/multiple/";
const string name_current_image = get<0>(GetParam());
const cv::Size sz = get<1>(GetParam());
const string image_path = findDataFile(root + name_current_image);
Mat src = imread(image_path);
ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path;
cv::resize(src, src, sz);
vector< Point > corners;
auto bardet = barcode::BarcodeDetector();
bool res = false;
TEST_CYCLE()
{
res = bardet.detect(src, corners);
}
SANITY_CHECK_NOTHING();
ASSERT_TRUE(res);
}
PERF_TEST_P_(Perf_Barcode_multi, detect_decode)
{
const string root = "cv/barcode/multiple/";
const string name_current_image = get<0>(GetParam());
const cv::Size sz = get<1>(GetParam());
const string image_path = findDataFile(root + name_current_image);
Mat src = imread(image_path);
ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path;
cv::resize(src, src, sz);
vector<cv::String> decoded_info;
vector<barcode::BarcodeType> decoded_type;
vector< Point > corners;
auto bardet = barcode::BarcodeDetector();
bool res = false;
TEST_CYCLE()
{
res = bardet.detectAndDecode(src, decoded_info, decoded_type, corners);
}
SANITY_CHECK_NOTHING();
ASSERT_TRUE(res);
}
PERF_TEST_P_(Perf_Barcode_single, detect)
{
const string root = "cv/barcode/single/";
const string name_current_image = get<0>(GetParam());
const cv::Size sz = get<1>(GetParam());
const string image_path = findDataFile(root + name_current_image);
Mat src = imread(image_path);
ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path;
cv::resize(src, src, sz);
vector< Point > corners;
auto bardet = barcode::BarcodeDetector();
bool res = false;
TEST_CYCLE()
{
res = bardet.detect(src, corners);
}
SANITY_CHECK_NOTHING();
ASSERT_TRUE(res);
}
PERF_TEST_P_(Perf_Barcode_single, detect_decode)
{
const string root = "cv/barcode/single/";
const string name_current_image = get<0>(GetParam());
const cv::Size sz = get<1>(GetParam());
const string image_path = findDataFile(root + name_current_image);
Mat src = imread(image_path);
ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path;
cv::resize(src, src, sz);
vector<cv::String> decoded_info;
vector<barcode::BarcodeType> decoded_type;
vector< Point > corners;
auto bardet = barcode::BarcodeDetector();
bool res = false;
TEST_CYCLE()
{
res = bardet.detectAndDecode(src, decoded_info, decoded_type, corners);
}
SANITY_CHECK_NOTHING();
ASSERT_TRUE(res);
}
INSTANTIATE_TEST_CASE_P(/*nothing*/, Perf_Barcode_multi,
testing::Combine(
testing::Values("4_barcodes.jpg"),
testing::Values(cv::Size(2041, 2722), cv::Size(1361, 1815), cv::Size(680, 907))));
INSTANTIATE_TEST_CASE_P(/*nothing*/, Perf_Barcode_single,
testing::Combine(
testing::Values("book.jpg", "bottle_1.jpg", "bottle_2.jpg"),
testing::Values(cv::Size(480, 360), cv::Size(640, 480), cv::Size(800, 600))));
}} //namespace

@ -1,9 +0,0 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#include "perf_precomp.hpp"
using namespace perf;
CV_PERF_TEST_MAIN(barcode)

@ -1,11 +0,0 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#ifndef __OPENCV_PERF_PRECOMP_HPP__
#define __OPENCV_PERF_PRECOMP_HPP__
#include "opencv2/ts.hpp"
#include "opencv2/barcode.hpp"
#endif

@ -1,298 +0,0 @@
#include <iostream>
#include "opencv2/barcode.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
using namespace cv;
using namespace std;
static int liveBarCodeDetect();
static int imageBarCodeDetect(const string &in_file);
static bool g_detectOnly = false;
static string g_out_file_name, g_out_file_ext;
static Ptr<barcode::BarcodeDetector> bardet;
int main(int argc, char **argv)
{
const string keys = "{h help ? | | print help messages }"
"{i in | | input image path (also switches to image detection mode) }"
"{detect | false | detect 1D barcode only (skip decoding) }"
"{o out | | path to result file (only for single image decode) }"
"{sr_prototxt| | super resolution prototxt path }"
"{sr_model | | super resolution model path }";
CommandLineParser cmd_parser(argc, argv, keys);
cmd_parser.about("This program detects the 1D barcodes from camera or images using the OpenCV library.");
if (cmd_parser.has("help"))
{
cmd_parser.printMessage();
return 0;
}
string in_file_name = cmd_parser.get<string>("in"); // path to input image
string sr_prototxt = cmd_parser.get<string>("sr_prototxt"); // path to sr_prototxt
string sr_model = cmd_parser.get<string>("sr_model"); // path to sr_model
if (cmd_parser.has("out"))
{
std::string fpath = cmd_parser.get<string>("out"); // path to output image
std::string::size_type idx = fpath.rfind('.');
if (idx != std::string::npos)
{
g_out_file_name = fpath.substr(0, idx);
g_out_file_ext = fpath.substr(idx);
}
else
{
g_out_file_name = fpath;
g_out_file_ext = ".png";
}
}
if (!cmd_parser.check())
{
cmd_parser.printErrors();
return -1;
}
g_detectOnly = cmd_parser.has("detect") && cmd_parser.get<bool>("detect");
//! [initialize]
try{
bardet = makePtr<barcode::BarcodeDetector>(sr_prototxt, sr_model);
} catch (const std::exception& e)
{
cout <<
"\n---------------------------------------------------------------\n"
"Failed to initialize super resolution.\n"
"Please, download 'sr.*' from\n"
"https://github.com/WeChatCV/opencv_3rdparty/tree/wechat_qrcode\n"
"and put them into the current directory.\n"
"Or you can leave sr_prototxt and sr_model unspecified.\n"
"---------------------------------------------------------------\n";
cout << e.what() << endl;
return -1;
}
//! [initialize]
int return_code;
if (in_file_name.empty())
{
return_code = liveBarCodeDetect();
}
else
{
return_code = imageBarCodeDetect(in_file_name);
}
return return_code;
}
static void drawBarcodeContour(Mat &color_image, const vector<Point> &corners, bool decodable)
{
if (!corners.empty())
{
double show_radius = (color_image.rows > color_image.cols) ? (2.813 * color_image.rows) / color_image.cols :
(2.813 * color_image.cols) / color_image.rows;
double contour_radius = show_radius * 0.4;
vector<vector<Point> > contours;
contours.push_back(corners);
drawContours(color_image, contours, 0, decodable ? Scalar(0, 255, 0) : Scalar(0, 0, 255),
cvRound(contour_radius));
RNG rng(1000);
for (size_t i = 0; i < 4; i++)
{
Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
circle(color_image, corners[i], cvRound(show_radius), color, -1);
}
}
}
//! [visualize]
static void drawFPS(Mat &color_image, double fps)
{
ostringstream convert;
convert << cv::format("%.2f", fps) << " FPS (" << (g_detectOnly ? " detector" : " decoder") << ")";
putText(color_image, convert.str(), Point(25, 25), FONT_HERSHEY_DUPLEX, 1, Scalar(0, 0, 255), 2);
}
static void drawBarcodeResults(Mat &frame, const vector<Point> &corners, const vector<cv::String> &decode_info,
const vector<cv::barcode::BarcodeType> &decode_type, double fps)
{
if (!corners.empty())
{
for (size_t i = 0; i < corners.size(); i += 4)
{
size_t bar_idx = i / 4;
vector<Point> barcode_contour(corners.begin() + i, corners.begin() + i + 4);
drawBarcodeContour(frame, barcode_contour, g_detectOnly || decode_type[bar_idx] != barcode::NONE);
cout << "BAR[" << bar_idx << "] @ " << Mat(barcode_contour).reshape(2, 1) << ": ";
if (decode_info.size() > bar_idx)
{
if (!decode_info[bar_idx].empty())
{
cout << "TYPE: " << decode_type[bar_idx] << " INFO: " << decode_info[bar_idx] << endl;
}
else
{
cout << "can't decode 1D barcode" << endl;
}
}
else
{
cout << "decode information is not available (disabled)" << endl;
}
}
}
else
{
cout << "Barcode is not detected" << endl;
}
drawFPS(frame, fps);
}
//! [visualize]
static void
runBarcode(const Mat &input, vector<Point> &corners, vector<cv::String> &decode_info,
vector<cv::barcode::BarcodeType> &decode_type
)
{
if (!g_detectOnly)
{
//! [detectAndDecode]
bool result_detection = bardet->detectAndDecode(input, decode_info, decode_type, corners);
//! [detectAndDecode]
CV_UNUSED(result_detection);
}
else
{
//! [detect]
bool result_detection = bardet->detect(input, corners);
//! [detect]
CV_UNUSED(result_detection);
}
}
int liveBarCodeDetect()
{
VideoCapture cap(0);
if (!cap.isOpened())
{
cout << "Cannot open a camera" << endl;
return 2;
}
cout << "Press 'd' to switch between decoder and detector" << endl;
cout << "Press 'ESC' to exit" << endl;
for (;;)
{
Mat frame;
cap >> frame;
if (frame.empty())
{
cout << "End of video stream" << endl;
break;
}
Mat result;
if (frame.channels() == 1)
{
cvtColor(frame, result, COLOR_GRAY2BGR);
}
else
{
frame.copyTo(result);
}
TickMeter timer;
//! [output]
vector<cv::String> decode_info;
vector<barcode::BarcodeType> decoded_type;
vector<Point> corners;
//! [output]
timer.start();
runBarcode(frame, corners, decode_info, decoded_type);
timer.stop();
double fps = 1 / timer.getTimeSec();
drawBarcodeResults(result, corners, decode_info, decoded_type, fps);
if (!result.empty())
{
imshow("barcode", result);
}
int code = waitKey(1);
if (code < 0)
{
continue;
} // timeout
char c = (char) code;
if (c == 'd')
{
g_detectOnly = !g_detectOnly;
cout << "Switching barcode decoder mode ==> " << (g_detectOnly ? "detect" : "decode") << endl;
}
if (c == 27)
{
cout << "'ESC' is pressed. Exiting..." << endl;
break;
}
}
cout << "Exit." << endl;
return 0;
}
int imageBarCodeDetect(const string &in_file)
{
const int count_experiments = 10;
Mat input = imread(in_file, IMREAD_COLOR);
cout << "Run BarCode" << (g_detectOnly ? " detector" : " decoder") << " on image: " << input.size() << " ("
<< typeToString(input.type()) << ")" << endl;
vector<Point> corners;
vector<cv::String> decode_info;
vector<barcode::BarcodeType> decoded_type;
TickMeter timer;
for (size_t i = 0; i < count_experiments; i++)
{
corners.clear();
decode_info.clear();
timer.start();
runBarcode(input, corners, decode_info, decoded_type);
timer.stop();
}
double fps = count_experiments / timer.getTimeSec();
cout << "FPS: " << fps << endl;
Mat result;
input.copyTo(result);
drawBarcodeResults(result, corners, decode_info, decoded_type, fps);
if (!g_out_file_name.empty())
{
string out_file = g_out_file_name + g_out_file_ext;
cout << "Saving result: " << out_file << endl;
imwrite(out_file, result);
}
imshow("barcode", result);
waitKey(1);
cout << "Press any key to exit ..." << endl;
waitKey(0);
cout << "Exit." << endl;
return 0;
}

@ -1,272 +0,0 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
// Copyright (c) 2020-2021 darkliang wangberlinT Certseeds
#include "precomp.hpp"
#include <opencv2/barcode.hpp>
#include <opencv2/core/utils/filesystem.hpp>
#include "decoder/ean13_decoder.hpp"
#include "decoder/ean8_decoder.hpp"
#include "detector/bardetect.hpp"
#include "decoder/common/super_scale.hpp"
#include "decoder/common/utils.hpp"
namespace cv {
namespace barcode {
static bool checkBarInputImage(InputArray img, Mat &gray)
{
CV_Assert(!img.empty());
CV_CheckDepthEQ(img.depth(), CV_8U, "");
if (img.cols() <= 40 || img.rows() <= 40)
{
return false; // image data is not enough for providing reliable results
}
int incn = img.channels();
CV_Check(incn, incn == 1 || incn == 3 || incn == 4, "");
if (incn == 3 || incn == 4)
{
cvtColor(img, gray, COLOR_BGR2GRAY);
}
else
{
gray = img.getMat();
}
return true;
}
static void updatePointsResult(OutputArray points_, const vector<Point2f> &points)
{
if (points_.needed())
{
int N = int(points.size() / 4);
if (N > 0)
{
Mat m_p(N, 4, CV_32FC2, (void *) &points[0]);
int points_type = points_.fixedType() ? points_.type() : CV_32FC2;
m_p.reshape(2, points_.rows()).convertTo(points_, points_type); // Mat layout: N x 4 x 2cn
}
else
{
points_.release();
}
}
}
inline const std::array<std::shared_ptr<AbsDecoder>, 2> &getDecoders()
{
//indicate Decoder
static const std::array<std::shared_ptr<AbsDecoder>, 2> decoders{
std::shared_ptr<AbsDecoder>(new Ean13Decoder()), std::shared_ptr<AbsDecoder>(new Ean8Decoder())};
return decoders;
}
class BarDecode
{
public:
void init(const vector<Mat> &bar_imgs_);
const vector<Result> &getDecodeInformation()
{ return result_info; }
bool decodeMultiplyProcess();
private:
vector<Mat> bar_imgs;
vector<Result> result_info;
};
void BarDecode::init(const vector<Mat> &bar_imgs_)
{
bar_imgs = bar_imgs_;
}
bool BarDecode::decodeMultiplyProcess()
{
static float constexpr THRESHOLD_CONF = 0.6f;
result_info.clear();
result_info.resize(bar_imgs.size());
parallel_for_(Range(0, int(bar_imgs.size())), [&](const Range &range) {
for (int i = range.start; i < range.end; i++)
{
Mat bin_bar;
Result max_res;
float max_conf = -1.f;
bool decoded = false;
for (const auto &decoder:getDecoders())
{
if (decoded)
{ break; }
for (const auto binary_type : binary_types)
{
binarize(bar_imgs[i], bin_bar, binary_type);
auto cur_res = decoder->decodeROI(bin_bar);
if (cur_res.second > max_conf)
{
max_res = cur_res.first;
max_conf = cur_res.second;
if (max_conf > THRESHOLD_CONF)
{
// code decoded
decoded = true;
break;
}
}
} //binary types
} //decoder types
result_info[i] = max_res;
}
});
return !result_info.empty();
}
struct BarcodeDetector::Impl
{
public:
Impl() = default;
~Impl() = default;
vector<Mat> initDecode(const Mat &src, const vector<vector<Point2f>> &points) const;
std::shared_ptr<SuperScale> sr;
bool use_nn_sr = false;
};
// return cropped and scaled bar img
vector<Mat> BarcodeDetector::Impl::initDecode(const Mat &src, const vector<vector<Point2f>> &points) const
{
vector<Mat> bar_imgs;
for (auto &corners : points)
{
Mat bar_img;
cropROI(src, bar_img, corners);
// sharpen(bar_img, bar_img);
// empirical settings
if (bar_img.cols < 320 || bar_img.cols > 640)
{
float scale = 560.0f / static_cast<float>(bar_img.cols);
sr->processImageScale(bar_img, bar_img, scale, use_nn_sr);
}
bar_imgs.emplace_back(bar_img);
}
return bar_imgs;
}
BarcodeDetector::BarcodeDetector(const string &prototxt_path, const string &model_path) : p(new Impl)
{
if (!prototxt_path.empty() && !model_path.empty())
{
CV_Assert(utils::fs::exists(prototxt_path));
CV_Assert(utils::fs::exists(model_path));
p->sr = std::make_shared<SuperScale>();
int res = p->sr->init(prototxt_path, model_path);
CV_Assert(res == 0);
p->use_nn_sr = true;
}
}
BarcodeDetector::~BarcodeDetector() = default;
bool BarcodeDetector::detect(InputArray img, OutputArray points) const
{
Mat inarr;
if (!checkBarInputImage(img, inarr))
{
points.release();
return false;
}
Detect bardet;
bardet.init(inarr);
bardet.localization();
if (!bardet.computeTransformationPoints())
{ return false; }
vector<vector<Point2f>> pnts2f = bardet.getTransformationPoints();
vector<Point2f> trans_points;
for (auto &i : pnts2f)
{
for (const auto &j : i)
{
trans_points.push_back(j);
}
}
updatePointsResult(points, trans_points);
return true;
}
bool BarcodeDetector::decode(InputArray img, InputArray points, vector<std::string> &decoded_info,
vector<BarcodeType> &decoded_type) const
{
Mat inarr;
if (!checkBarInputImage(img, inarr))
{
return false;
}
CV_Assert(points.size().width > 0);
CV_Assert((points.size().width % 4) == 0);
vector<vector<Point2f>> src_points;
Mat bar_points = points.getMat();
bar_points = bar_points.reshape(2, 1);
for (int i = 0; i < bar_points.size().width; i += 4)
{
vector<Point2f> tempMat = bar_points.colRange(i, i + 4);
if (contourArea(tempMat) > 0.0)
{
src_points.push_back(tempMat);
}
}
CV_Assert(!src_points.empty());
vector<Mat> bar_imgs = p->initDecode(inarr, src_points);
BarDecode bardec;
bardec.init(bar_imgs);
bardec.decodeMultiplyProcess();
const vector<Result> info = bardec.getDecodeInformation();
decoded_info.clear();
decoded_type.clear();
bool ok = false;
for (const auto &res : info)
{
if (res.format != NONE)
{
ok = true;
}
decoded_info.emplace_back(res.result);
decoded_type.emplace_back(res.format);
}
return ok;
}
bool
BarcodeDetector::detectAndDecode(InputArray img, vector<std::string> &decoded_info, vector<BarcodeType> &decoded_type,
OutputArray points_) const
{
Mat inarr;
if (!checkBarInputImage(img, inarr))
{
points_.release();
return false;
}
vector<Point2f> points;
bool ok = this->detect(inarr, points);
if (!ok)
{
points_.release();
return false;
}
updatePointsResult(points_, points);
decoded_info.clear();
decoded_type.clear();
ok = this->decode(inarr, points, decoded_info, decoded_type);
return ok;
}
}// namespace barcode
} // namespace cv

@ -1,118 +0,0 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
// Copyright (c) 2020-2021 darkliang wangberlinT Certseeds
#include "../precomp.hpp"
#include "abs_decoder.hpp"
namespace cv {
namespace barcode {
void cropROI(const Mat &src, Mat &dst, const std::vector<Point2f> &rects)
{
std::vector<Point2f> vertices = rects;
int height = cvRound(norm(vertices[0] - vertices[1]));
int width = cvRound(norm(vertices[1] - vertices[2]));
if (height > width)
{
std::swap(height, width);
Point2f v0 = vertices[0];
vertices.erase(vertices.begin());
vertices.push_back(v0);
}
std::vector<Point2f> dst_vertices{
Point2f(0, (float) (height - 1)), Point2f(0, 0), Point2f((float) (width - 1), 0),
Point2f((float) (width - 1), (float) (height - 1))};
dst.create(Size(width, height), CV_8UC1);
Mat M = getPerspectiveTransform(vertices, dst_vertices);
warpPerspective(src, dst, M, dst.size(), cv::INTER_LINEAR, BORDER_CONSTANT, Scalar(255));
}
void fillCounter(const std::vector<uchar> &row, uint start, Counter &counter)
{
size_t counter_length = counter.pattern.size();
std::fill(counter.pattern.begin(), counter.pattern.end(), 0);
counter.sum = 0;
size_t end = row.size();
uchar color = row[start];
uint counterPosition = 0;
while (start < end)
{
if (row[start] == color)
{ // that is, exactly one is true
counter.pattern[counterPosition]++;
counter.sum++;
}
else
{
counterPosition++;
if (counterPosition == counter_length)
{
break;
}
else
{
counter.pattern[counterPosition] = 1;
counter.sum++;
color = 255 - color;
}
}
++start;
}
}
static inline uint
patternMatchVariance(const Counter &counter, const std::vector<int> &pattern, uint maxIndividualVariance)
{
size_t numCounters = counter.pattern.size();
int total = static_cast<int>(counter.sum);
int patternLength = std::accumulate(pattern.cbegin(), pattern.cend(), 0);
if (total < patternLength)
{
// If we don't even have one pixel per unit of bar width, assume this is too small
// to reliably match, so fail:
// and use constexpr functions
return WHITE;// max
}
// We're going to fake floating-point math in integers. We just need to use more bits.
// Scale up patternLength so that intermediate values below like scaledCounter will have
// more "significant digits"
int unitBarWidth = (total << INTEGER_MATH_SHIFT) / patternLength;
maxIndividualVariance = (maxIndividualVariance * unitBarWidth) >> INTEGER_MATH_SHIFT;
uint totalVariance = 0;
for (uint x = 0; x < numCounters; x++)
{
int cnt = counter.pattern[x] << INTEGER_MATH_SHIFT;
int scaledPattern = pattern[x] * unitBarWidth;
uint variance = std::abs(cnt - scaledPattern);
if (variance > maxIndividualVariance)
{
return WHITE;
}
totalVariance += variance;
}
return totalVariance / total;
}
/**
* Determines how closely a set of observed counts of runs of black/white values matches a given
* target pattern. This is reported as the ratio of the total variance from the expected pattern
* proportions across all pattern elements, to the length of the pattern.
*
* @param counters observed counters
* @param pattern expected pattern
* @param maxIndividualVariance The most any counter can differ before we give up
* @return ratio of total variance between counters and pattern compared to total pattern size,
* where the ratio has been multiplied by 256. So, 0 means no variance (perfect match); 256 means
* the total variance between counters and patterns equals the pattern length, higher values mean
* even more variance
*/
uint patternMatch(const Counter &counters, const std::vector<int> &pattern, uint maxIndividual)
{
CV_Assert(counters.pattern.size() == pattern.size());
return patternMatchVariance(counters, pattern, maxIndividual);
}
}
}

@ -1,73 +0,0 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
// Copyright (c) 2020-2021 darkliang wangberlinT Certseeds
#ifndef __OPENCV_BARCODE_ABS_DECODER_HPP__
#define __OPENCV_BARCODE_ABS_DECODER_HPP__
#include <opencv2/barcode.hpp>
namespace cv {
namespace barcode {
using std::string;
using std::vector;
constexpr static uchar BLACK = std::numeric_limits<uchar>::min();
// WHITE elemental area is 0xff
constexpr static uchar WHITE = std::numeric_limits<uchar>::max();
struct Result
{
std::string result;
BarcodeType format = BarcodeType::NONE;
Result() = default;
Result(const std::string &_result, BarcodeType _format)
{
result = _result;
format = _format;
}
};
struct Counter
{
std::vector<int> pattern;
uint sum;
explicit Counter(const vector<int> &_pattern)
{
pattern = _pattern;
sum = 0;
}
};
class AbsDecoder
{
public:
virtual std::pair<Result, float> decodeROI(const Mat &bar_img) const = 0;
virtual ~AbsDecoder() = default;
protected:
virtual Result decode(const vector<uchar> &data) const = 0;
virtual bool isValid(const string &result) const = 0;
size_t bits_num{};
size_t digit_number{};
};
void cropROI(const Mat &_src, Mat &_dst, const std::vector<Point2f> &rect);
void fillCounter(const std::vector<uchar> &row, uint start, Counter &counter);
constexpr static uint INTEGER_MATH_SHIFT = 8;
constexpr static uint PATTERN_MATCH_RESULT_SCALE_FACTOR = 1 << INTEGER_MATH_SHIFT;
uint patternMatch(const Counter &counters, const std::vector<int> &pattern, uint maxIndividual);
}
} // namespace cv
#endif //! __OPENCV_BARCODE_ABS_DECODER_HPP__

@ -1,195 +0,0 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
// Modified from ZXing. Copyright ZXing authors.
// Licensed under the Apache License, Version 2.0 (the "License").
#include "../../precomp.hpp"
#include "hybrid_binarizer.hpp"
namespace cv {
namespace barcode {
#define CLAMP(x, x1, x2) x < (x1) ? (x1) : ((x) > (x2) ? (x2) : (x))
// This class uses 5x5 blocks to compute local luminance, where each block is 8x8 pixels.
// So this is the smallest dimension in each axis we can accept.
constexpr static int BLOCK_SIZE_POWER = 3;
constexpr static int BLOCK_SIZE = 1 << BLOCK_SIZE_POWER; // ...0100...00
constexpr static int BLOCK_SIZE_MASK = BLOCK_SIZE - 1; // ...0011...11
constexpr static int MINIMUM_DIMENSION = BLOCK_SIZE * 5;
constexpr static int MIN_DYNAMIC_RANGE = 24;
void
calculateThresholdForBlock(const std::vector<uchar> &luminances, int sub_width, int sub_height, int width, int height,
const Mat &black_points, Mat &dst)
{
int maxYOffset = height - BLOCK_SIZE;
int maxXOffset = width - BLOCK_SIZE;
for (int y = 0; y < sub_height; y++)
{
int yoffset = y << BLOCK_SIZE_POWER;
if (yoffset > maxYOffset)
{
yoffset = maxYOffset;
}
int top = CLAMP(y, 2, sub_height - 3);
for (int x = 0; x < sub_width; x++)
{
int xoffset = x << BLOCK_SIZE_POWER;
if (xoffset > maxXOffset)
{
xoffset = maxXOffset;
}
int left = CLAMP(x, 2, sub_width - 3);
int sum = 0;
const auto *black_row = black_points.ptr<uchar>(top - 2);
for (int z = 0; z <= 4; z++)
{
sum += black_row[left - 2] + black_row[left - 1] + black_row[left] + black_row[left + 1] +
black_row[left + 2];
black_row += black_points.cols;
}
int average = sum / 25;
int temp_y = 0;
auto *ptr = dst.ptr<uchar>(yoffset, xoffset);
for (int offset = yoffset * width + xoffset; temp_y < 8; offset += width)
{
for (int temp_x = 0; temp_x < 8; ++temp_x)
{
*(ptr + temp_x) = (luminances[offset + temp_x] & 255) <= average ? 0 : 255;
}
++temp_y;
ptr += width;
}
}
}
}
Mat calculateBlackPoints(std::vector<uchar> luminances, int sub_width, int sub_height, int width, int height)
{
int maxYOffset = height - BLOCK_SIZE;
int maxXOffset = width - BLOCK_SIZE;
Mat black_points(Size(sub_width, sub_height), CV_8UC1);
for (int y = 0; y < sub_height; y++)
{
int yoffset = y << BLOCK_SIZE_POWER;
if (yoffset > maxYOffset)
{
yoffset = maxYOffset;
}
for (int x = 0; x < sub_width; x++)
{
int xoffset = x << BLOCK_SIZE_POWER;
if (xoffset > maxXOffset)
{
xoffset = maxXOffset;
}
int sum = 0;
int min = 0xFF;
int max = 0;
for (int yy = 0, offset = yoffset * width + xoffset; yy < BLOCK_SIZE; yy++, offset += width)
{
for (int xx = 0; xx < BLOCK_SIZE; xx++)
{
int pixel = luminances[offset + xx] & 0xFF;
sum += pixel;
// still looking for good contrast
if (pixel < min)
{
min = pixel;
}
if (pixel > max)
{
max = pixel;
}
}
// short-circuit min/max tests once dynamic range is met
if (max - min > MIN_DYNAMIC_RANGE)
{
// finish the rest of the rows quickly
for (yy++, offset += width; yy < BLOCK_SIZE; yy++, offset += width)
{
for (int xx = 0; xx < BLOCK_SIZE; xx++)
{
sum += luminances[offset + xx] & 0xFF;
}
}
}
}
// The default estimate is the average of the values in the block.
int average = sum >> (BLOCK_SIZE_POWER * 2);
if (max - min <= MIN_DYNAMIC_RANGE)
{
// If variation within the block is low, assume this is a block with only light or only
// dark pixels. In that case we do not want to use the average, as it would divide this
// low contrast area into black and white pixels, essentially creating data out of noise.
//
// The default assumption is that the block is light/background. Since no estimate for
// the level of dark pixels exists locally, use half the min for the block.
average = min / 2;
if (y > 0 && x > 0)
{
// Correct the "white background" assumption for blocks that have neighbors by comparing
// the pixels in this block to the previously calculated black points. This is based on
// the fact that dark barcode symbology is always surrounded by some amount of light
// background for which reasonable black point estimates were made. The bp estimated at
// the boundaries is used for the interior.
// The (min < bp) is arbitrary but works better than other heuristics that were tried.
int averageNeighborBlackPoint =
(black_points.at<uchar>(y - 1, x) + (2 * black_points.at<uchar>(y, x - 1)) +
black_points.at<uchar>(y - 1, x - 1)) / 4;
if (min < averageNeighborBlackPoint)
{
average = averageNeighborBlackPoint;
}
}
}
black_points.at<uchar>(y, x) = (uchar) average;
}
}
return black_points;
}
void hybridBinarization(const Mat &src, Mat &dst)
{
int width = src.cols;
int height = src.rows;
if (width >= MINIMUM_DIMENSION && height >= MINIMUM_DIMENSION)
{
std::vector<uchar> luminances(src.begin<uchar>(), src.end<uchar>());
int sub_width = width >> BLOCK_SIZE_POWER;
if ((width & BLOCK_SIZE_MASK) != 0)
{
sub_width++;
}
int sub_height = height >> BLOCK_SIZE_POWER;
if ((height & BLOCK_SIZE_MASK) != 0)
{
sub_height++;
}
Mat black_points = calculateBlackPoints(luminances, sub_width, sub_height, width, height);
dst.create(src.size(), src.type());
calculateThresholdForBlock(luminances, sub_width, sub_height, width, height, black_points, dst);
}
else
{
threshold(src, dst, 155, 255, THRESH_OTSU + THRESH_BINARY);
}
}
}
}

@ -1,22 +0,0 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
// Modified from ZXing. Copyright ZXing authors.
// Licensed under the Apache License, Version 2.0 (the "License").
#ifndef __OPENCV_BARCODE_HYBRID_BINARIZER_HPP__
#define __OPENCV_BARCODE_HYBRID_BINARIZER_HPP__
namespace cv {
namespace barcode {
void hybridBinarization(const Mat &src, Mat &dst);
void
calculateThresholdForBlock(const std::vector<uchar> &luminances, int sub_width, int sub_height, int width, int height,
const Mat &black_points, Mat &dst);
Mat calculateBlackPoints(std::vector<uchar> luminances, int sub_width, int sub_height, int width, int height);
}
}
#endif //__OPENCV_BARCODE_HYBRID_BINARIZER_HPP__

@ -1,75 +0,0 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Tencent is pleased to support the open source community by making WeChat QRCode available.
// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved.
// Modified by darkliang wangberlinT
#include "../../precomp.hpp"
#include "opencv2/dnn.hpp"
#include "super_scale.hpp"
namespace cv {
namespace barcode {
constexpr static float MAX_SCALE = 4.0f;
int SuperScale::init(const std::string &proto_path, const std::string &model_path)
{
srnet_ = dnn::readNetFromCaffe(proto_path, model_path);
net_loaded_ = true;
return 0;
}
void SuperScale::processImageScale(const Mat &src, Mat &dst, float scale, const bool &use_sr, int sr_max_size)
{
scale = min(scale, MAX_SCALE);
if (scale > .0 && scale < 1.0)
{ // down sample
resize(src, dst, Size(), scale, scale, INTER_AREA);
}
else if (scale > 1.5 && scale < 2.0)
{
resize(src, dst, Size(), scale, scale, INTER_CUBIC);
}
else if (scale >= 2.0)
{
int width = src.cols;
int height = src.rows;
if (use_sr && (int) sqrt(width * height * 1.0) < sr_max_size && net_loaded_)
{
superResolutionScale(src, dst);
if (scale > 2.0)
{
processImageScale(dst, dst, scale / 2.0f, use_sr);
}
}
else
{ resize(src, dst, Size(), scale, scale, INTER_CUBIC); }
}
}
int SuperScale::superResolutionScale(const Mat &src, Mat &dst)
{
Mat blob;
dnn::blobFromImage(src, blob, 1.0 / 255, Size(src.cols, src.rows), {0.0f}, false, false);
srnet_.setInput(blob);
auto prob = srnet_.forward();
dst = Mat(prob.size[2], prob.size[3], CV_8UC1);
for (int row = 0; row < prob.size[2]; row++)
{
const float *prob_score = prob.ptr<float>(0, 0, row);
auto *dst_row = dst.ptr<uchar>(row);
for (int col = 0; col < prob.size[3]; col++)
{
dst_row[col] = saturate_cast<uchar>(prob_score[col] * 255.0f);
}
}
return 0;
}
} // namespace barcode
} // namespace cv

@ -1,35 +0,0 @@
/// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Tencent is pleased to support the open source community by making WeChat QRCode available.
// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved.
#ifndef __OPENCV_BARCODE_SUPER_SCALE_HPP__
#define __OPENCV_BARCODE_SUPER_SCALE_HPP__
namespace cv {
namespace barcode {
class SuperScale
{
public:
SuperScale() = default;
~SuperScale() = default;
int init(const std::string &proto_path, const std::string &model_path);
void processImageScale(const Mat &src, Mat &dst, float scale, const bool &use_sr, int sr_max_size = 160);
private:
dnn::Net srnet_;
bool net_loaded_ = false;
int superResolutionScale(const cv::Mat &src, cv::Mat &dst);
};
} // namespace barcode
} // namespace cv
#endif //__OPENCV_BARCODE_SUPER_SCALE_HPP__

@ -1,36 +0,0 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
// Copyright (c) 2020-2021 darkliang wangberlinT Certseeds
#include "../../precomp.hpp"
#include "utils.hpp"
#include "hybrid_binarizer.hpp"
namespace cv {
namespace barcode {
void sharpen(const Mat &src, const Mat &dst)
{
Mat blur;
GaussianBlur(src, blur, Size(0, 0), 25);
addWeighted(src, 2, blur, -1, -20, dst);
}
void binarize(const Mat &src, Mat &dst, BinaryType mode)
{
switch (mode)
{
case OTSU:
threshold(src, dst, 155, 255, THRESH_OTSU + THRESH_BINARY);
break;
case HYBRID:
hybridBinarization(src, dst);
break;
default:
CV_Error(Error::StsNotImplemented, "This binary type is not yet implemented");
}
}
}
}

@ -1,25 +0,0 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
// Copyright (c) 2020-2021 darkliang wangberlinT Certseeds
#ifndef __OPENCV_BARCODE_UTILS_HPP__
#define __OPENCV_BARCODE_UTILS_HPP__
namespace cv {
namespace barcode {
enum BinaryType
{
OTSU = 0, HYBRID = 1
};
static constexpr BinaryType binary_types[] = {OTSU, HYBRID};
void sharpen(const Mat &src, const Mat &dst);
void binarize(const Mat &src, Mat &dst, BinaryType mode);
}
}
#endif //__OPENCV_BARCODE_UTILS_HPP__

@ -1,92 +0,0 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
// Copyright (c) 2020-2021 darkliang wangberlinT Certseeds
#include "../precomp.hpp"
#include "ean13_decoder.hpp"
// three digit decode method from https://baike.baidu.com/item/EAN-13
namespace cv {
namespace barcode {
static constexpr size_t EAN13BITS_NUM = 95;
static constexpr size_t EAN13DIGIT_NUM = 13;
// default thought that mat is a matrix after binary-transfer.
/**
* decode EAN-13
* @prama: data: the input array,
* @prama: start: the index of start order, begin at 0, max-value is data.size()-1
* it scan begin at the data[start]
*/
Result Ean13Decoder::decode(const vector<uchar> &data) const
{
string result;
char decode_result[EAN13DIGIT_NUM + 1]{'\0'};
if (data.size() < EAN13BITS_NUM)
{
return Result("Wrong Size", BarcodeType::NONE);
}
pair<uint, uint> pattern;
if (!findStartGuardPatterns(data, pattern))
{
return Result("Begin Pattern Not Found", BarcodeType::NONE);
}
uint start = pattern.second;
Counter counter(vector<int>{0, 0, 0, 0});
size_t end = data.size();
int first_char_bit = 0;
// [1,6] are left part of EAN, [7,12] are right part, index 0 is calculated by left part
for (int i = 1; i < 7 && start < end; ++i)
{
int bestMatch = decodeDigit(data, counter, start, get_AB_Patterns());
if (bestMatch == -1)
{
return Result("Decode Error", BarcodeType::NONE);
}
decode_result[i] = static_cast<char>('0' + bestMatch % 10);
start = counter.sum + start;
first_char_bit += (bestMatch >= 10) << i;
}
decode_result[0] = static_cast<char>(FIRST_CHAR_ARRAY()[first_char_bit >> 2] + '0');
// why there need >> 2?
// first, the i in for-cycle is begin in 1
// second, the first i = 1 is always
Counter middle_counter(vector<int>(MIDDLE_PATTERN().size()));
if (!findGuardPatterns(data, start, true, MIDDLE_PATTERN(), middle_counter, pattern))
{
return Result("Middle Pattern Not Found", BarcodeType::NONE);
}
start = pattern.second;
for (int i = 0; i < 6 && start < end; ++i)
{
int bestMatch = decodeDigit(data, counter, start, get_A_or_C_Patterns());
if (bestMatch == -1)
{
return Result("Decode Error", BarcodeType::NONE);
}
decode_result[i + 7] = static_cast<char>('0' + bestMatch);
start = counter.sum + start;
}
Counter end_counter(vector<int>(BEGIN_PATTERN().size()));
if (!findGuardPatterns(data, start, false, BEGIN_PATTERN(), end_counter, pattern))
{
return Result("End Pattern Not Found", BarcodeType::NONE);
}
result = string(decode_result);
if (!isValid(result))
{
return Result("Wrong: " + result.append(string(EAN13DIGIT_NUM - result.size(), ' ')), BarcodeType::NONE);
}
return Result(result, BarcodeType::EAN_13);
}
Ean13Decoder::Ean13Decoder()
{
this->bits_num = EAN13BITS_NUM;
this->digit_number = EAN13DIGIT_NUM;
}
}
}

@ -1,31 +0,0 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
// Copyright (c) 2020-2021 darkliang wangberlinT Certseeds
#ifndef __OPENCV_BARCODE_EAN13_DECODER_HPP__
#define __OPENCV_BARCODE_EAN13_DECODER_HPP__
#include "upcean_decoder.hpp"
namespace cv {
namespace barcode {
//extern struct EncodePair;
using std::string;
using std::vector;
using std::pair;
class Ean13Decoder : public UPCEANDecoder
{
public:
Ean13Decoder();
~Ean13Decoder() override = default;
protected:
Result decode(const vector<uchar> &data) const override;
};
}
} // namespace cv
#endif // !__OPENCV_BARCODE_EAN13_DECODER_HPP__

@ -1,79 +0,0 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
// Copyright (c) 2020-2021 darkliang wangberlinT Certseeds
#include "../precomp.hpp"
#include "ean8_decoder.hpp"
namespace cv {
namespace barcode {
static constexpr size_t EAN8BITS_NUM = 70;
static constexpr size_t EAN8DIGIT_NUM = 8;
Result Ean8Decoder::decode(const vector<uchar> &data) const
{
std::string result;
char decode_result[EAN8DIGIT_NUM + 1]{'\0'};
if (data.size() < EAN8BITS_NUM)
{
return Result("Wrong Size", BarcodeType::NONE);
}
pair<uint, uint> pattern;
if (!findStartGuardPatterns(data, pattern))
{
return Result("Begin Pattern Not Found", BarcodeType::NONE);
}
uint start = pattern.second;
Counter counter(vector<int>{0, 0, 0, 0});
size_t end = data.size();
for (int i = 0; i < 4 && start < end; ++i)
{
int bestMatch = decodeDigit(data, counter, start, get_A_or_C_Patterns());
if (bestMatch == -1)
{
return Result("Decode Error", BarcodeType::NONE);
}
decode_result[i] = static_cast<char>('0' + bestMatch % 10);
start = counter.sum + start;
}
Counter middle_counter(vector<int>(MIDDLE_PATTERN().size()));
if (!findGuardPatterns(data, start, true, MIDDLE_PATTERN(), middle_counter, pattern))
{
return Result("Middle Pattern Not Found", BarcodeType::NONE);
}
start = pattern.second;
for (int i = 0; i < 4 && start < end; ++i)
{
int bestMatch = decodeDigit(data, counter, start, get_A_or_C_Patterns());
if (bestMatch == -1)
{
return Result("Decode Error", BarcodeType::NONE);
}
decode_result[i + 4] = static_cast<char>('0' + bestMatch);
start = counter.sum + start;
}
Counter end_counter(vector<int>(BEGIN_PATTERN().size()));
if (!findGuardPatterns(data, start, false, BEGIN_PATTERN(), end_counter, pattern))
{
return Result("End Pattern Not Found", BarcodeType::NONE);
}
result = string(decode_result);
if (!isValid(result))
{
return Result("Wrong: " + result.append(string(EAN8DIGIT_NUM - result.size(), ' ')), BarcodeType::NONE);
}
return Result(result, BarcodeType::EAN_8);
}
Ean8Decoder::Ean8Decoder()
{
this->digit_number = EAN8DIGIT_NUM;
this->bits_num = EAN8BITS_NUM;
}
}
}

@ -1,32 +0,0 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
// Copyright (c) 2020-2021 darkliang wangberlinT Certseeds
#ifndef __OPENCV_BARCODE_EAN8_DECODER_HPP__
#define __OPENCV_BARCODE_EAN8_DECODER_HPP__
#include "upcean_decoder.hpp"
namespace cv {
namespace barcode {
using std::string;
using std::vector;
using std::pair;
class Ean8Decoder : public UPCEANDecoder
{
public:
Ean8Decoder();
~Ean8Decoder() override = default;
protected:
Result decode(const vector<uchar> &data) const override;
};
}
}
#endif //__OPENCV_BARCODE_EAN8_DECODER_HPP__

@ -1,290 +0,0 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
// Copyright (c) 2020-2021 darkliang wangberlinT Certseeds
#include "../precomp.hpp"
#include "upcean_decoder.hpp"
#include <map>
namespace cv {
namespace barcode {
static constexpr int DIVIDE_PART = 15;
static constexpr int BIAS_PART = 2;
#if 0
void UPCEANDecoder::drawDebugLine(Mat &debug_img, const Point2i &begin, const Point2i &end) const
{
Result result;
std::vector<uchar> middle;
LineIterator line = LineIterator(debug_img, begin, end);
middle.reserve(line.count);
for (int cnt = 0; cnt < line.count; cnt++, line++)
{
middle.push_back(debug_img.at<uchar>(line.pos()));
}
std::pair<int, int> start_range;
if (findStartGuardPatterns(middle, start_range))
{
circle(debug_img, Point2i(begin.x + start_range.second, begin.y), 2, Scalar(0), 2);
}
result = this->decode(middle);
if (result.format == BarcodeType::NONE)
{
result = this->decode(std::vector<uchar>(middle.crbegin(), middle.crend()));
}
if (result.format == BarcodeType::NONE)
{
cv::line(debug_img, begin, end, Scalar(0), 2);
cv::putText(debug_img, result.result, begin, cv::FONT_HERSHEY_PLAIN, 1, cv::Scalar(0, 0, 255), 1);
}
}
#endif
bool UPCEANDecoder::findGuardPatterns(const std::vector<uchar> &row, uint rowOffset, uchar whiteFirst,
const std::vector<int> &pattern, Counter &counter, std::pair<uint, uint> &result)
{
size_t patternLength = pattern.size();
size_t width = row.size();
uchar color = whiteFirst ? WHITE : BLACK;
rowOffset = (int) (std::find(row.cbegin() + rowOffset, row.cend(), color) - row.cbegin());
uint counterPosition = 0;
uint patternStart = rowOffset;
for (uint x = rowOffset; x < width; x++)
{
if (row[x] == color)
{
counter.pattern[counterPosition]++;
counter.sum++;
}
else
{
if (counterPosition == patternLength - 1)
{
if (patternMatch(counter, pattern, MAX_INDIVIDUAL_VARIANCE) < MAX_AVG_VARIANCE)
{
result.first = patternStart;
result.second = x;
return true;
}
patternStart += counter.pattern[0] + counter.pattern[1];
counter.sum -= counter.pattern[0] + counter.pattern[1];
std::copy(counter.pattern.begin() + 2, counter.pattern.end(), counter.pattern.begin());
counter.pattern[patternLength - 2] = 0;
counter.pattern[patternLength - 1] = 0;
counterPosition--;
}
else
{
counterPosition++;
}
counter.pattern[counterPosition] = 1;
counter.sum++;
color = (std::numeric_limits<uchar>::max() - color);
}
}
return false;
}
bool UPCEANDecoder::findStartGuardPatterns(const std::vector<uchar> &row, std::pair<uint, uint> &start_range)
{
bool is_find = false;
int next_start = 0;
while (!is_find)
{
Counter guard_counters(std::vector<int>{0, 0, 0});
if (!findGuardPatterns(row, next_start, BLACK, BEGIN_PATTERN(), guard_counters, start_range))
{
return false;
}
int start = static_cast<int>(start_range.first);
next_start = static_cast<int>(start_range.second);
int quiet_start = max(start - (next_start - start), 0);
is_find = (quiet_start != start) &&
(std::find(std::begin(row) + quiet_start, std::begin(row) + start, BLACK) == std::begin(row) + start);
}
return true;
}
int UPCEANDecoder::decodeDigit(const std::vector<uchar> &row, Counter &counters, uint rowOffset,
const std::vector<std::vector<int>> &patterns)
{
fillCounter(row, rowOffset, counters);
int bestMatch = -1;
uint bestVariance = MAX_AVG_VARIANCE; // worst variance we'll accept
int i = 0;
for (const auto &pattern : patterns)
{
uint variance = patternMatch(counters, pattern, MAX_INDIVIDUAL_VARIANCE);
if (variance < bestVariance)
{
bestVariance = variance;
bestMatch = i;
}
i++;
}
return std::max(-1, bestMatch);
// -1 is Mismatch or means error.
}
/*Input a ROI mat return result */
std::pair<Result, float> UPCEANDecoder::decodeROI(const Mat &bar_img) const
{
if ((size_t) bar_img.cols < this->bits_num)
{
return std::make_pair(Result{string(), BarcodeType::NONE}, 0.0F);
}
std::map<std::string, int> result_vote;
std::map<BarcodeType, int> format_vote;
int vote_cnt = 0;
int total_vote = 0;
std::string max_result;
BarcodeType max_type = BarcodeType::NONE;
const int step = bar_img.rows / (DIVIDE_PART + BIAS_PART);
Result result;
int row_num;
for (int i = 0; i < DIVIDE_PART; ++i)
{
row_num = (i + BIAS_PART / 2) * step;
if (row_num < 0 || row_num > bar_img.rows)
{
continue;
}
const auto *ptr = bar_img.ptr<uchar>(row_num);
vector<uchar> line(ptr, ptr + bar_img.cols);
result = decodeLine(line);
if (result.format != BarcodeType::NONE)
{
total_vote++;
result_vote[result.result] += 1;
if (result_vote[result.result] > vote_cnt)
{
vote_cnt = result_vote[result.result];
max_result = result.result;
max_type = result.format;
}
}
}
if (total_vote == 0 || (vote_cnt << 2) < total_vote)
{
return std::make_pair(Result(string(), BarcodeType::NONE), 0.0f);
}
float confidence = (float) vote_cnt / (float) DIVIDE_PART;
//Check if it is UPC-A format
if (max_type == BarcodeType::EAN_13 && max_result[0] == '0')
{
max_result = max_result.substr(1, 12); //UPC-A length 12
max_type = UPC_A;
}
return std::make_pair(Result(max_result, max_type), confidence);
}
Result UPCEANDecoder::decodeLine(const vector<uchar> &line) const
{
Result result = this->decode(line);
if (result.format == BarcodeType::NONE)
{
result = this->decode(std::vector<uchar>(line.crbegin(), line.crend()));
}
return result;
}
bool UPCEANDecoder::isValid(const string &result) const
{
if (result.size() != digit_number)
{
return false;
}
int sum = 0;
for (int index = (int) result.size() - 2, i = 1; index >= 0; index--, i++)
{
int temp = result[index] - '0';
sum += (temp + ((i & 1) != 0 ? temp << 1 : 0));
}
return (result.back() - '0') == ((10 - (sum % 10)) % 10);
}
// right for A
const std::vector<std::vector<int>> &get_A_or_C_Patterns()
{
static const std::vector<std::vector<int>> A_or_C_Patterns{{3, 2, 1, 1}, // 0
{2, 2, 2, 1}, // 1
{2, 1, 2, 2}, // 2
{1, 4, 1, 1}, // 3
{1, 1, 3, 2}, // 4
{1, 2, 3, 1}, // 5
{1, 1, 1, 4}, // 6
{1, 3, 1, 2}, // 7
{1, 2, 1, 3}, // 8
{3, 1, 1, 2} // 9
};
return A_or_C_Patterns;
}
const std::vector<std::vector<int>> &get_AB_Patterns()
{
static const std::vector<std::vector<int>> AB_Patterns = [] {
constexpr uint offset = 10;
auto AB_Patterns_inited = std::vector<std::vector<int>>(offset << 1, std::vector<int>(PATTERN_LENGTH, 0));
std::copy(get_A_or_C_Patterns().cbegin(), get_A_or_C_Patterns().cend(), AB_Patterns_inited.begin());
//AB pattern is
for (uint i = 0; i < offset; ++i)
{
for (uint j = 0; j < PATTERN_LENGTH; ++j)
{
AB_Patterns_inited[i + offset][j] = AB_Patterns_inited[i][PATTERN_LENGTH - j - 1];
}
}
return AB_Patterns_inited;
}();
return AB_Patterns;
}
const std::vector<int> &BEGIN_PATTERN()
{
// it just need it's 1:1:1(black:white:black)
static const std::vector<int> BEGIN_PATTERN_(3, 1);
return BEGIN_PATTERN_;
}
const std::vector<int> &MIDDLE_PATTERN()
{
// it just need it's 1:1:1:1:1(white:black:white:black:white)
static const std::vector<int> MIDDLE_PATTERN_(5, 1);
return MIDDLE_PATTERN_;
}
const std::array<char, 32> &FIRST_CHAR_ARRAY()
{
// use array to simulation a Hashmap,
// because the data's size is small,
// use a hashmap or brute-force search 10 times both can not accept
static const std::array<char, 32> pattern{
'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x06', '\x00', '\x00', '\x00', '\x09', '\x00',
'\x08', '\x03', '\x00', '\x00', '\x00', '\x00', '\x05', '\x00', '\x07', '\x02', '\x00', '\x00', '\x04',
'\x01', '\x00', '\x00', '\x00', '\x00', '\x00'};
// length is 32 to ensure the security
// 0x00000 -> 0 -> 0
// 0x11010 -> 26 -> 1
// 0x10110 -> 22 -> 2
// 0x01110 -> 14 -> 3
// 0x11001 -> 25 -> 4
// 0x10011 -> 19 -> 5
// 0x00111 -> 7 -> 6
// 0x10101 -> 21 -> 7
// 0x01101 -> 13 -> 8
// 0x01011 -> 11 -> 9
// delete the 1-13's 2 number's bit,
// it always be A which do not need to count.
return pattern;
}
}
} // namespace cv

@ -1,67 +0,0 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
// Copyright (c) 2020-2021 darkliang wangberlinT Certseeds
#ifndef __OPENCV_BARCODE_UPCEAN_DECODER_HPP__
#define __OPENCV_BARCODE_UPCEAN_DECODER_HPP__
#include "abs_decoder.hpp"
/**
* upcean_decoder the abstract basic class for decode formats,
* it will have ean13/8,upc_a,upc_e , etc.. class extend this class
*/
namespace cv {
namespace barcode {
using std::string;
using std::vector;
class UPCEANDecoder : public AbsDecoder
{
public:
~UPCEANDecoder() override = default;
std::pair<Result, float> decodeROI(const Mat &bar_img) const override;
protected:
static int decodeDigit(const std::vector<uchar> &row, Counter &counters, uint rowOffset,
const std::vector<std::vector<int>> &patterns);
static bool
findGuardPatterns(const std::vector<uchar> &row, uint rowOffset, uchar whiteFirst, const std::vector<int> &pattern,
Counter &counter, std::pair<uint, uint> &result);
static bool findStartGuardPatterns(const std::vector<uchar> &row, std::pair<uint, uint> &start_range);
Result decodeLine(const vector<uchar> &line) const;
Result decode(const vector<uchar> &bar) const override = 0;
bool isValid(const string &result) const override;
private:
#if 0
void drawDebugLine(Mat &debug_img, const Point2i &begin, const Point2i &end) const;
#endif
};
const std::vector<std::vector<int>> &get_A_or_C_Patterns();
const std::vector<std::vector<int>> &get_AB_Patterns();
const std::vector<int> &BEGIN_PATTERN();
const std::vector<int> &MIDDLE_PATTERN();
const std::array<char, 32> &FIRST_CHAR_ARRAY();
constexpr static uint PATTERN_LENGTH = 4;
constexpr static uint MAX_AVG_VARIANCE = static_cast<uint>(PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.48f);
constexpr static uint MAX_INDIVIDUAL_VARIANCE = static_cast<uint>(PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.7f);
}
} // namespace cv
#endif //! __OPENCV_BARCODE_UPCEAN_DECODER_HPP__

@ -1,410 +0,0 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
// Copyright (c) 2020-2021 darkliang wangberlinT Certseeds
#include "../precomp.hpp"
#include "bardetect.hpp"
namespace cv {
namespace barcode {
static constexpr float PI = static_cast<float>(CV_PI);
static constexpr float HALF_PI = static_cast<float>(CV_PI / 2);
#define CALCULATE_SUM(ptr, result) \
top_left = static_cast<float>(*((ptr) + left_col + integral_cols * top_row));\
top_right = static_cast<float>(*((ptr) + integral_cols * top_row + right_col));\
bottom_right = static_cast<float>(*((ptr) + right_col + bottom_row * integral_cols));\
bottom_left = static_cast<float>(*((ptr) + bottom_row * integral_cols + left_col));\
(result) = (bottom_right - bottom_left - top_right + top_left);
inline bool Detect::isValidCoord(const Point &coord, const Size &limit)
{
if ((coord.x < 0) || (coord.y < 0))
{
return false;
}
if ((unsigned) coord.x > (unsigned) (limit.width - 1) || ((unsigned) coord.y > (unsigned) (limit.height - 1)))
{
return false;
}
return true;
}
void Detect::init(const Mat &src)
{
const double min_side = std::min(src.size().width, src.size().height);
if (min_side > 512.0)
{
purpose = SHRINKING;
coeff_expansion = min_side / 512.0;
width = cvRound(src.size().width / coeff_expansion);
height = cvRound(src.size().height / coeff_expansion);
Size new_size(width, height);
resize(src, resized_barcode, new_size, 0, 0, INTER_AREA);
}
// else if (min_side < 512.0)
// {
// purpose = ZOOMING;
// coeff_expansion = 512.0 / min_side;
// width = cvRound(src.size().width * coeff_expansion);
// height = cvRound(src.size().height * coeff_expansion);
// Size new_size(width, height);
// resize(src, resized_barcode, new_size, 0, 0, INTER_CUBIC);
// }
else
{
purpose = UNCHANGED;
coeff_expansion = 1.0;
width = src.size().width;
height = src.size().height;
resized_barcode = src.clone();
}
// median blur: sometimes it reduces the noise, but also reduces the recall
// medianBlur(resized_barcode, resized_barcode, 3);
}
void Detect::localization()
{
localization_bbox.clear();
bbox_scores.clear();
// get integral image
preprocess();
// empirical setting
static constexpr float SCALE_LIST[] = {0.01f, 0.03f, 0.06f, 0.08f};
const auto min_side = static_cast<float>(std::min(width, height));
int window_size;
for (const float scale:SCALE_LIST)
{
window_size = cvRound(min_side * scale);
if(window_size == 0) {
window_size = 1;
}
calCoherence(window_size);
barcodeErode();
regionGrowing(window_size);
}
}
bool Detect::computeTransformationPoints()
{
bbox_indices.clear();
transformation_points.clear();
transformation_points.reserve(bbox_indices.size());
RotatedRect rect;
Point2f temp[4];
const float THRESHOLD_SCORE = float(width * height) / 300.f;
dnn::NMSBoxes(localization_bbox, bbox_scores, THRESHOLD_SCORE, 0.1f, bbox_indices);
for (const auto &bbox_index : bbox_indices)
{
rect = localization_bbox[bbox_index];
if (purpose == ZOOMING)
{
rect.center /= coeff_expansion;
rect.size.height /= static_cast<float>(coeff_expansion);
rect.size.width /= static_cast<float>(coeff_expansion);
}
else if (purpose == SHRINKING)
{
rect.center *= coeff_expansion;
rect.size.height *= static_cast<float>(coeff_expansion);
rect.size.width *= static_cast<float>(coeff_expansion);
}
rect.points(temp);
transformation_points.emplace_back(vector<Point2f>{temp[0], temp[1], temp[2], temp[3]});
}
return !transformation_points.empty();
}
void Detect::preprocess()
{
Mat scharr_x, scharr_y, temp;
static constexpr double THRESHOLD_MAGNITUDE = 64.;
Scharr(resized_barcode, scharr_x, CV_32F, 1, 0);
Scharr(resized_barcode, scharr_y, CV_32F, 0, 1);
// calculate magnitude of gradient and truncate
magnitude(scharr_x, scharr_y, temp);
threshold(temp, temp, THRESHOLD_MAGNITUDE, 1, THRESH_BINARY);
temp.convertTo(gradient_magnitude, CV_8U);
integral(gradient_magnitude, integral_edges, CV_32F);
for (int y = 0; y < height; y++)
{
auto *const x_row = scharr_x.ptr<float_t>(y);
auto *const y_row = scharr_y.ptr<float_t>(y);
auto *const magnitude_row = gradient_magnitude.ptr<uint8_t>(y);
for (int pos = 0; pos < width; pos++)
{
if (magnitude_row[pos] == 0)
{
x_row[pos] = 0;
y_row[pos] = 0;
continue;
}
if (x_row[pos] < 0)
{
x_row[pos] *= -1;
y_row[pos] *= -1;
}
}
}
integral(scharr_x, temp, integral_x_sq, CV_32F, CV_32F);
integral(scharr_y, temp, integral_y_sq, CV_32F, CV_32F);
integral(scharr_x.mul(scharr_y), integral_xy, temp, CV_32F, CV_32F);
}
// Change coherence orientation edge_nums
// depend on width height integral_edges integral_x_sq integral_y_sq integral_xy
void Detect::calCoherence(int window_size)
{
static constexpr float THRESHOLD_COHERENCE = 0.9f;
int right_col, left_col, top_row, bottom_row;
float xy, x_sq, y_sq, d, rect_area;
const float THRESHOLD_AREA = float(window_size * window_size) * 0.42f;
Size new_size(width / window_size, height / window_size);
coherence = Mat(new_size, CV_8U), orientation = Mat(new_size, CV_32F), edge_nums = Mat(new_size, CV_32F);
float top_left, top_right, bottom_left, bottom_right;
int integral_cols = width + 1;
const auto *edges_ptr = integral_edges.ptr<float_t>(), *x_sq_ptr = integral_x_sq.ptr<float_t>(), *y_sq_ptr = integral_y_sq.ptr<float_t>(), *xy_ptr = integral_xy.ptr<float_t>();
for (int y = 0; y < new_size.height; y++)
{
auto *coherence_row = coherence.ptr<uint8_t>(y);
auto *orientation_row = orientation.ptr<float_t>(y);
auto *edge_nums_row = edge_nums.ptr<float_t>(y);
if (y * window_size >= height)
{
continue;
}
top_row = y * window_size;
bottom_row = min(height, (y + 1) * window_size);
for (int pos = 0; pos < new_size.width; pos++)
{
// then calculate the column locations of the rectangle and set them to -1
// if they are outside the matrix bounds
if (pos * window_size >= width)
{
continue;
}
left_col = pos * window_size;
right_col = min(width, (pos + 1) * window_size);
//we had an integral image to count non-zero elements
CALCULATE_SUM(edges_ptr, rect_area)
if (rect_area < THRESHOLD_AREA)
{
// smooth region
coherence_row[pos] = 0;
continue;
}
CALCULATE_SUM(x_sq_ptr, x_sq)
CALCULATE_SUM(y_sq_ptr, y_sq)
CALCULATE_SUM(xy_ptr, xy)
// get the values of the rectangle corners from the integral image - 0 if outside bounds
d = sqrt((x_sq - y_sq) * (x_sq - y_sq) + 4 * xy * xy) / (x_sq + y_sq);
if (d > THRESHOLD_COHERENCE)
{
coherence_row[pos] = 255;
orientation_row[pos] = atan2(x_sq - y_sq, 2 * xy) / 2.0f;
edge_nums_row[pos] = rect_area;
}
else
{
coherence_row[pos] = 0;
}
}
}
}
// will change localization_bbox bbox_scores
// will change coherence,
// depend on coherence orientation edge_nums
void Detect::regionGrowing(int window_size)
{
static constexpr float LOCAL_THRESHOLD_COHERENCE = 0.95f, THRESHOLD_RADIAN =
PI / 30, LOCAL_RATIO = 0.5f, EXPANSION_FACTOR = 1.2f;
static constexpr uint THRESHOLD_BLOCK_NUM = 35;
Point pt_to_grow, pt; //point to grow
float src_value;
float cur_value;
float edge_num;
float rect_orientation;
float sin_sum, cos_sum;
uint counter;
//grow direction
static constexpr int DIR[8][2] = {{-1, -1},
{0, -1},
{1, -1},
{1, 0},
{1, 1},
{0, 1},
{-1, 1},
{-1, 0}};
vector<Point2f> growingPoints, growingImgPoints;
for (int y = 0; y < coherence.rows; y++)
{
auto *coherence_row = coherence.ptr<uint8_t>(y);
for (int x = 0; x < coherence.cols; x++)
{
if (coherence_row[x] == 0)
{
continue;
}
// flag
coherence_row[x] = 0;
growingPoints.clear();
growingImgPoints.clear();
pt = Point(x, y);
cur_value = orientation.at<float_t>(pt);
sin_sum = sin(2 * cur_value);
cos_sum = cos(2 * cur_value);
counter = 1;
edge_num = edge_nums.at<float_t>(pt);
growingPoints.push_back(pt);
growingImgPoints.push_back(Point(pt));
while (!growingPoints.empty())
{
pt = growingPoints.back();
growingPoints.pop_back();
src_value = orientation.at<float_t>(pt);
//growing in eight directions
for (auto i : DIR)
{
pt_to_grow = Point(pt.x + i[0], pt.y + i[1]);
//check if out of boundary
if (!isValidCoord(pt_to_grow, coherence.size()))
{
continue;
}
if (coherence.at<uint8_t>(pt_to_grow) == 0)
{
continue;
}
cur_value = orientation.at<float_t>(pt_to_grow);
if (abs(cur_value - src_value) < THRESHOLD_RADIAN ||
abs(cur_value - src_value) > PI - THRESHOLD_RADIAN)
{
coherence.at<uint8_t>(pt_to_grow) = 0;
sin_sum += sin(2 * cur_value);
cos_sum += cos(2 * cur_value);
counter += 1;
edge_num += edge_nums.at<float_t>(pt_to_grow);
growingPoints.push_back(pt_to_grow); //push next point to grow back to stack
growingImgPoints.push_back(pt_to_grow);
}
}
}
//minimum block num
if (counter < THRESHOLD_BLOCK_NUM)
{
continue;
}
float local_coherence = (sin_sum * sin_sum + cos_sum * cos_sum) / static_cast<float>(counter * counter);
// minimum local gradient orientation_arg coherence_arg
if (local_coherence < LOCAL_THRESHOLD_COHERENCE)
{
continue;
}
RotatedRect minRect = minAreaRect(growingImgPoints);
if (edge_num < minRect.size.area() * float(window_size * window_size) * LOCAL_RATIO ||
static_cast<float>(counter) < minRect.size.area() * LOCAL_RATIO)
{
continue;
}
const float local_orientation = atan2(cos_sum, sin_sum) / 2.0f;
// only orientation_arg is approximately equal to the rectangle orientation_arg
rect_orientation = (minRect.angle) * PI / 180.f;
if (minRect.size.width < minRect.size.height)
{
rect_orientation += (rect_orientation <= 0.f ? HALF_PI : -HALF_PI);
std::swap(minRect.size.width, minRect.size.height);
}
if (abs(local_orientation - rect_orientation) > THRESHOLD_RADIAN &&
abs(local_orientation - rect_orientation) < PI - THRESHOLD_RADIAN)
{
continue;
}
minRect.angle = local_orientation * 180.f / PI;
minRect.size.width *= static_cast<float>(window_size) * EXPANSION_FACTOR;
minRect.size.height *= static_cast<float>(window_size);
minRect.center.x = (minRect.center.x + 0.5f) * static_cast<float>(window_size);
minRect.center.y = (minRect.center.y + 0.5f) * static_cast<float>(window_size);
localization_bbox.push_back(minRect);
bbox_scores.push_back(edge_num);
}
}
}
inline const std::array<Mat, 4> &getStructuringElement()
{
static const std::array<Mat, 4> structuringElement{
Mat_<uint8_t>{{3, 3},
{255, 0, 0, 0, 0, 0, 0, 0, 255}}, Mat_<uint8_t>{{3, 3},
{0, 0, 255, 0, 0, 0, 255, 0, 0}},
Mat_<uint8_t>{{3, 3},
{0, 0, 0, 255, 0, 255, 0, 0, 0}}, Mat_<uint8_t>{{3, 3},
{0, 255, 0, 0, 0, 0, 0, 255, 0}}};
return structuringElement;
}
// Change mat
void Detect::barcodeErode()
{
static const std::array<Mat, 4> &structuringElement = getStructuringElement();
Mat m0, m1, m2, m3;
dilate(coherence, m0, structuringElement[0]);
dilate(coherence, m1, structuringElement[1]);
dilate(coherence, m2, structuringElement[2]);
dilate(coherence, m3, structuringElement[3]);
int sum;
for (int y = 0; y < coherence.rows; y++)
{
auto coherence_row = coherence.ptr<uint8_t>(y);
auto m0_row = m0.ptr<uint8_t>(y);
auto m1_row = m1.ptr<uint8_t>(y);
auto m2_row = m2.ptr<uint8_t>(y);
auto m3_row = m3.ptr<uint8_t>(y);
for (int pos = 0; pos < coherence.cols; pos++)
{
if (coherence_row[pos] != 0)
{
sum = m0_row[pos] + m1_row[pos] + m2_row[pos] + m3_row[pos];
//more than 2 group
coherence_row[pos] = sum > 600 ? 255 : 0;
}
}
}
}
}
}

@ -1,63 +0,0 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
// Copyright (c) 2020-2021 darkliang wangberlinT Certseeds
#ifndef __OPENCV_BARCODE_BARDETECT_HPP__
#define __OPENCV_BARCODE_BARDETECT_HPP__
#include <opencv2/core.hpp>
#include <opencv2/dnn/dnn.hpp>
namespace cv {
namespace barcode {
using std::vector;
class Detect
{
private:
vector<RotatedRect> localization_rects;
vector<RotatedRect> localization_bbox;
vector<float> bbox_scores;
vector<int> bbox_indices;
vector<vector<Point2f>> transformation_points;
public:
void init(const Mat &src);
void localization();
vector<vector<Point2f>> getTransformationPoints()
{ return transformation_points; }
bool computeTransformationPoints();
protected:
enum resize_direction
{
ZOOMING, SHRINKING, UNCHANGED
} purpose = UNCHANGED;
double coeff_expansion = 1.0;
int height, width;
Mat resized_barcode, gradient_magnitude, coherence, orientation, edge_nums, integral_x_sq, integral_y_sq, integral_xy, integral_edges;
void preprocess();
void calCoherence(int window_size);
static inline bool isValidCoord(const Point &coord, const Size &limit);
void regionGrowing(int window_size);
void barcodeErode();
};
}
}
#endif //__OPENCV_BARCODE_BARDETECT_HPP__

@ -1,16 +0,0 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
// Copyright (c) 2020-2021 darkliang wangberlinT Certseeds
#ifndef __OPENCV_PRECOMP_H__
#define __OPENCV_PRECOMP_H__
#include <opencv2/imgproc.hpp>
#include <numeric>
#include <string>
#ifdef _MSC_VER
#pragma warning(disable: 4244)
#endif
#endif

@ -1,79 +0,0 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#include "test_precomp.hpp"
namespace opencv_test{namespace{
//init validation map
datasetType initValidation(std::string path)
{
const std::string valid_path = findDataFile(path);
return buildDataSet(valid_path);
}
TEST(Barcode_BarcodeDetector_single, regression)
{
const std::string root = "barcode/single/";
datasetType validation = initValidation(root + "result.csv");
auto bardet = barcode::BarcodeDetector();
datasetType::iterator iterator = validation.begin();
while (iterator != validation.end())
{
std::string img_name = iterator->first;
std::string result = iterator->second;
std::string image_path = findDataFile(root + img_name);
Mat img = imread(image_path);
EXPECT_FALSE(img.empty()) << "Can't read image: " << image_path;
std::vector<cv::Point2f> points;
std::vector<std::string> infos;
std::vector<cv::barcode::BarcodeType> formats;
bardet.detectAndDecode(img, infos, formats, points);
EXPECT_FALSE(points.empty()) << "Nothing detected: " << image_path;
bool is_correct = false;
for (const auto &ans : infos)
{
if (ans == result)
{
is_correct = true;
break;
}
}
EXPECT_TRUE(is_correct) << "No results for " << img_name;
iterator++;
}
}
TEST(Barcode_BarcodeDetector_detect_multi, detect_regression)
{
const std::string root = "barcode/multiple/";
datasetType validation = initValidation(root + "result.csv");
auto bardet = barcode::BarcodeDetector();
datasetType::iterator iterator = validation.begin();
while (iterator != validation.end())
{
std::string img = iterator->first;
size_t expect_corners_size = std::stoi(iterator->second);
std::string image_path = findDataFile(root + img);
Mat src = imread(image_path);
EXPECT_FALSE(src.empty()) << "Can't read image: " << image_path;
std::vector<Point> corners;
bardet.detect(src, corners);
EXPECT_EQ(corners.size(), expect_corners_size) << "Can't detect all barcodes: " << img;
iterator++;
}
}
TEST(Barcode_BarcodeDetector_basic, not_found_barcode)
{
auto bardet = barcode::BarcodeDetector();
std::vector<Point> corners;
vector<cv::String> decoded_info;
vector<barcode::BarcodeType> decoded_type;
Mat zero_image = Mat::zeros(256, 256, CV_8UC1);
EXPECT_FALSE(bardet.detect(zero_image, corners));
corners = std::vector<Point>(4);
EXPECT_ANY_THROW(bardet.decode(zero_image, corners, decoded_info, decoded_type));
}
}} // namespace

@ -1,6 +0,0 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#include "test_precomp.hpp"
CV_TEST_MAIN("cv")

@ -1,11 +0,0 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#ifndef __OPENCV_TEST_PRECOMP_HPP__
#define __OPENCV_TEST_PRECOMP_HPP__
#include "opencv2/ts.hpp"
#include "opencv2/barcode.hpp"
#include "utils.hpp"
#endif

@ -1,59 +0,0 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#ifndef OPENCV_CONTRIB_UTILS_HPP
#define OPENCV_CONTRIB_UTILS_HPP
#include <fstream>
#include <iostream>
#include <vector>
#include <map>
typedef std::vector<std::string> stringvec;
typedef std::map<std::string, std::string> datasetType;
namespace opencv_test{namespace {
inline stringvec explode(const std::string &s, const char &c)
{
std::string buff;
stringvec v;
for (auto n:s)
{
if (n != c) { buff += n; }
else if (n == c && !buff.empty())
{
v.push_back(buff);
buff = "";
}
}
if (!buff.empty()) { v.push_back(buff); }
return v;
}
inline datasetType buildDataSet(std::string result_file_path)
{
std::ifstream result_file;
datasetType dataset;
result_file.open(result_file_path);
std::string line;
if (result_file.is_open())
{
while (std::getline(result_file, line))
{
stringvec result = explode(line, ',');
std::string filename = result[0];
if (dataset.find(filename) == dataset.end())
{
dataset[filename] = result[1];
}
}
}
result_file.close();
return dataset;
}
}}
#endif //OPENCV_CONTRIB_UTILS_HPP

@ -1,73 +0,0 @@
Bar code Recognition{#tutorial_barcode_detect_and_decode}
======================
Goal
----
In this chapter,
- We will familiarize with the bar code detection and decoding methods available in OpenCV.
Basics
----
Bar code is major technique to identify commodity in real life. A common bar code is a pattern of parallel lines arranged by black bars and white bars with vastly different reflectivity. Bar code recognition is to scan the bar code in the horizontal direction to get a string of binary codes composed of bars of different widths and colors, that is, the code information of the bar code. The content of bar code can be decoded by matching with various bar code encoding methods. For current work, we only support EAN13 encoding method.
### EAN 13
The EAN-13 bar code is based on the UPC-A standard, which was first implemented in Europe by the International Item Coding Association and later gradually spread worldwide. Most of the common goods in life use EAN-13 barcode.
for more detail see [EAN - Wikipedia](https://en.wikipedia.org/wiki/International_Article_Number)
### BarcodeDetector
Several algorithms were introduced for bar code recognition.
While coding, we firstly need to create a **cv::barcode::BarcodeDetector** object. It has mainly three member functions, which will be introduced in the following.
#### Initilization
User can construct BarcodeDetector with super resolution model which should be downloaded automatically to `<opencv_build_dir>/downloads/barcode`. If not, please download them from `https://github.com/WeChatCV/opencv_3rdparty/tree/wechat_qrcode`
or choose not to use super resolution.
@snippet ./samples/barcode.cpp initialize
We need create variables to store the outputs.
@snippet ./samples/barcode.cpp output
#### detect
It is a algorithm based on directional coherence. First of all, we compute the average squared gradients of every pixels. It was proposed in the paper "Systematic methods for the computation of the directional fields and singular points of fingerprints" by A.M. Bazen and S.H. Gerez in 2002. Then we divide the image into some square patches and compute the **gradient orientation coherence** and **mean gradient direction** of each patch. At last we connected the patches that have **high gradient orientation coherence** and **similar gradient direction**. In this stage, we use multi-scale patches to capture the gradient distribution of multi-size bar codes, and apply non-maximum suppression to filter duplicate proposals. A last, we use minAreaRect() to bound the ROI, and output the corners of the rectangles.
Detect codes in the input image, and output the corners of detected rectangles:
@snippet ./samples/barcode.cpp detect
#### decode
This function first super-scales the image if it is smaller than threshold, sharpens the image and then binaries it by OTSU or local-binarization. At last reads the contents of the barcode by matching the similarity of the specified barcode pattern. Only EAN-13 barcode currently supported.
You can find more information in **cv::barcode::BarcodeDetector::decode()**.
#### detectAndDecode
This function combines `detect` and `decode`. A simple example below to use this function showing recognized bar codes.
@snippet ./samples/barcode.cpp detectAndDecode
Visualize the results:
@snippet ./samples/barcode.cpp visualize
Results
-------
**Original Image**
Below image shows four EAN 13 bar codes photoed by a smart phone.
![image](images/4_barcodes.jpg)
**Result of detectAndDecode**
Bar codes are bounded by green box, and decoded numbers are lying on the boxes.
![image](images/result.jpg)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

@ -1,6 +0,0 @@
Tutorials for barcode module {#tutorial_table_of_content_barcode}
===============================================================
- @subpage tutorial_barcode_detect_and_decode
Bar code is a widely used technology in real goods, we often need to detect and decode the bar code. It can be easy to implement via `barcode` module.
Loading…
Cancel
Save