mirror of
https://github.com/opencv/opencv.git
synced 2026-07-30 15:53:03 +04:00
Merge branch 4.x
This commit is contained in:
@@ -881,7 +881,7 @@ void ArucoDetector::detectMarkers(InputArray _image, OutputArrayOfArrays _corner
|
||||
}
|
||||
else {
|
||||
// always turn on corner refinement in case of Aruco3, due to upsampling
|
||||
detectorParams.cornerRefinementMethod = CORNER_REFINE_SUBPIX;
|
||||
detectorParams.cornerRefinementMethod = (int)CORNER_REFINE_SUBPIX;
|
||||
// only CORNER_REFINE_SUBPIX implement correctly for useAruco3Detection
|
||||
// Todo: update other CORNER_REFINE methods
|
||||
}
|
||||
@@ -923,7 +923,7 @@ void ArucoDetector::detectMarkers(InputArray _image, OutputArrayOfArrays _corner
|
||||
vector<vector<vector<Point> > > contoursSet;
|
||||
|
||||
/// STEP 2.a Detect marker candidates :: using AprilTag
|
||||
if(detectorParams.cornerRefinementMethod == CORNER_REFINE_APRILTAG){
|
||||
if(detectorParams.cornerRefinementMethod == (int)CORNER_REFINE_APRILTAG){
|
||||
_apriltag(grey, detectorParams, candidates, contours);
|
||||
|
||||
candidatesSet.push_back(candidates);
|
||||
@@ -938,7 +938,7 @@ void ArucoDetector::detectMarkers(InputArray _image, OutputArrayOfArrays _corner
|
||||
candidates, contours, ids, detectorParams, _rejectedImgPoints);
|
||||
|
||||
/// STEP 3: Corner refinement :: use corner subpix
|
||||
if (detectorParams.cornerRefinementMethod == CORNER_REFINE_SUBPIX) {
|
||||
if (detectorParams.cornerRefinementMethod == (int)CORNER_REFINE_SUBPIX) {
|
||||
CV_Assert(detectorParams.cornerRefinementWinSize > 0 && detectorParams.cornerRefinementMaxIterations > 0 &&
|
||||
detectorParams.cornerRefinementMinAccuracy > 0);
|
||||
// Do subpixel estimation. In Aruco3 start on the lowest pyramid level and upscale the corners
|
||||
@@ -963,7 +963,7 @@ void ArucoDetector::detectMarkers(InputArray _image, OutputArrayOfArrays _corner
|
||||
}
|
||||
|
||||
/// STEP 3, Optional : Corner refinement :: use contour container
|
||||
if (detectorParams.cornerRefinementMethod == CORNER_REFINE_CONTOUR){
|
||||
if (detectorParams.cornerRefinementMethod == (int)CORNER_REFINE_CONTOUR){
|
||||
|
||||
if (!ids.empty()) {
|
||||
|
||||
@@ -976,7 +976,7 @@ void ArucoDetector::detectMarkers(InputArray _image, OutputArrayOfArrays _corner
|
||||
}
|
||||
}
|
||||
|
||||
if (detectorParams.cornerRefinementMethod != CORNER_REFINE_SUBPIX && fxfy != 1.f) {
|
||||
if (detectorParams.cornerRefinementMethod != (int)CORNER_REFINE_SUBPIX && fxfy != 1.f) {
|
||||
// only CORNER_REFINE_SUBPIX implement correctly for useAruco3Detection
|
||||
// Todo: update other CORNER_REFINE methods
|
||||
|
||||
@@ -1213,7 +1213,7 @@ void ArucoDetector::refineDetectedMarkers(InputArray _image, const Board& _board
|
||||
if(closestCandidateIdx >= 0) {
|
||||
|
||||
// subpixel refinement
|
||||
if(detectorParams.cornerRefinementMethod == CORNER_REFINE_SUBPIX) {
|
||||
if(detectorParams.cornerRefinementMethod == (int)CORNER_REFINE_SUBPIX) {
|
||||
CV_Assert(detectorParams.cornerRefinementWinSize > 0 &&
|
||||
detectorParams.cornerRefinementMaxIterations > 0 &&
|
||||
detectorParams.cornerRefinementMinAccuracy > 0);
|
||||
|
||||
@@ -258,6 +258,8 @@ Dictionary getPredefinedDictionary(PredefinedDictionaryType name) {
|
||||
static const Dictionary DICT_APRILTAG_36h10_DATA = Dictionary(Mat(2320, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_APRILTAG_36h10_BYTES), 6, 0);
|
||||
static const Dictionary DICT_APRILTAG_36h11_DATA = Dictionary(Mat(587, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_APRILTAG_36h11_BYTES), 6, 0);
|
||||
|
||||
static const Dictionary DICT_ARUCO_MIP_36h12_DATA = Dictionary(Mat(250, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_ARUCO_MIP_36h12_BYTES), 6, 12);
|
||||
|
||||
switch(name) {
|
||||
|
||||
case DICT_ARUCO_ORIGINAL:
|
||||
@@ -308,6 +310,8 @@ Dictionary getPredefinedDictionary(PredefinedDictionaryType name) {
|
||||
case DICT_APRILTAG_36h11:
|
||||
return Dictionary(DICT_APRILTAG_36h11_DATA);
|
||||
|
||||
case DICT_ARUCO_MIP_36h12:
|
||||
return Dictionary(DICT_ARUCO_MIP_36h12_DATA);
|
||||
}
|
||||
return Dictionary(DICT_4X4_50_DATA);
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,45 @@
|
||||
// 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 "precomp.hpp"
|
||||
#include "opencv2/objdetect/graphical_code_detector.hpp"
|
||||
#include "graphical_code_detector_impl.hpp"
|
||||
|
||||
namespace cv {
|
||||
|
||||
GraphicalCodeDetector::GraphicalCodeDetector() {}
|
||||
|
||||
bool GraphicalCodeDetector::detect(InputArray img, OutputArray points) const {
|
||||
CV_Assert(p);
|
||||
return p->detect(img, points);
|
||||
}
|
||||
|
||||
std::string GraphicalCodeDetector::decode(InputArray img, InputArray points, OutputArray straight_code) const {
|
||||
CV_Assert(p);
|
||||
return p->decode(img, points, straight_code);
|
||||
}
|
||||
|
||||
std::string GraphicalCodeDetector::detectAndDecode(InputArray img, OutputArray points, OutputArray straight_code) const {
|
||||
CV_Assert(p);
|
||||
return p->detectAndDecode(img, points, straight_code);
|
||||
}
|
||||
|
||||
bool GraphicalCodeDetector::detectMulti(InputArray img, OutputArray points) const {
|
||||
CV_Assert(p);
|
||||
return p->detectMulti(img, points);
|
||||
}
|
||||
|
||||
bool GraphicalCodeDetector::decodeMulti(InputArray img, InputArray points, std::vector<std::string>& decoded_info,
|
||||
OutputArrayOfArrays straight_code) const {
|
||||
CV_Assert(p);
|
||||
return p->decodeMulti(img, points, decoded_info, straight_code);
|
||||
}
|
||||
|
||||
bool GraphicalCodeDetector::detectAndDecodeMulti(InputArray img, std::vector<std::string>& decoded_info, OutputArray points,
|
||||
OutputArrayOfArrays straight_code) const {
|
||||
CV_Assert(p);
|
||||
return p->detectAndDecodeMulti(img, decoded_info, points, straight_code);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html
|
||||
#ifndef OPENCV_OBJDETECT_GRAPHICAL_CODE_DETECTOR_IMPL_HPP
|
||||
#define OPENCV_OBJDETECT_GRAPHICAL_CODE_DETECTOR_IMPL_HPP
|
||||
|
||||
#include <opencv2/core.hpp>
|
||||
|
||||
namespace cv {
|
||||
|
||||
struct GraphicalCodeDetector::Impl {
|
||||
virtual ~Impl() {}
|
||||
virtual bool detect(InputArray img, OutputArray points) const = 0;
|
||||
virtual std::string decode(InputArray img, InputArray points, OutputArray straight_code) const = 0;
|
||||
virtual std::string detectAndDecode(InputArray img, OutputArray points, OutputArray straight_code) const = 0;
|
||||
virtual bool detectMulti(InputArray img, OutputArray points) const = 0;
|
||||
virtual bool decodeMulti(InputArray img, InputArray points, std::vector<std::string>& decoded_info,
|
||||
OutputArrayOfArrays straight_code) const = 0;
|
||||
virtual bool detectAndDecodeMulti(InputArray img, std::vector<std::string>& decoded_info,
|
||||
OutputArray points, OutputArrayOfArrays straight_code) const = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -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
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "opencv2/objdetect.hpp"
|
||||
#include "opencv2/3d.hpp"
|
||||
#include <opencv2/core/utils/logger.hpp>
|
||||
#include "graphical_code_detector_impl.hpp"
|
||||
|
||||
#ifdef HAVE_QUIRC
|
||||
#include "quirc.h"
|
||||
@@ -950,34 +951,53 @@ vector<Point2f> QRDetect::getQuadrilateral(vector<Point2f> angle_list)
|
||||
return result_angle_list;
|
||||
}
|
||||
|
||||
|
||||
struct QRCodeDetector::Impl
|
||||
struct ImplContour : public GraphicalCodeDetector::Impl
|
||||
{
|
||||
public:
|
||||
Impl() { epsX = 0.2; epsY = 0.1; }
|
||||
~Impl() {}
|
||||
ImplContour(): epsX(0.2), epsY(0.1) {}
|
||||
|
||||
double epsX, epsY;
|
||||
vector<vector<Point2f>> alignmentMarkers;
|
||||
vector<Point2f> updateQrCorners;
|
||||
mutable vector<vector<Point2f>> alignmentMarkers;
|
||||
mutable vector<Point2f> updateQrCorners;
|
||||
bool useAlignmentMarkers = true;
|
||||
|
||||
bool detect(InputArray in, OutputArray points) const override;
|
||||
std::string decode(InputArray img, InputArray points, OutputArray straight_qrcode) const override;
|
||||
std::string detectAndDecode(InputArray img, OutputArray points, OutputArray straight_qrcode) const override;
|
||||
|
||||
bool detectMulti(InputArray img, OutputArray points) const override;
|
||||
bool decodeMulti(InputArray img, InputArray points, std::vector<cv::String>& decoded_info,
|
||||
OutputArrayOfArrays straight_qrcode) const override;
|
||||
bool detectAndDecodeMulti(InputArray img, std::vector<cv::String>& decoded_info, OutputArray points,
|
||||
OutputArrayOfArrays straight_qrcode) const override;
|
||||
|
||||
String decodeCurved(InputArray in, InputArray points, OutputArray straight_qrcode);
|
||||
|
||||
std::string detectAndDecodeCurved(InputArray in, OutputArray points, OutputArray straight_qrcode);
|
||||
};
|
||||
|
||||
QRCodeDetector::QRCodeDetector() : p(new Impl) {}
|
||||
QRCodeDetector::QRCodeDetector() {
|
||||
p = makePtr<ImplContour>();
|
||||
}
|
||||
|
||||
QRCodeDetector::~QRCodeDetector() {}
|
||||
QRCodeDetector& QRCodeDetector::setEpsX(double epsX) {
|
||||
std::dynamic_pointer_cast<ImplContour>(p)->epsX = epsX;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void QRCodeDetector::setEpsX(double epsX) { p->epsX = epsX; }
|
||||
void QRCodeDetector::setEpsY(double epsY) { p->epsY = epsY; }
|
||||
QRCodeDetector& QRCodeDetector::setEpsY(double epsY) {
|
||||
std::dynamic_pointer_cast<ImplContour>(p)->epsY = epsY;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool QRCodeDetector::detect(InputArray in, OutputArray points) const
|
||||
bool ImplContour::detect(InputArray in, OutputArray points) const
|
||||
{
|
||||
Mat inarr;
|
||||
if (!checkQRInputImage(in, inarr))
|
||||
return false;
|
||||
|
||||
QRDetect qrdet;
|
||||
qrdet.init(inarr, p->epsX, p->epsY);
|
||||
qrdet.init(inarr, epsX, epsY);
|
||||
if (!qrdet.localization()) { return false; }
|
||||
if (!qrdet.computeTransformationPoints()) { return false; }
|
||||
vector<Point2f> pnts2f = qrdet.getTransformationPoints();
|
||||
@@ -2789,9 +2809,7 @@ QRDecode::QRDecode(bool _useAlignmentMarkers):
|
||||
test_perspective_size(0.f)
|
||||
{}
|
||||
|
||||
std::string QRCodeDetector::decode(InputArray in, InputArray points,
|
||||
OutputArray straight_qrcode)
|
||||
{
|
||||
std::string ImplContour::decode(InputArray in, InputArray points, OutputArray straight_qrcode) const {
|
||||
Mat inarr;
|
||||
if (!checkQRInputImage(in, inarr))
|
||||
return std::string();
|
||||
@@ -2801,7 +2819,7 @@ std::string QRCodeDetector::decode(InputArray in, InputArray points,
|
||||
CV_Assert(src_points.size() == 4);
|
||||
CV_CheckGT(contourArea(src_points), 0.0, "Invalid QR code source points");
|
||||
|
||||
QRDecode qrdec(p->useAlignmentMarkers);
|
||||
QRDecode qrdec(useAlignmentMarkers);
|
||||
qrdec.init(inarr, src_points);
|
||||
bool ok = qrdec.straightDecodingProcess();
|
||||
|
||||
@@ -2815,14 +2833,18 @@ std::string QRCodeDetector::decode(InputArray in, InputArray points,
|
||||
qrdec.getStraightBarcode().convertTo(straight_qrcode, CV_8UC1);
|
||||
}
|
||||
if (ok && !decoded_info.empty()) {
|
||||
p->alignmentMarkers = {qrdec.alignment_coords};
|
||||
p->updateQrCorners = qrdec.getOriginalPoints();
|
||||
alignmentMarkers = {qrdec.alignment_coords};
|
||||
updateQrCorners = qrdec.getOriginalPoints();
|
||||
}
|
||||
return ok ? decoded_info : std::string();
|
||||
}
|
||||
|
||||
cv::String QRCodeDetector::decodeCurved(InputArray in, InputArray points,
|
||||
OutputArray straight_qrcode)
|
||||
String QRCodeDetector::decodeCurved(InputArray in, InputArray points, OutputArray straight_qrcode) {
|
||||
CV_Assert(p);
|
||||
return std::dynamic_pointer_cast<ImplContour>(p)->decodeCurved(in, points, straight_qrcode);
|
||||
}
|
||||
|
||||
String ImplContour::decodeCurved(InputArray in, InputArray points, OutputArray straight_qrcode)
|
||||
{
|
||||
Mat inarr;
|
||||
if (!checkQRInputImage(in, inarr))
|
||||
@@ -2833,7 +2855,7 @@ cv::String QRCodeDetector::decodeCurved(InputArray in, InputArray points,
|
||||
CV_Assert(src_points.size() == 4);
|
||||
CV_CheckGT(contourArea(src_points), 0.0, "Invalid QR code source points");
|
||||
|
||||
QRDecode qrdec(p->useAlignmentMarkers);
|
||||
QRDecode qrdec(useAlignmentMarkers);
|
||||
qrdec.init(inarr, src_points);
|
||||
bool ok = qrdec.curvedDecodingProcess();
|
||||
|
||||
@@ -2851,10 +2873,7 @@ cv::String QRCodeDetector::decodeCurved(InputArray in, InputArray points,
|
||||
return ok ? decoded_info : std::string();
|
||||
}
|
||||
|
||||
std::string QRCodeDetector::detectAndDecode(InputArray in,
|
||||
OutputArray points_,
|
||||
OutputArray straight_qrcode)
|
||||
{
|
||||
std::string ImplContour::detectAndDecode(InputArray in, OutputArray points_, OutputArray straight_qrcode) const {
|
||||
Mat inarr;
|
||||
if (!checkQRInputImage(in, inarr))
|
||||
{
|
||||
@@ -2874,9 +2893,14 @@ std::string QRCodeDetector::detectAndDecode(InputArray in,
|
||||
return decoded_info;
|
||||
}
|
||||
|
||||
std::string QRCodeDetector::detectAndDecodeCurved(InputArray in,
|
||||
OutputArray points_,
|
||||
OutputArray straight_qrcode)
|
||||
std::string QRCodeDetector::detectAndDecodeCurved(InputArray in, OutputArray points,
|
||||
OutputArray straight_qrcode) {
|
||||
CV_Assert(p);
|
||||
return std::dynamic_pointer_cast<ImplContour>(p)->detectAndDecodeCurved(in, points, straight_qrcode);
|
||||
}
|
||||
|
||||
std::string ImplContour::detectAndDecodeCurved(InputArray in, OutputArray points_,
|
||||
OutputArray straight_qrcode)
|
||||
{
|
||||
Mat inarr;
|
||||
if (!checkQRInputImage(in, inarr))
|
||||
@@ -3817,31 +3841,28 @@ bool QRDetectMulti::computeTransformationPoints(const size_t cur_ind)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QRCodeDetector::detectMulti(InputArray in, OutputArray points) const
|
||||
{
|
||||
Mat inarr;
|
||||
if (!checkQRInputImage(in, inarr))
|
||||
{
|
||||
bool ImplContour::detectMulti(InputArray in, OutputArray points) const {
|
||||
Mat gray;
|
||||
if (!checkQRInputImage(in, gray)) {
|
||||
points.release();
|
||||
return false;
|
||||
}
|
||||
|
||||
vector<Point2f> result;
|
||||
QRDetectMulti qrdet;
|
||||
qrdet.init(inarr, p->epsX, p->epsY);
|
||||
if (!qrdet.localization())
|
||||
{
|
||||
qrdet.init(gray, epsX, epsY);
|
||||
if (!qrdet.localization()) {
|
||||
points.release();
|
||||
return false;
|
||||
}
|
||||
vector< vector< Point2f > > pnts2f = qrdet.getTransformationPoints();
|
||||
vector<Point2f> trans_points;
|
||||
vector<vector<Point2f> > pnts2f = qrdet.getTransformationPoints();
|
||||
for(size_t i = 0; i < pnts2f.size(); i++)
|
||||
for(size_t j = 0; j < pnts2f[i].size(); j++)
|
||||
trans_points.push_back(pnts2f[i][j]);
|
||||
|
||||
updatePointsResult(points, trans_points);
|
||||
|
||||
return true;
|
||||
result.push_back(pnts2f[i][j]);
|
||||
if (result.size() >= 4) {
|
||||
updatePointsResult(points, result);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
class ParallelDecodeProcess : public ParallelLoopBody
|
||||
@@ -3902,7 +3923,7 @@ private:
|
||||
|
||||
};
|
||||
|
||||
bool QRCodeDetector::decodeMulti(
|
||||
bool ImplContour::decodeMulti(
|
||||
InputArray img,
|
||||
InputArray points,
|
||||
CV_OUT std::vector<cv::String>& decoded_info,
|
||||
@@ -3926,7 +3947,7 @@ bool QRCodeDetector::decodeMulti(
|
||||
}
|
||||
}
|
||||
CV_Assert(src_points.size() > 0);
|
||||
vector<QRDecode> qrdec(src_points.size(), p->useAlignmentMarkers);
|
||||
vector<QRDecode> qrdec(src_points.size(), useAlignmentMarkers);
|
||||
vector<Mat> straight_barcode(src_points.size());
|
||||
vector<std::string> info(src_points.size());
|
||||
ParallelDecodeProcess parallelDecodeProcess(inarr, qrdec, info, straight_barcode, src_points);
|
||||
@@ -3957,12 +3978,12 @@ bool QRCodeDetector::decodeMulti(
|
||||
{
|
||||
decoded_info.push_back(info[i]);
|
||||
}
|
||||
p->alignmentMarkers.resize(src_points.size());
|
||||
p->updateQrCorners.resize(src_points.size()*4ull);
|
||||
alignmentMarkers.resize(src_points.size());
|
||||
updateQrCorners.resize(src_points.size()*4ull);
|
||||
for (size_t i = 0ull; i < src_points.size(); i++) {
|
||||
p->alignmentMarkers[i] = qrdec[i].alignment_coords;
|
||||
alignmentMarkers[i] = qrdec[i].alignment_coords;
|
||||
for (size_t j = 0ull; j < 4ull; j++)
|
||||
p->updateQrCorners[i*4ull+j] = qrdec[i].getOriginalPoints()[j] * qrdec[i].coeff_expansion;
|
||||
updateQrCorners[i*4ull+j] = qrdec[i].getOriginalPoints()[j] * qrdec[i].coeff_expansion;
|
||||
}
|
||||
if (!decoded_info.empty())
|
||||
return true;
|
||||
@@ -3970,7 +3991,7 @@ bool QRCodeDetector::decodeMulti(
|
||||
return false;
|
||||
}
|
||||
|
||||
bool QRCodeDetector::detectAndDecodeMulti(
|
||||
bool ImplContour::detectAndDecodeMulti(
|
||||
InputArray img,
|
||||
CV_OUT std::vector<cv::String>& decoded_info,
|
||||
OutputArray points_,
|
||||
@@ -3994,13 +4015,537 @@ bool QRCodeDetector::detectAndDecodeMulti(
|
||||
updatePointsResult(points_, points);
|
||||
decoded_info.clear();
|
||||
ok = decodeMulti(inarr, points, decoded_info, straight_qrcode);
|
||||
updatePointsResult(points_, p->updateQrCorners);
|
||||
updatePointsResult(points_, updateQrCorners);
|
||||
return ok;
|
||||
}
|
||||
|
||||
void QRCodeDetector::setUseAlignmentMarkers(bool useAlignmentMarkers) {
|
||||
p->useAlignmentMarkers = useAlignmentMarkers;
|
||||
QRCodeDetector& QRCodeDetector::setUseAlignmentMarkers(bool useAlignmentMarkers) {
|
||||
(std::dynamic_pointer_cast<ImplContour>)(p)->useAlignmentMarkers = useAlignmentMarkers;
|
||||
return *this;
|
||||
}
|
||||
|
||||
QRCodeDetectorAruco::Params::Params() {
|
||||
minModuleSizeInPyramid = 4.f;
|
||||
maxRotation = (float)CV_PI/12.f;
|
||||
maxModuleSizeMismatch = 1.75f;
|
||||
maxTimingPatternMismatch = 2.f;
|
||||
maxPenalties = 0.4f;
|
||||
maxColorsMismatch = 0.2f;
|
||||
scaleTimingPatternScore = 0.9f;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
struct FinderPatternInfo {
|
||||
FinderPatternInfo() {}
|
||||
|
||||
FinderPatternInfo(const vector<Point2f>& patternPoints): points(patternPoints) {
|
||||
float minSin = 1.f;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
center += points[i];
|
||||
const Point2f side = points[i]-points[(i+1) % 4];
|
||||
const float lenSide = sqrt(normL2Sqr<float>(side));
|
||||
minSin = min(minSin, abs(side.y) / lenSide);
|
||||
moduleSize += lenSide;
|
||||
}
|
||||
moduleSize /= (4.f * 7.f); // 4 sides, 7 modules in one side
|
||||
center /= 4.f;
|
||||
minQrAngle = asin(minSin);
|
||||
}
|
||||
|
||||
enum TypePattern {
|
||||
CENTER,
|
||||
RIGHT,
|
||||
BOTTOM,
|
||||
NONE
|
||||
};
|
||||
|
||||
void setType(const TypePattern& _typePattern, const Point2f& centerQR) {
|
||||
typePattern = _typePattern;
|
||||
float bestLen = normL2Sqr<float>(centerQR - points[0]);
|
||||
int id = 0;
|
||||
for (int i = 1; i < 4; i++) {
|
||||
float len = normL2Sqr<float>(centerQR - points[i]);
|
||||
if (len < bestLen) {
|
||||
bestLen = len;
|
||||
id = i;
|
||||
}
|
||||
}
|
||||
innerCornerId = id;
|
||||
}
|
||||
|
||||
Point2f getDirectionTo(const TypePattern& other) const {
|
||||
Point2f res = points[innerCornerId];
|
||||
if (typePattern == TypePattern::CENTER) {
|
||||
if (other == TypePattern::RIGHT) {
|
||||
res -= points[(innerCornerId + 1) % 4];
|
||||
res = 0.5f*(res + points[(innerCornerId + 3) % 4] - points[(innerCornerId + 2) % 4]);
|
||||
}
|
||||
else if (other == TypePattern::BOTTOM) {
|
||||
res -= points[(innerCornerId + 3) % 4];
|
||||
res = 0.5f*(res + points[(innerCornerId + 1) % 4] - points[(innerCornerId + 2) % 4]);
|
||||
}
|
||||
}
|
||||
else if (typePattern == TypePattern::RIGHT && other == TypePattern::CENTER) {
|
||||
res = res - points[(innerCornerId + 3) % 4];
|
||||
res = 0.5f*(res + points[(innerCornerId + 1) % 4] - points[(innerCornerId + 2) % 4]);
|
||||
}
|
||||
else if (typePattern == TypePattern::BOTTOM && other == TypePattern::CENTER) {
|
||||
res = res - points[(innerCornerId + 1) % 4];
|
||||
res = 0.5f*(res + points[(innerCornerId + 3) % 4] - points[(innerCornerId + 2) % 4]);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
bool checkTriangleAngle(const FinderPatternInfo& patternRight, const FinderPatternInfo& patternBottom, const float length2Vec) {
|
||||
// check the triangle angle btw right & center & bootom sides of QR code
|
||||
// the triangle angle shoud be between 30 and 150 degrees
|
||||
// abs(pi/2 - triangle_angle) should be less 60 degrees
|
||||
const float angle = abs((float)CV_PI/2.f - acos((center - patternRight.center).dot((center - patternBottom.center)) / length2Vec));
|
||||
|
||||
const float maxTriangleDeltaAngle = (float)CV_PI / 3.f;
|
||||
if (angle > maxTriangleDeltaAngle) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool checkAngle(const FinderPatternInfo& other, const float maxRotation) {
|
||||
Point2f toOther = getDirectionTo(other.typePattern);
|
||||
Point2f toThis = other.getDirectionTo(typePattern);
|
||||
const float cosAngle = getCosAngle(toOther, toThis);
|
||||
if (cosAngle < 0.f && (CV_PI - acos(cosAngle)) / 2.f < maxRotation) {
|
||||
const float angleCenter = max(acos(getCosAngle(toOther, other.center - center)), acos(getCosAngle(toThis, center - other.center)));
|
||||
if (angleCenter < maxRotation)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static float getCosAngle(const Point2f& vec1, const Point2f& vec2) {
|
||||
float cosAngle = vec1.dot(vec2) / (sqrt(normL2Sqr<float>(vec1)) * sqrt(normL2Sqr<float>(vec2)));
|
||||
cosAngle = std::max(-1.f, cosAngle);
|
||||
cosAngle = std::min(1.f, cosAngle);
|
||||
return cosAngle;
|
||||
}
|
||||
|
||||
pair<int, Point2f> getQRCorner() const {
|
||||
if (typePattern == TypePattern::CENTER) {
|
||||
int id = (innerCornerId + 2) % 4;
|
||||
return std::make_pair(id, points[id]);
|
||||
}
|
||||
else if (typePattern != TypePattern::NONE) {
|
||||
int id = (innerCornerId + 2) % 4;
|
||||
return std::make_pair(id, points[id]);
|
||||
}
|
||||
return std::make_pair(-1, Point2f());
|
||||
}
|
||||
|
||||
pair<int, Point2f> getCornerForIntersection() const {
|
||||
if (typePattern == TypePattern::RIGHT) {
|
||||
int id = (innerCornerId + 3) % 4;
|
||||
return std::make_pair(id, points[id]);
|
||||
}
|
||||
else if (typePattern == TypePattern::BOTTOM) {
|
||||
int id = (innerCornerId + 1) % 4;
|
||||
return std::make_pair(id, points[id]);
|
||||
}
|
||||
return std::make_pair(-1, Point2f());
|
||||
}
|
||||
|
||||
Point2f getTimingStart(TypePattern direction) const {
|
||||
const float timingStartPosition = .5f;
|
||||
const float patternLength = 7.f;
|
||||
Point2f start = points[innerCornerId]*((patternLength - timingStartPosition)/patternLength);
|
||||
if (typePattern == TypePattern::CENTER && direction == TypePattern::RIGHT) {
|
||||
start += points[(innerCornerId + 3) % 4]*(timingStartPosition/patternLength);
|
||||
}
|
||||
else if (typePattern == TypePattern::CENTER && direction == TypePattern::BOTTOM) {
|
||||
start += points[(innerCornerId + 1) % 4]*(timingStartPosition/patternLength);
|
||||
}
|
||||
else if (typePattern == TypePattern::RIGHT && direction == TypePattern::CENTER) {
|
||||
start += points[(innerCornerId + 1) % 4]*(timingStartPosition/patternLength);
|
||||
}
|
||||
else if (typePattern == TypePattern::BOTTOM && direction == TypePattern::CENTER) {
|
||||
start += points[(innerCornerId + 3) % 4]*(timingStartPosition/patternLength);
|
||||
}
|
||||
return start + getDirectionTo(direction)/(patternLength*2.f);
|
||||
}
|
||||
|
||||
// return total white+black modules in timing pattern, total white modules, penaltyPoints
|
||||
Point3i getTimingPatternScore(const Point2f& start, const Point2f& end, Mat &img, const float maxTimingPatternMismatch) const {
|
||||
Rect imageRect(Point(), img.size());
|
||||
int penaltyPoints = 0;
|
||||
int colorCounters[2] = {0, 0};
|
||||
if (imageRect.contains(Point(cvRound(end.x), cvRound(end.y)))) {
|
||||
LineIterator lineIterator(start, end);
|
||||
uint8_t prevValue = img.at<uint8_t>(lineIterator.pos());
|
||||
|
||||
vector<Point> vec = {lineIterator.pos()};
|
||||
|
||||
// the starting position in the timing pattern is the white module white module next to the finder pattern.
|
||||
bool whiteColor = true;
|
||||
lineIterator++;
|
||||
colorCounters[whiteColor]++;
|
||||
|
||||
for(int i = 1; i < lineIterator.count; i++, ++lineIterator) {
|
||||
const uint8_t value = img.at<uint8_t>(lineIterator.pos());
|
||||
if (prevValue != value) {
|
||||
const float dist = sqrt(normL2Sqr<float>((Point2f)(vec.back()-lineIterator.pos())));
|
||||
// check long and short lines in timing pattern
|
||||
const float relativeDiff = max(moduleSize, dist)/min(moduleSize, dist);
|
||||
if (relativeDiff > maxTimingPatternMismatch) {
|
||||
if (dist < moduleSize || relativeDiff < maxTimingPatternMismatch*8.f)
|
||||
penaltyPoints++;
|
||||
else
|
||||
penaltyPoints += cvRound(relativeDiff);
|
||||
}
|
||||
vec.push_back(lineIterator.pos());
|
||||
prevValue = value;
|
||||
whiteColor ^= true;
|
||||
colorCounters[whiteColor]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Point3i(colorCounters[0] + colorCounters[1], colorCounters[1], penaltyPoints);
|
||||
}
|
||||
|
||||
FinderPatternInfo& operator*=(const float scale) {
|
||||
moduleSize *= scale;
|
||||
center *= scale;
|
||||
for (auto& point: points)
|
||||
point *= scale;
|
||||
return *this;
|
||||
}
|
||||
|
||||
float moduleSize = 0.f;
|
||||
|
||||
// Index of inner QR corner.
|
||||
// The inner corner is the corner closest to the center of the QR code.
|
||||
int innerCornerId = 0;
|
||||
|
||||
float minQrAngle = 0.f;
|
||||
TypePattern typePattern = NONE;
|
||||
|
||||
Point2f center;
|
||||
vector<Point2f> points;
|
||||
};
|
||||
|
||||
struct QRCode {
|
||||
QRCode() {}
|
||||
|
||||
QRCode(const FinderPatternInfo& _centerPattern, const FinderPatternInfo& _rightPattern, const FinderPatternInfo& _bottomPattern,
|
||||
Point2f _center, float dist): centerPattern(_centerPattern), rightPattern(_rightPattern), bottomPattern(_bottomPattern),
|
||||
center(_center), distance(dist) {
|
||||
moduleSize = (centerPattern.moduleSize + rightPattern.moduleSize + bottomPattern.moduleSize) / 3.f;
|
||||
}
|
||||
|
||||
vector<Point2f> getQRCorners() const {
|
||||
Point2f a1 = rightPattern.getQRCorner().second;
|
||||
Point2f a2 = rightPattern.getCornerForIntersection().second;
|
||||
|
||||
Point2f b1 = bottomPattern.getQRCorner().second;
|
||||
Point2f b2 = bottomPattern.getCornerForIntersection().second;
|
||||
|
||||
Point2f rightBottom = intersectionLines(a1, a2, b1, b2);
|
||||
|
||||
return {centerPattern.getQRCorner().second, rightPattern.getQRCorner().second, rightBottom, bottomPattern.getQRCorner().second};
|
||||
}
|
||||
|
||||
static QRCode checkCompatibilityPattern(const FinderPatternInfo &_pattern1, const FinderPatternInfo& _pattern2, const FinderPatternInfo& _pattern3,
|
||||
Point3i& index, const QRCodeDetectorAruco::Params& qrDetectorParameters) {
|
||||
FinderPatternInfo pattern1 = _pattern1, pattern2 = _pattern2, pattern3 = _pattern3;
|
||||
Point2f centerQR;
|
||||
float distance = std::numeric_limits<float>::max();
|
||||
|
||||
if (abs(pattern1.minQrAngle - pattern2.minQrAngle) > qrDetectorParameters.maxRotation ||
|
||||
abs(pattern1.minQrAngle - pattern3.minQrAngle) > qrDetectorParameters.maxRotation) // check maxRotation
|
||||
return QRCode(pattern1, pattern2, pattern3, centerQR, distance);
|
||||
if (max(pattern1.moduleSize, pattern2.moduleSize) / min(pattern1.moduleSize, pattern2.moduleSize) > qrDetectorParameters.maxModuleSizeMismatch ||
|
||||
max(pattern1.moduleSize, pattern3.moduleSize) / min(pattern1.moduleSize, pattern3.moduleSize) > qrDetectorParameters.maxModuleSizeMismatch)
|
||||
return QRCode(pattern1, pattern2, pattern3, centerQR, distance);
|
||||
// QR code:
|
||||
// center right
|
||||
// 1 ________ 2
|
||||
// |_| |_|
|
||||
// | / |
|
||||
// | / |
|
||||
// | / |
|
||||
// |_ / |
|
||||
// |_|______|
|
||||
// 4
|
||||
// bottom
|
||||
|
||||
// sides length check
|
||||
const float side1 = sqrt(normL2Sqr<float>(pattern1.center - pattern2.center));
|
||||
const float side2 = sqrt(normL2Sqr<float>(pattern1.center - pattern3.center));
|
||||
const float side3 = sqrt(normL2Sqr<float>(pattern2.center - pattern3.center));
|
||||
std::array<float, 3> sides = {side1, side2, side3};
|
||||
std::sort(sides.begin(), sides.end());
|
||||
// check sides diff
|
||||
if (sides[1] / sides[0] < qrDetectorParameters.maxModuleSizeMismatch) {
|
||||
// find center pattern
|
||||
if (side1 > side2 && side1 > side3) { // centerPattern is pattern3
|
||||
std::swap(pattern3, pattern1); // now pattern1 is centerPattern
|
||||
std::swap(index.x, index.z);
|
||||
}
|
||||
else if (side2 > side1 && side2 > side3) { // centerPattern is pattern2
|
||||
std::swap(pattern2, pattern1); // now pattern1 is centerPattern
|
||||
std::swap(index.x, index.y);
|
||||
}
|
||||
// now pattern1 is centerPattern
|
||||
centerQR = (pattern2.center + pattern3.center) / 2.f;
|
||||
pattern1.setType(FinderPatternInfo::TypePattern::CENTER, centerQR);
|
||||
// check triangle angle
|
||||
if (pattern1.checkTriangleAngle(pattern2, pattern3, sides[0]*sides[1]) == false)
|
||||
return QRCode(pattern1, pattern2, pattern3, centerQR, distance);
|
||||
// check that pattern2 is right
|
||||
pattern2.setType(FinderPatternInfo::TypePattern::RIGHT, centerQR);
|
||||
bool ok = pattern1.checkAngle(pattern2, qrDetectorParameters.maxRotation);
|
||||
if (!ok) {
|
||||
// check that pattern3 is right
|
||||
pattern3.setType(FinderPatternInfo::TypePattern::RIGHT, centerQR);
|
||||
ok = pattern1.checkAngle(pattern3, qrDetectorParameters.maxRotation);
|
||||
if (ok) {
|
||||
std::swap(pattern3, pattern2); // now pattern2 is rightPattern
|
||||
std::swap(index.y, index.z);
|
||||
}
|
||||
}
|
||||
if (ok) {
|
||||
// check that pattern3 is bottom
|
||||
pattern3.setType(FinderPatternInfo::TypePattern::BOTTOM, centerQR);
|
||||
ok = pattern1.checkAngle(pattern3, qrDetectorParameters.maxRotation);
|
||||
if (ok) {
|
||||
// intersection check
|
||||
Point2f c1 = intersectionLines(pattern1.getQRCorner().second, pattern1.points[pattern1.innerCornerId],
|
||||
pattern2.getQRCorner().second, pattern2.points[pattern2.innerCornerId]);
|
||||
Point2f c2 = intersectionLines(pattern1.getQRCorner().second, pattern1.points[pattern1.innerCornerId],
|
||||
pattern3.getQRCorner().second, pattern3.points[pattern3.innerCornerId]);
|
||||
const float centerDistance = sqrt(normL2Sqr<float>(c1 - c2));
|
||||
distance = (sides[0] + sides[1] + centerDistance)*(sides[1] / sides[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
QRCode qrcode(pattern1, pattern2, pattern3, centerQR, distance);
|
||||
return qrcode;
|
||||
}
|
||||
|
||||
int calculateScoreByTimingPattern(Mat &img, const QRCodeDetectorAruco::Params& params) {
|
||||
const int minModulesInTimingPattern = 4;
|
||||
|
||||
const Point3i v1 = centerPattern.getTimingPatternScore(rightPattern.getTimingStart(FinderPatternInfo::CENTER),
|
||||
centerPattern.getTimingStart(FinderPatternInfo::RIGHT), img,
|
||||
params.maxTimingPatternMismatch);
|
||||
|
||||
if ((float)v1.z > params.maxPenalties*v1.x || v1.x <= minModulesInTimingPattern || abs(v1.y / (float)v1.x - 0.5f) > params.maxColorsMismatch)
|
||||
return std::numeric_limits<int>::max();
|
||||
|
||||
const Point3i v2 = centerPattern.getTimingPatternScore(bottomPattern.getTimingStart(FinderPatternInfo::CENTER),
|
||||
centerPattern.getTimingStart(FinderPatternInfo::BOTTOM), img,
|
||||
params.maxTimingPatternMismatch);
|
||||
|
||||
|
||||
if ((float)v2.z > params.maxPenalties*v2.x || v2.x <= minModulesInTimingPattern || abs(v2.y / (float)v2.x - 0.5f) > params.maxColorsMismatch)
|
||||
return std::numeric_limits<int>::max();
|
||||
|
||||
// TODO: add v1, v2 check, add "y" checks
|
||||
float numModules = (sqrt(normL2Sqr<float>((centerPattern.getQRCorner().second - rightPattern.getQRCorner().second)))*0.5f +
|
||||
sqrt(normL2Sqr<float>((centerPattern.getQRCorner().second - bottomPattern.getQRCorner().second))*0.5f)) / moduleSize;
|
||||
|
||||
const int sizeDelta = abs(cvRound(numModules) - (14 + v1.z < v2.z ? v1.x : v2.x));
|
||||
const int colorDelta = abs(v1.x - v1.y - v1.y) + abs(v2.x - v2.y - v2.y);
|
||||
const int score = v1.z + v2.z + sizeDelta + colorDelta;
|
||||
return score;
|
||||
}
|
||||
|
||||
QRCode& operator*=(const float scale) {
|
||||
centerPattern *= scale;
|
||||
rightPattern *= scale;
|
||||
bottomPattern *= scale;
|
||||
center *= scale;
|
||||
moduleSize *= scale;
|
||||
return *this;
|
||||
}
|
||||
|
||||
FinderPatternInfo centerPattern;
|
||||
FinderPatternInfo rightPattern;
|
||||
FinderPatternInfo bottomPattern;
|
||||
Point2f center;
|
||||
float distance = std::numeric_limits<float>::max();
|
||||
int timingPatternScore = std::numeric_limits<int>::max();
|
||||
float moduleSize = 0.f;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
static
|
||||
vector<QRCode> analyzeFinderPatterns(const vector<vector<Point2f> > &corners, const Mat& img,
|
||||
const QRCodeDetectorAruco::Params& qrDetectorParameters) {
|
||||
vector<QRCode> qrCodes;
|
||||
vector<FinderPatternInfo> patterns;
|
||||
if (img.empty())
|
||||
return qrCodes;
|
||||
float maxModuleSize = 0.f;
|
||||
for (size_t i = 0ull; i < corners.size(); i++) {
|
||||
FinderPatternInfo pattern = FinderPatternInfo(corners[i]);
|
||||
// TODO: improve thinning Aruco markers
|
||||
bool isUniq = true;
|
||||
for (const FinderPatternInfo& tmp : patterns) {
|
||||
Point2f dist = pattern.center - tmp.center;
|
||||
if (max(abs(dist.x), abs(dist.y)) < 3.f * tmp.moduleSize) {
|
||||
isUniq = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isUniq) {
|
||||
patterns.push_back(pattern);
|
||||
maxModuleSize = max(maxModuleSize, patterns.back().moduleSize);
|
||||
}
|
||||
}
|
||||
const int threshold = cvRound(qrDetectorParameters.minModuleSizeInPyramid * 12.5f) +
|
||||
(cvRound(qrDetectorParameters.minModuleSizeInPyramid * 12.5f) % 2 ? 0 : 1);
|
||||
int maxLevelPyramid = 0;
|
||||
while (maxModuleSize / 2.f > qrDetectorParameters.minModuleSizeInPyramid) {
|
||||
maxLevelPyramid++;
|
||||
maxModuleSize /= 2.f;
|
||||
}
|
||||
vector<Mat> pyramid;
|
||||
buildPyramid(img, pyramid, maxLevelPyramid);
|
||||
// TODO: ADAPTIVE_THRESH_GAUSSIAN_C vs ADAPTIVE_THRESH_MEAN_C
|
||||
for (Mat& pyr: pyramid) {
|
||||
adaptiveThreshold(pyr, pyr, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY, threshold, -1);
|
||||
}
|
||||
|
||||
for (size_t i = 0ull; i < patterns.size(); i++) {
|
||||
QRCode qrCode;
|
||||
int indexes[3] = {0};
|
||||
for (size_t j = i + 1ull; j < patterns.size(); j++) {
|
||||
for (size_t k = j + 1ull; k < patterns.size(); k++) {
|
||||
Point3i index((int)i, (int)j, (int)k);
|
||||
QRCode tmp = QRCode::checkCompatibilityPattern(patterns[i], patterns[j], patterns[k], index, qrDetectorParameters);
|
||||
if (tmp.distance != std::numeric_limits<float>::max()) {
|
||||
int levelPyramid = 0;
|
||||
QRCode qrCopy = tmp;
|
||||
while (tmp.moduleSize / 2.f > qrDetectorParameters.minModuleSizeInPyramid) {
|
||||
tmp *= 0.5f;
|
||||
levelPyramid++;
|
||||
}
|
||||
qrCopy.timingPatternScore = tmp.calculateScoreByTimingPattern(pyramid[levelPyramid], qrDetectorParameters);
|
||||
if (qrCopy.timingPatternScore != std::numeric_limits<int>::max() &&
|
||||
qrCopy.timingPatternScore * qrDetectorParameters.scaleTimingPatternScore < (float)qrCode.timingPatternScore
|
||||
&& qrCopy.distance < qrCode.distance)
|
||||
{
|
||||
qrCode = qrCopy;
|
||||
indexes[0] = (int)i;
|
||||
indexes[1] = (int)j;
|
||||
indexes[2] = (int)k;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (qrCode.distance != std::numeric_limits<float>::max()) {
|
||||
qrCodes.push_back(qrCode);
|
||||
std::swap(patterns[indexes[2]], patterns.back());
|
||||
patterns.pop_back();
|
||||
std::swap(patterns[indexes[1]], patterns.back());
|
||||
patterns.pop_back();
|
||||
std::swap(patterns[indexes[0]], patterns.back());
|
||||
patterns.pop_back();
|
||||
i--;
|
||||
}
|
||||
}
|
||||
return qrCodes;
|
||||
}
|
||||
|
||||
struct PimplQRAruco : public ImplContour {
|
||||
QRCodeDetectorAruco::Params qrParams;
|
||||
aruco::ArucoDetector arucoDetector;
|
||||
aruco::DetectorParameters arucoParams;
|
||||
|
||||
PimplQRAruco() {
|
||||
Mat bits = Mat::ones(Size(5, 5), CV_8UC1);
|
||||
Mat(bits, Rect(1, 1, 3, 3)).setTo(Scalar(0));
|
||||
Mat byteList = aruco::Dictionary::getByteListFromBits(bits);
|
||||
aruco::Dictionary dictionary = aruco::Dictionary(byteList, 5, 4);
|
||||
arucoParams.minMarkerPerimeterRate = 0.02;
|
||||
arucoDetector = aruco::ArucoDetector(dictionary, arucoParams);
|
||||
}
|
||||
|
||||
bool detectMulti(InputArray in, OutputArray points) const override {
|
||||
Mat gray;
|
||||
if (!checkQRInputImage(in, gray)) {
|
||||
points.release();
|
||||
return false;
|
||||
}
|
||||
vector<Point2f> result;
|
||||
vector<vector<Point2f> > corners;
|
||||
vector<int> ids;
|
||||
arucoDetector.detectMarkers(gray, corners, ids);
|
||||
if (corners.size() >= 3ull) {
|
||||
vector<QRCode> qrCodes = analyzeFinderPatterns(corners, gray.clone(), qrParams);
|
||||
if (qrCodes.size() == 0ull)
|
||||
return false;
|
||||
for (auto& qr : qrCodes) {
|
||||
for (Point2f& corner : qr.getQRCorners()) {
|
||||
result.push_back(corner);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (result.size() >= 4) {
|
||||
updatePointsResult(points, result);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool detect(InputArray img, OutputArray points) const override {
|
||||
vector<Point2f> corners, result;
|
||||
bool flag = detectMulti(img, corners);
|
||||
CV_Assert((int)corners.size() % 4 == 0);
|
||||
|
||||
Point2f imageCenter(((float)img.cols())/2.f, ((float)img.rows())/2.f);
|
||||
size_t minQrId = 0ull;
|
||||
float minDist = std::numeric_limits<float>::max();
|
||||
for (size_t i = 0ull; i < corners.size(); i += 4ull) {
|
||||
Point2f qrCenter((corners[i] + corners[i+1ull] + corners[i+2ull] + corners[i+3ull]) / 4.f);
|
||||
float dist = sqrt(normL2Sqr<float>(qrCenter - imageCenter));
|
||||
if (dist < minDist) {
|
||||
minQrId = i;
|
||||
minDist = dist;
|
||||
}
|
||||
}
|
||||
if (flag) {
|
||||
result = {corners[minQrId], corners[minQrId+1ull], corners[minQrId+2ull], corners[minQrId+3ull]};
|
||||
updatePointsResult(points, result);
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
};
|
||||
|
||||
QRCodeDetectorAruco::QRCodeDetectorAruco() {
|
||||
p = makePtr<PimplQRAruco>();
|
||||
}
|
||||
|
||||
QRCodeDetectorAruco::QRCodeDetectorAruco(const QRCodeDetectorAruco::Params& params) {
|
||||
p = makePtr<PimplQRAruco>();
|
||||
std::dynamic_pointer_cast<PimplQRAruco>(p)->qrParams = params;
|
||||
}
|
||||
|
||||
const QRCodeDetectorAruco::Params& QRCodeDetectorAruco::getDetectorParameters() const {
|
||||
return std::dynamic_pointer_cast<PimplQRAruco>(p)->qrParams;
|
||||
}
|
||||
|
||||
QRCodeDetectorAruco& QRCodeDetectorAruco::setDetectorParameters(const QRCodeDetectorAruco::Params& params) {
|
||||
std::dynamic_pointer_cast<PimplQRAruco>(p)->qrParams = params;
|
||||
return *this;
|
||||
}
|
||||
|
||||
aruco::DetectorParameters QRCodeDetectorAruco::getArucoParameters() {
|
||||
return std::dynamic_pointer_cast<PimplQRAruco>(p)->arucoParams;
|
||||
}
|
||||
|
||||
void QRCodeDetectorAruco::setArucoParameters(const aruco::DetectorParameters& params) {
|
||||
std::dynamic_pointer_cast<PimplQRAruco>(p)->arucoParams = params;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Reference in New Issue
Block a user