1
0
mirror of https://github.com/opencv/opencv.git synced 2026-07-29 15:23:05 +04:00

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
32 changed files with 2904 additions and 2 deletions
@@ -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);
}
}
}
@@ -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
@@ -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);
}
}
}
}
@@ -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
@@ -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
@@ -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
@@ -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");
}
}
}
}
@@ -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
@@ -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;
}
}
}
@@ -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
@@ -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;
}
}
}
@@ -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
@@ -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
@@ -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