Merge pull request #23666 from mshabunin:barcode-move

Moved barcode from opencv_contrib #23666

Merge with https://github.com/opencv/opencv_contrib/pull/3497

##### TODO
- [x] Documentation (bib)
- [x] Tutorial (references)
- [x] Sample app (refactored)
- [x] Java (test passes)
- [x] Python (test passes)
- [x] Build without DNN
This commit is contained in:
Maksim Shabunin 2023-06-14 22:21:38 +03:00 committed by GitHub
parent 52f46589a0
commit 463cd09811
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 2904 additions and 2 deletions

View File

@ -0,0 +1,76 @@
Barcode Recognition {#tutorial_barcode_detect_and_decode}
===================
@tableofcontents
@prev_tutorial{tutorial_traincascade}
@next_tutorial{tutorial_introduction_to_svm}
| | |
| -: | :- |
| Compatibility | OpenCV >= 4.8 |
Goal
----
In this chapter we will familiarize with the barcode detection and decoding methods available in OpenCV.
Basics
----
Barcode is major technique to identify commodity in real life. A common barcode is a pattern of parallel lines arranged by black bars and white bars with vastly different reflectivity. Barcode recognition is to scan the barcode 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 barcode. The content of barcode can be decoded by matching with various barcode encoding methods. Currently, we support EAN-8, EAN-13, UPC-A and UPC-E standards.
See https://en.wikipedia.org/wiki/Universal_Product_Code and https://en.wikipedia.org/wiki/International_Article_Number
Related papers: @cite Xiangmin2015research , @cite kass1987analyzing , @cite bazen2002systematic
Code example
------------
### Main class
Several algorithms were introduced for barcode 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.
#### Initialization
Optionally user can construct barcode detector with super resolution model which should be downloaded from https://github.com/WeChatCV/opencv_3rdparty/tree/wechat_qrcode (`sr.caffemodel`, `sr.prototxt`).
@snippet cpp/barcode.cpp initialize
We need to create variables to store the outputs.
@snippet cpp/barcode.cpp output
#### Detecting
cv::barcode::BarcodeDetector::detect method uses an algorithm based on directional coherence. First, we compute the average squared gradients of every pixel, @cite bazen2002systematic . Then we divide an image into square patches and compute the **gradient orientation coherence** and **mean gradient direction** of each patch. Then, we connect all patches that have **high gradient orientation coherence** and **similar gradient direction**. At this stage we use multiscale patches to capture the gradient distribution of multi-size barcodes, and apply non-maximum suppression to filter duplicate proposals. At last, we use cv::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 cpp/barcode.cpp detect
#### Decoding
cv::barcode::BarcodeDetector::decode method first super-scales the image (_optionally_) if it is smaller than threshold, sharpens the image and then binaries it by OTSU or local binarization. Then it reads the contents of the barcode by matching the similarity of the specified barcode pattern.
#### Detecting and decoding
cv::barcode::BarcodeDetector::detectAndDecode combines `detect` and `decode` in a single call. A simple example below shows how to use this function:
@snippet cpp/barcode.cpp detectAndDecode
Visualize the results:
@snippet cpp/barcode.cpp visualize
Results
-------
Original image:
![image](images/barcode_book.jpg)
After detection:
![image](images/barcode_book_res.jpg)

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -3,7 +3,7 @@ Introduction to Support Vector Machines {#tutorial_introduction_to_svm}
@tableofcontents
@prev_tutorial{tutorial_traincascade}
@prev_tutorial{tutorial_barcode_detect_and_decode}
@next_tutorial{tutorial_non_linear_svms}
| | |

View File

@ -8,6 +8,7 @@ Other tutorials (ml, objdetect, photo, stitching, video) {#tutorial_table_of_con
- video. @subpage tutorial_optical_flow
- objdetect. @subpage tutorial_cascade_classifier
- objdetect. @subpage tutorial_traincascade
- objdetect. @subpage tutorial_barcode_detect_and_decode
- ml. @subpage tutorial_introduction_to_svm
- ml. @subpage tutorial_non_linear_svms
- ml. @subpage tutorial_introduction_to_pca

View File

@ -4,7 +4,7 @@ Cascade Classifier Training {#tutorial_traincascade}
@tableofcontents
@prev_tutorial{tutorial_cascade_classifier}
@next_tutorial{tutorial_introduction_to_svm}
@next_tutorial{tutorial_barcode_detect_and_decode}
Introduction
------------

View File

@ -18,3 +18,32 @@
year = {2016},
month = {October}
}
@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}
}

View File

@ -103,6 +103,7 @@ using a Boosted Cascade of Simple Features. IEEE CVPR, 2001. The paper is availa
<https://github.com/SvHey/thesis/blob/master/Literature/ObjectDetection/violaJones_CVPR2001.pdf>
@defgroup objdetect_hog HOG (Histogram of Oriented Gradients) descriptor and object detector
@defgroup objdetect_barcode Barcode detection and decoding
@defgroup objdetect_qrcode QRCode detection and encoding
@defgroup objdetect_dnn_face DNN-based face detection and recognition
Check @ref tutorial_dnn_face "the corresponding tutorial" for more details.
@ -863,5 +864,6 @@ public:
#include "opencv2/objdetect/detection_based_tracker.hpp"
#include "opencv2/objdetect/face.hpp"
#include "opencv2/objdetect/charuco_detector.hpp"
#include "opencv2/objdetect/barcode.hpp"
#endif

View File

@ -0,0 +1,65 @@
// 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_OBJDETECT_BARCODE_HPP
#define OPENCV_OBJDETECT_BARCODE_HPP
#include <opencv2/core.hpp>
#include <opencv2/objdetect/graphical_code_detector.hpp>
namespace cv {
namespace barcode {
//! @addtogroup objdetect_barcode
//! @{
class CV_EXPORTS_W_SIMPLE BarcodeDetector : public cv::GraphicalCodeDetector
{
public:
/** @brief Initialize the BarcodeDetector.
*/
CV_WRAP BarcodeDetector();
/** @brief Initialize the BarcodeDetector.
*
* Parameters allow to load _optional_ Super Resolution DNN model for better quality.
* @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 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 strings, specifies the type of these barcodes
* @return true if at least one valid barcode have been found
*/
CV_WRAP bool decodeWithType(InputArray img,
InputArray points,
CV_OUT std::vector<std::string> &decoded_info,
CV_OUT std::vector<std::string> &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 strings, specifies the type of these barcodes
* @param points optional output vector of vertices of the found barcode rectangle. Will be empty if not found.
* @return true if at least one valid barcode have been found
*/
CV_WRAP bool detectAndDecodeWithType(InputArray img,
CV_OUT std::vector<std::string> &decoded_info,
CV_OUT std::vector<std::string> &decoded_type,
OutputArray points = noArray()) const;
};
//! @}
}} // cv::barcode::
#endif // OPENCV_OBJDETECT_BARCODE_HPP

View File

@ -0,0 +1,50 @@
package org.opencv.test.barcode;
import java.util.List;
import org.opencv.core.Mat;
import org.opencv.objdetect.BarcodeDetector;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.test.OpenCVTestCase;
import java.util.ArrayList;
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 < String > types = new ArrayList< String >();
boolean result = detector.detectAndDecodeWithType(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), "EAN_13");
result = false;
for (int j = 0; j < 4; j++) {
if (correctResults[j].equals(infos.get(i))) {
result = true;
break;
}
}
assertTrue(result);
}
}
}

