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:
@@ -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
|
||||
Reference in New Issue
Block a user