View File

@ -0,0 +1,33 @@
#!/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.detectAndDecodeWithType(img)
self.assertTrue(retval)
self.assertTrue(len(decoded_info) > 0)
self.assertTrue(len(decoded_type) > 0)
self.assertEqual(decoded_info[0], "9787115279460")
self.assertEqual(decoded_type[0], "EAN_13")
self.assertEqual(corners.shape, (1, 4, 2))

View File

@ -0,0 +1,114 @@
// 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"
#include "opencv2/objdetect/barcode.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.detectMulti(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<std::string> decoded_info;
vector<std::string> decoded_type;
vector< Point > corners;
auto bardet = barcode::BarcodeDetector();
bool res = false;
TEST_CYCLE()
{
res = bardet.detectAndDecodeWithType(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.detectMulti(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<std::string> decoded_info;
vector<std::string> decoded_type;
vector< Point > corners;
auto bardet = barcode::BarcodeDetector();
bool res = false;
TEST_CYCLE()
{
res = bardet.detectAndDecodeWithType(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

View File

@ -0,0 +1,374 @@
// 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/objdetect/barcode.hpp>
#include <opencv2/core/utils/filesystem.hpp>
#include "barcode_decoder/ean13_decoder.hpp"
#include "barcode_decoder/ean8_decoder.hpp"
#include "barcode_detector/bardetect.hpp"
#include "barcode_decoder/common/super_scale.hpp"
#include "barcode_decoder/common/utils.hpp"
#include "graphical_code_detector_impl.hpp"
using std::string;
using std::vector;
using std::make_shared;
using std::array;
using std::shared_ptr;
using std::dynamic_pointer_cast;
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 array<shared_ptr<AbsDecoder>, 2> &getDecoders()
{
//indicate Decoder
static const array<shared_ptr<AbsDecoder>, 2> decoders{
shared_ptr<AbsDecoder>(new Ean13Decoder()), 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();
}
//==================================================================================================
// Private class definition and implementation (pimpl)
struct BarcodeImpl : public GraphicalCodeDetector::Impl
{
public:
shared_ptr<SuperScale> sr;
bool use_nn_sr = false;
public:
//=================
// own methods
BarcodeImpl() = default;
vector<Mat> initDecode(const Mat &src, const vector<vector<Point2f>> &points) const;
bool decodeWithType(InputArray img,
InputArray points,
vector<string> &decoded_info,
vector<string> &decoded_type) const;
bool detectAndDecodeWithType(InputArray img,
vector<string> &decoded_info,
vector<string> &decoded_type,
OutputArray points_) const;
//=================
// implement interface
~BarcodeImpl() CV_OVERRIDE {}
bool detect(InputArray img, OutputArray points) const CV_OVERRIDE;
string decode(InputArray img, InputArray points, OutputArray straight_code) const CV_OVERRIDE;
string detectAndDecode(InputArray img, OutputArray points, OutputArray straight_code) const CV_OVERRIDE;
bool detectMulti(InputArray img, OutputArray points) const CV_OVERRIDE;
bool decodeMulti(InputArray img, InputArray points, vector<string>& decoded_info, OutputArrayOfArrays straight_code) const CV_OVERRIDE;
bool detectAndDecodeMulti(InputArray img, vector<string>& decoded_info, OutputArray points, OutputArrayOfArrays straight_code) const CV_OVERRIDE;
};
// return cropped and scaled bar img
vector<Mat> BarcodeImpl::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;
}
bool BarcodeImpl::decodeWithType(InputArray img,
InputArray points,
vector<string> &decoded_info,
vector<string> &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 = 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.isValid())
{
ok = true;
}
decoded_info.emplace_back(res.result);
decoded_type.emplace_back(res.typeString());
}
return ok;
}
bool BarcodeImpl::detectAndDecodeWithType(InputArray img,
vector<string> &decoded_info,
vector<string> &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 = decodeWithType(inarr, points, decoded_info, decoded_type);
return ok;
}
bool BarcodeImpl::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;
}
string BarcodeImpl::decode(InputArray img, InputArray points, OutputArray straight_code) const
{
CV_UNUSED(straight_code);
vector<string> decoded_info;
vector<string> decoded_type;
if (!decodeWithType(img, points, decoded_info, decoded_type))
return string();
if (decoded_info.size() < 1)
return string();
return decoded_info[0];
}
string BarcodeImpl::detectAndDecode(InputArray img, OutputArray points, OutputArray straight_code) const
{
CV_UNUSED(straight_code);
vector<string> decoded_info;
vector<string> decoded_type;
vector<Point> points_;
if (!detectAndDecodeWithType(img, decoded_info, decoded_type, points_))
return string();
if (points_.size() < 4 || decoded_info.size() < 1)
return string();
points_.resize(4);
points.setTo(points_);
return decoded_info[0];
}
bool BarcodeImpl::detectMulti(InputArray img, OutputArray points) const
{
return detect(img, points);
}
bool BarcodeImpl::decodeMulti(InputArray img, InputArray points, vector<string> &decoded_info, OutputArrayOfArrays straight_code) const
{
CV_UNUSED(straight_code);
vector<string> decoded_type;
return decodeWithType(img, points, decoded_info, decoded_type);
}
bool BarcodeImpl::detectAndDecodeMulti(InputArray img, vector<string> &decoded_info, OutputArray points, OutputArrayOfArrays straight_code) const
{
CV_UNUSED(straight_code);
vector<string> decoded_type;
return detectAndDecodeWithType(img, decoded_info, decoded_type, points);
}
//==================================================================================================
// Public class implementation
BarcodeDetector::BarcodeDetector()
: BarcodeDetector(string(), string())
{
}
BarcodeDetector::BarcodeDetector(const string &prototxt_path, const string &model_path)
{
Ptr<BarcodeImpl> p_ = new BarcodeImpl();
p = p_;
if (!prototxt_path.empty() && !model_path.empty())
{
CV_Assert(utils::fs::exists(prototxt_path));
CV_Assert(utils::fs::exists(model_path));
p_->sr = 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::decodeWithType(InputArray img, InputArray points, vector<string> &decoded_info, vector<string> &decoded_type) const
{
Ptr<BarcodeImpl> p_ = dynamic_pointer_cast<BarcodeImpl>(p);
CV_Assert(p_);
return p_->decodeWithType(img, points, decoded_info, decoded_type);
}
bool BarcodeDetector::detectAndDecodeWithType(InputArray img, vector<string> &decoded_info, vector<string> &decoded_type, OutputArray points_) const
{
Ptr<BarcodeImpl> p_ = dynamic_pointer_cast<BarcodeImpl>(p);
CV_Assert(p_);
return p_->detectAndDecodeWithType(img, decoded_info, decoded_type, points_);
}
}// namespace barcode
} // namespace cv

View File

@ -0,0 +1,118 @@
// 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);
}
}
}

View File

@ -0,0 +1,99 @@
// 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/objdetect/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
{
enum BarcodeType
{
BARCODE_NONE,
BARCODE_EAN_8,
BARCODE_EAN_13,
BARCODE_UPC_A,
BARCODE_UPC_E,
BARCODE_UPC_EAN_EXTENSION
};
std::string result;
BarcodeType format = Result::BARCODE_NONE;
Result() = default;
Result(const std::string &_result, BarcodeType _format)
{
result = _result;
format = _format;
}
string typeString() const
{
switch (format)
{
case Result::BARCODE_EAN_8: return "EAN_8";
case Result::BARCODE_EAN_13: return "EAN_13";
case Result::BARCODE_UPC_E: return "UPC_E";
case Result::BARCODE_UPC_A: return "UPC_A";
case Result::BARCODE_UPC_EAN_EXTENSION: return "UPC_EAN_EXTENSION";
default: return string();
}
}
bool isValid() const
{
return format != BARCODE_NONE;
}
};
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

View File

@ -0,0 +1,195 @@
// 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);
}
}
}
}

View File

@ -0,0 +1,22 @@
// 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

View File

@ -0,0 +1,77 @@
// 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 "super_scale.hpp"
#ifdef HAVE_OPENCV_DNN
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
#endif // HAVE_OPENCV_DNN

View File

@ -0,0 +1,69 @@
/// 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
#ifdef HAVE_OPENCV_DNN
#include "opencv2/dnn.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
#else // HAVE_OPENCV_DNN
#include "opencv2/core.hpp"
#include "opencv2/core/utils/logger.hpp"
namespace cv {
namespace barcode {
class SuperScale
{
public:
int init(const std::string &, const std::string &)
{
return 0;
}
void processImageScale(const Mat &src, Mat &dst, float scale, const bool & isEnabled, int)
{
if (isEnabled)
{
CV_LOG_WARNING(NULL, "objdetect/barcode: SuperScaling disabled - OpenCV has been built without DNN support");
}
resize(src, dst, Size(), scale, scale, INTER_CUBIC);
}
};
} // namespace barcode
} // namespace cv
#endif // !HAVE_OPENCV_DNN
#endif // OPENCV_BARCODE_SUPER_SCALE_HPP

View File

@ -0,0 +1,36 @@
// 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");
}
}
}
}

View File

@ -0,0 +1,26 @@
// 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

View File

@ -0,0 +1,92 @@
// 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", Result::BARCODE_NONE);
}
pair<uint, uint> pattern;
if (!findStartGuardPatterns(data, pattern))
{
return Result("Begin Pattern Not Found", Result::BARCODE_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", Result::BARCODE_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", Result::BARCODE_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", Result::BARCODE_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", Result::BARCODE_NONE);
}
result = string(decode_result);
if (!isValid(result))
{
return Result("Wrong: " + result.append(string(EAN13DIGIT_NUM - result.size(), ' ')), Result::BARCODE_NONE);
}
return Result(result, Result::BARCODE_EAN_13);
}
Ean13Decoder::Ean13Decoder()
{
this->bits_num = EAN13BITS_NUM;
this->digit_number = EAN13DIGIT_NUM;
}
}
}

View File

@ -0,0 +1,31 @@
// 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

View File

@ -0,0 +1,79 @@
// 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", Result::BARCODE_NONE);
}
pair<uint, uint> pattern;
if (!findStartGuardPatterns(data, pattern))
{
return Result("Begin Pattern Not Found", Result::BARCODE_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", Result::BARCODE_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", Result::BARCODE_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", Result::BARCODE_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", Result::BARCODE_NONE);
}
result = string(decode_result);
if (!isValid(result))
{
return Result("Wrong: " + result.append(string(EAN8DIGIT_NUM - result.size(), ' ')), Result::BARCODE_NONE);
}
return Result(result, Result::BARCODE_EAN_8);
}
Ean8Decoder::Ean8Decoder()
{
this->digit_number = EAN8DIGIT_NUM;
this->bits_num = EAN8BITS_NUM;
}
}
}

View File

@ -0,0 +1,32 @@
// 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

View File

@ -0,0 +1,290 @@
// 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 == Result::BARCODE_NONE)
{
result = this->decode(std::vector<uchar>(middle.crbegin(), middle.crend()));
}
if (result.format == Result::BARCODE_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(), Result::BARCODE_NONE}, 0.0F);
}
std::map<std::string, int> result_vote;
std::map<Result::BarcodeType, int> format_vote;
int vote_cnt = 0;
int total_vote = 0;
std::string max_result;
Result::BarcodeType max_type = Result::BARCODE_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 != Result::BARCODE_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(), Result::BARCODE_NONE), 0.0f);
}
float confidence = (float) vote_cnt / (float) DIVIDE_PART;
//Check if it is UPC-A format
if (max_type == Result::BARCODE_EAN_13 && max_result[0] == '0')
{
max_result = max_result.substr(1, 12); //UPC-A length 12
max_type = Result::BARCODE_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 == Result::BARCODE_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

View File

@ -0,0 +1,67 @@
// 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

View File

@ -0,0 +1,510 @@
// 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;
}
//==============================================================================
// NMSBoxes copied from modules/dnn/src/nms.inl.hpp
// TODO: move NMSBoxes outside the dnn module to allow other modules use it
namespace
{
template <typename T>
static inline bool SortScorePairDescend(const std::pair<float, T>& pair1,
const std::pair<float, T>& pair2)
{
return pair1.first > pair2.first;
}
inline void GetMaxScoreIndex(const std::vector<float>& scores, const float threshold, const int top_k,
std::vector<std::pair<float, int> >& score_index_vec)
{
CV_DbgAssert(score_index_vec.empty());
// Generate index score pairs.
for (size_t i = 0; i < scores.size(); ++i)
{
if (scores[i] > threshold)
{
score_index_vec.push_back(std::make_pair(scores[i], (int)i));
}
}
// Sort the score pair according to the scores in descending order
std::stable_sort(score_index_vec.begin(), score_index_vec.end(),
SortScorePairDescend<int>);
// Keep top_k scores if needed.
if (top_k > 0 && top_k < (int)score_index_vec.size())
{
score_index_vec.resize(top_k);
}
}
template <typename BoxType>
inline void NMSFast_(const std::vector<BoxType>& bboxes,
const std::vector<float>& scores, const float score_threshold,
const float nms_threshold, const float eta, const int top_k,
std::vector<int>& indices,
float (*computeOverlap)(const BoxType&, const BoxType&),
size_t limit = std::numeric_limits<int>::max())
{
CV_Assert(bboxes.size() == scores.size());
// Get top_k scores (with corresponding indices).
std::vector<std::pair<float, int> > score_index_vec;
GetMaxScoreIndex(scores, score_threshold, top_k, score_index_vec);
// Do nms.
float adaptive_threshold = nms_threshold;
indices.clear();
for (size_t i = 0; i < score_index_vec.size(); ++i) {
const int idx = score_index_vec[i].second;
bool keep = true;
for (int k = 0; k < (int)indices.size() && keep; ++k) {
const int kept_idx = indices[k];
float overlap = computeOverlap(bboxes[idx], bboxes[kept_idx]);
keep = overlap <= adaptive_threshold;
}
if (keep) {
indices.push_back(idx);
if (indices.size() >= limit) {
break;
}
}
if (keep && eta < 1 && adaptive_threshold > 0.5) {
adaptive_threshold *= eta;
}
}
}
static inline float rotatedRectIOU(const RotatedRect& a, const RotatedRect& b)
{
std::vector<Point2f> inter;
int res = rotatedRectangleIntersection(a, b, inter);
if (inter.empty() || res == INTERSECT_NONE)
return 0.0f;
if (res == INTERSECT_FULL)
return 1.0f;
float interArea = (float)contourArea(inter);
return interArea / (a.size.area() + b.size.area() - interArea);
}
static void NMSBoxes(const std::vector<RotatedRect>& bboxes, const std::vector<float>& scores,
const float score_threshold, const float nms_threshold,
std::vector<int>& indices, const float eta = 1.f, const int top_k = 0)
{
CV_Assert_N(bboxes.size() == scores.size(), score_threshold >= 0,
nms_threshold >= 0, eta > 0);
NMSFast_(bboxes, scores, score_threshold, nms_threshold, eta, top_k, indices, rotatedRectIOU);
}
} // namespace <anonymous>::
//==============================================================================
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;
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;
}
}
}
}
}
}

View File

@ -0,0 +1,62 @@
// 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>
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

View File

@ -44,10 +44,13 @@
#define __OPENCV_PRECOMP_H__
#include "opencv2/objdetect.hpp"
#include "opencv2/objdetect/barcode.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/core/utility.hpp"
#include "opencv2/core/ocl.hpp"
#include "opencv2/core/private.hpp"
#include <numeric>
#endif

View File

@ -0,0 +1,127 @@
// 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"
#include "opencv2/objdetect/barcode.hpp"
namespace opencv_test{namespace{
typedef std::vector<std::string> stringvec;
typedef std::map<std::string, std::string> datasetType;
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;
}
inline 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<std::string> formats;
bardet.detectAndDecodeWithType(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.detectMulti(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;
Mat zero_image = Mat::zeros(256, 256, CV_8UC1);
EXPECT_FALSE(bardet.detectMulti(zero_image, corners));
corners = std::vector<Point>(4);
EXPECT_ANY_THROW(bardet.decodeMulti(zero_image, corners, decoded_info));
}
}} // opencv_test::<anonymous>::

223
samples/cpp/barcode.cpp Normal file
View File

@ -0,0 +1,223 @@
#include <iostream>
#include "opencv2/objdetect.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
using namespace cv;
using namespace std;
static const Scalar greenColor(0, 255, 0);
static const Scalar redColor(0, 0, 255);
static const Scalar yellowColor(0, 255, 255);
static Scalar randColor()
{
RNG &rng = theRNG();
return Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
}
//==============================================================================
struct TheApp
{
Ptr<barcode::BarcodeDetector> bardet;
//! [output]
vector<Point> corners;
vector<string> decode_info;
vector<string> decode_type;
//! [output]
bool detectOnly;
void cleanup()
{
corners.clear();
decode_info.clear();
decode_type.clear();
}
inline string modeString() const
{
return detectOnly ? "<detect>" : "<detectAndDecode>";
}
void drawResults(Mat &frame) const
{
//! [visualize]
for (size_t i = 0; i < corners.size(); i += 4)
{
const size_t idx = i / 4;
const bool isDecodable = idx < decode_info.size()
&& idx < decode_type.size()
&& !decode_type[idx].empty();
const Scalar lineColor = isDecodable ? greenColor : redColor;
// draw barcode rectangle
vector<Point> contour(corners.begin() + i, corners.begin() + i + 4);
const vector< vector<Point> > contours {contour};
drawContours(frame, contours, 0, lineColor, 1);
// draw vertices
for (size_t j = 0; j < 4; j++)
circle(frame, contour[j], 2, randColor(), -1);
// write decoded text
if (isDecodable)
{
ostringstream buf;
buf << "[" << decode_type[idx] << "] " << decode_info[idx];
putText(frame, buf.str(), contour[1], FONT_HERSHEY_COMPLEX, 0.8, yellowColor, 1);
}
}
//! [visualize]
}
void drawFPS(Mat &frame, double fps) const
{
ostringstream buf;
buf << modeString()
<< " (" << corners.size() / 4 << "/" << decode_type.size() << "/" << decode_info.size() << ") "
<< cv::format("%.2f", fps) << " FPS ";
putText(frame, buf.str(), Point(25, 25), FONT_HERSHEY_COMPLEX, 0.8, redColor, 2);
}
inline void call_decode(Mat &frame)
{
cleanup();
if (detectOnly)
{
//! [detect]
bardet->detectMulti(frame, corners);
//! [detect]
}
else
{
//! [detectAndDecode]
bardet->detectAndDecodeWithType(frame, decode_info, decode_type, corners);
//! [detectAndDecode]
}
}
int liveBarCodeDetect()
{
VideoCapture cap(0);
if (!cap.isOpened())
{
cout << "Cannot open a camera" << endl;
return 2;
}
Mat frame;
Mat result;
cap >> frame;
cout << "Image size: " << frame.size() << endl;
cout << "Press 'd' to switch between <detect> and <detectAndDecode> modes" << endl;
cout << "Press 'ESC' to exit" << endl;
for (;;)
{
cap >> frame;
if (frame.empty())
{
cout << "End of video stream" << endl;
break;
}
if (frame.channels() == 1)
cvtColor(frame, frame, COLOR_GRAY2BGR);
TickMeter timer;
timer.start();
call_decode(frame);
timer.stop();
drawResults(frame);
drawFPS(frame, timer.getFPS());
imshow("barcode", frame);
const char c = (char)waitKey(1);
if (c == 'd')
{
detectOnly = !detectOnly;
cout << "Mode switched to " << modeString() << endl;
}
else if (c == 27)
{
cout << "'ESC' is pressed. Exiting..." << endl;
break;
}
}
return 0;
}
int imageBarCodeDetect(const string &in_file, const string &out_file)
{
Mat frame = imread(in_file, IMREAD_COLOR);
cout << "Image size: " << frame.size() << endl;
cout << "Mode is " << modeString() << endl;
const int count_experiments = 100;
TickMeter timer;
for (size_t i = 0; i < count_experiments; i++)
{
timer.start();
call_decode(frame);
timer.stop();
}
cout << "FPS: " << timer.getFPS() << endl;
drawResults(frame);
if (!out_file.empty())
{
cout << "Saving result: " << out_file << endl;
imwrite(out_file, frame);
}
imshow("barcode", frame);
cout << "Press any key to exit ..." << endl;
waitKey(0);
return 0;
}
};
//==============================================================================
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;
}
const string in_file = cmd_parser.get<string>("in");
const string out_file = cmd_parser.get<string>("out");
const string sr_prototxt = cmd_parser.get<string>("sr_prototxt");
const string sr_model = cmd_parser.get<string>("sr_model");
if (!cmd_parser.check())
{
cmd_parser.printErrors();
return -1;
}
TheApp app;
app.detectOnly = cmd_parser.has("detect") && cmd_parser.get<bool>("detect");
//! [initialize]
try
{
app.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]
if (in_file.empty())
return app.liveBarCodeDetect();
else
return app.imageBarCodeDetect(in_file, out_file);
}