????????全世界常用和不常用的條碼類型大概有一百多種,常用的條碼類型一般是指在世界上的多個(gè)國(guó)家或者地區(qū)使用比如EAN-13碼、UPC-A碼、Code-128碼、Code-39碼、EAN/UCC-128碼、ITF-14碼等等,而不常用的條形碼可能只是在某些國(guó)家地區(qū),或者僅在某一個(gè)行業(yè)使用,比較少見(jiàn)。
? ? ? ? ?根據(jù)條形碼的長(zhǎng)邊的黑條長(zhǎng)度是否一致,本文將條形碼簡(jiǎn)單的區(qū)分為等長(zhǎng)和不等長(zhǎng)兩種類型,例如Code 128每個(gè)黑條的長(zhǎng)度都是一致的,為等長(zhǎng)條形碼,而EAN - 8、EAN - 13左右兩邊的黑條比中間的多出來(lái)一截,為不等長(zhǎng)條形碼。
? ? ? ? 最近工作中的項(xiàng)目有識(shí)別條形碼的需求,在條形碼的圖片較理想時(shí),目前的開(kāi)源庫(kù)(openCV、ZXing、ZBar等)都可以滿足需求,但是實(shí)際應(yīng)用后發(fā)現(xiàn)當(dāng)條碼存在于圖片中的某個(gè)位置時(shí)所有的開(kāi)源庫(kù)都定位不到了,當(dāng)我手動(dòng)截取條形碼的ROI區(qū)域后又可以識(shí)別成功,并且openCV目前還沒(méi)有提供對(duì)code 128的支持,所以在開(kāi)發(fā)過(guò)程中,條形碼圖片預(yù)處理和定位比條形碼的識(shí)別更重要。
? ? ? ? 條形碼的定位不同于QR碼的定位,有三個(gè)回字的輪廓可以作為顯著的特征被提取,而條碼則缺少這種特征,在背景環(huán)境較復(fù)雜時(shí),很難被精準(zhǔn)的定位。在參考了網(wǎng)上很多開(kāi)源的定位方案后,發(fā)現(xiàn)很多方案都存在一定的局限性,例如被采取最多的方案是對(duì)原圖像灰度并高斯模糊處理,在x方向上對(duì)灰度圖像求取sobel邊緣并二值化,利用形態(tài)學(xué)運(yùn)算(閉運(yùn)算、膨脹等)時(shí)離散的條形輪廓合并為完整的四邊形條形碼輪廓,通過(guò)findContours函數(shù)實(shí)現(xiàn)對(duì)輪廓的提取,最后通過(guò)形態(tài)學(xué)特征(面積、慣量比、外接矩形等)篩選出條形碼實(shí)現(xiàn)定位。
? ? ? ? ?這種方法的局限性在于,由于只對(duì)水平方向求取邊緣,當(dāng)條形碼傾斜放置時(shí)將無(wú)法定位,又因?yàn)樾螒B(tài)學(xué)計(jì)算,當(dāng)圖像背景復(fù)雜時(shí),很容易產(chǎn)生粘連現(xiàn)象從而引入干擾,并且當(dāng)條形碼的大小、拍攝角度發(fā)生變化時(shí),相鄰的黑條之間距離也會(huì)發(fā)生變化,因此需要開(kāi)發(fā)者反復(fù)調(diào)整形態(tài)學(xué)計(jì)算的參數(shù),增加了開(kāi)發(fā)者的負(fù)擔(dān),此方法僅適用于場(chǎng)景單一的環(huán)境?;谠髡叩乃悸?,經(jīng)過(guò)三天的摸索,以code 128為例,提供一種通用性更強(qiáng)的改進(jìn)方法,并開(kāi)放完整源代碼。
????????【圖像預(yù)處理】
cv::Mat src = cv::imread("path");
cv::Mat gray,bilateral,laplacian,thresh;
cv::cvtColor(src,gray,cv::COLOR_BGR2GRAY);
cv::bilateralFilter(gray,bilateral,25,25,25);
cv::Laplacian(bilateral,laplacian,bilateral.depth(),3,3);
cv::threshold(laplacian,thresh,64,255,cv::THRESH_BINARY);
? ? ? ??對(duì)輸入圖像src灰度變換,進(jìn)行雙邊濾波在減少圖像顆粒噪聲的同時(shí)保留圖像邊緣,求取拉普拉斯導(dǎo)數(shù)得到邊緣圖像,最后通過(guò)threshold對(duì)圖像二值化,得到黑白輪廓圖像,這邊不建議使用Canny作為邊緣提取的算子,因?yàn)镃anny算子提取出的邊緣太細(xì),且當(dāng)條形碼黑條間距過(guò)小時(shí)存在粘連,不利于后續(xù)處理
?【輪廓提取】
for(size_t index = 0;index < contours.size();index++){
std::vector<cv::Point> blob = contours[index];
cv::RotatedRect minRect = cv::minAreaRect(blob);
double area = cv::contourArea(blob);
if(area < 20){
continue;
}
float min = std::fmin(minRect.size.width,minRect.size.height);
float max = std::fmax(minRect.size.width,minRect.size.height);
float inertia_ratio = max/min;
if(inertia_ratio < 5){
continue;
}
if(area / double(min * max) < 0.1){
continue;
}
ptSet.push_back(minRect.center);
rectSet.push_back(minRect);
contourSet.push_back(blob);
cv::drawContours(contourImage,contours,int(index),cv::Scalar(0,0,255),-1);
}
????????使用findContours函數(shù)進(jìn)行輪廓提取,篩選出所有符合條件的輪廓,例如這里我們篩選掉面積不足20像素,慣量比(長(zhǎng)軸與短軸尺寸的比值)不足5,并且輪廓實(shí)際面積不及最小外接矩形面積的十分之一的輪廓,一輪篩選后,我們得到了可能滿足是條形碼的所有輪廓,并記錄下它們的中心坐標(biāo)
??【條形碼擬合】
? ? ? ? 上一步中,得到了所有可能的條形的中心坐標(biāo),因?yàn)楸疚囊詂ode 128為例,黑條的長(zhǎng)度相等,那么所有黑條的中心點(diǎn)一定位于同一條直線上,那么如何擬合出這一條直線呢,因?yàn)槠渌蓴_項(xiàng)的存在,最小二乘法的效果往往不好,所以采用RANSAC(隨機(jī)抽樣一致算法),通過(guò)隨機(jī)抽樣擬合,多次迭代后排除掉異常點(diǎn)即得到我們想要的直線,本文不對(duì)算法做詳細(xì)的介紹,只在末尾提供源代碼。經(jīng)過(guò)擬合后根據(jù)中心點(diǎn)是否位于直線上即可得到目標(biāo)條形碼的輪廓。
? ? ? ? ?如圖所示,紅色輪廓為干擾項(xiàng),綠色輪廓為條形碼,圓圈為每個(gè)輪廓的中心點(diǎn),水平的黃色直線為擬合出的直線,對(duì)所有綠色的輪廓作為一個(gè)點(diǎn)集合,使用cv::minAreaRect函數(shù)得到最小外接矩形,如藍(lán)色矩形所示,垂直的黃色直線為其垂線,以這兩條直線為坐標(biāo)系,根據(jù)中學(xué)就學(xué)到的點(diǎn)與直線的關(guān)系,即可得到條形碼四個(gè)象限的各自角點(diǎn)坐標(biāo)
????????這個(gè)過(guò)程還會(huì)存在一個(gè)問(wèn)題:有些干擾項(xiàng)的中心點(diǎn)正好落在了直線上,因此也被擬合了進(jìn)去,針對(duì)這個(gè)現(xiàn)象采用中位數(shù)過(guò)濾方法,具體思路是根據(jù)條形碼每個(gè)黑條輪廓的長(zhǎng)軸尺寸、以及最小外接矩形的旋轉(zhuǎn)角度基本一致,可以作為一個(gè)很好的二維特征作為區(qū)分條碼和干擾項(xiàng)的依據(jù),對(duì)這個(gè)特征各自取中位數(shù),想象把他們放在一個(gè)二維坐標(biāo)系上,條碼的特征點(diǎn)是非常密集的聚集在一起的,如黃色圈內(nèi)的綠點(diǎn)所示,而干擾項(xiàng)就是離黃圈圓心(也就是中位數(shù))非常遠(yuǎn)的紅色點(diǎn),根據(jù)每個(gè)特征點(diǎn)到中位數(shù)的距離,設(shè)定一個(gè)閾值,即可排除掉所有的干擾
?【透視變換實(shí)現(xiàn)條形碼校正】
? ? ? ? 經(jīng)過(guò)了三輪篩選,我們終于得到了非常具體的條形碼位置信息,但是對(duì)于不夠成熟的開(kāi)源讀碼庫(kù)來(lái)說(shuō)還是需要做最后一步處理,也就是用過(guò)透視變換將條形碼校正,上一步中提到了我們要精準(zhǔn)的獲取條形碼各個(gè)象限的角點(diǎn)坐標(biāo),也是為了透視變換而服務(wù)的。透視變換的前后四個(gè)角點(diǎn)必須一一對(duì)應(yīng),而一旦角點(diǎn)找錯(cuò)了,進(jìn)行透視變換的時(shí)候就會(huì)出錯(cuò),由于條形碼的長(zhǎng)度、旋轉(zhuǎn)角度無(wú)法確定,故不能通過(guò)簡(jiǎn)單的方式判斷哪個(gè)點(diǎn)是左上角、哪個(gè)點(diǎn)是右下角,必須根據(jù)點(diǎn)與直線的關(guān)系來(lái)確定。為了給變換后的條形碼保留一片空白邊框區(qū)域,變換前的四個(gè)角點(diǎn)應(yīng)該往外點(diǎn),也就是等比例適當(dāng)放大其外接矩形,取放大后的角點(diǎn)。經(jīng)過(guò)測(cè)試,在不同條碼大小、拍攝角度、不同背景環(huán)境下,本方法均能夠較好的實(shí)現(xiàn)定位,本文示例圖片定位的結(jié)果如下圖所示:
????????解決了單一等長(zhǎng)條形碼的定位問(wèn)題,針對(duì)更加復(fù)雜的情況,例如不等長(zhǎng)條碼、或者多條形碼同時(shí)提取的問(wèn)題,暫無(wú)具體的源代碼,有需要的讀者可以嘗試下面的思路:
????????針對(duì)定位不等長(zhǎng)的條形碼,由于條形碼的不等長(zhǎng),所有中心點(diǎn)的位置無(wú)法擬合成一條完美的直線,那么數(shù)量少的長(zhǎng)邊就會(huì)被過(guò)濾掉,針對(duì)這個(gè)現(xiàn)象,可以調(diào)整RANCAC算法的參數(shù),也可以根據(jù)點(diǎn)到直線的距離公式,單獨(dú)遍歷每一個(gè)中心點(diǎn)到擬合直線的距離,再根據(jù)實(shí)際情況篩選。
? ? ? ? 對(duì)于多條形碼的定位,我們可以按部就班使用上面的方法,僅需要實(shí)現(xiàn)每定位一個(gè)條形碼,就把其相關(guān)的輪廓、中心點(diǎn)等信息從集合中剔除,循環(huán)以上的過(guò)程,直到圖像中再也找不到其他條形碼為止。
【完整代碼】
????????RANSAC直線擬合、中位數(shù)濾波代碼
#ifndef RANSAC_H
#define RANSAC_H
#include <opencv2/opencv.hpp>
class Filter
{
public:
static Filter * GetInstance();
public:
void filterLineRANSAC(std::vector<cv::Point2d> ptSet,double & a,double & b,double & c,std::vector<bool> & inlierFlag);
void filterOutLierPointMAD(std::vector<cv::Point2d> ptSet,double threshold,std::vector<bool> & inlierFlag);
private:
Filter() = default;
~Filter() = default;
private:
//直線樣本中兩隨機(jī)點(diǎn)位置不能太近
bool verifyComposition(const std::vector<cv::Point2d> pts);
//得到直線擬合樣本,即在直線采樣點(diǎn)集上隨機(jī)選2個(gè)點(diǎn)
bool getSample(std::vector<int> set, std::vector<int> &sset);
//根據(jù)點(diǎn)集擬合直線ax+by+c=0,res為殘差
void calcLinePara(std::vector<cv::Point2d> pts, double & a, double & b, double & c, double & res);
//生成[0,1]之間符合高斯分布的數(shù)
double gaussianRandom();
//生成[0,1]之間符合均勻分布的數(shù)
double uniformRandom();
};
#endif // RANSAC_H
#include "Filter.h"
Filter * Filter::GetInstance()
{
static Filter obj;
return &obj;
}
double Filter::gaussianRandom(void)
{
static int next_gaussian = 0;
static double saved_gaussian_value;
double fac, rsq, v1, v2;
if (next_gaussian == 0) {
do {
v1 = 2 * uniformRandom() - 1;
v2 = 2 * uniformRandom() - 1;
rsq = v1*v1 + v2*v2;
} while (rsq >= 1.0 || rsq == 0.0);
fac = sqrt(-2 * log(rsq) / rsq);
saved_gaussian_value = v1*fac;
next_gaussian = 1;
return v2*fac;
}
else {
next_gaussian = 0;
return saved_gaussian_value;
}
}
double Filter::uniformRandom(void)
{
return double(rand()) / double(RAND_MAX);
}
void Filter::calcLinePara(std::vector<cv::Point2d> pts, double & a, double & b, double & c, double & res)
{
res = 0;
cv::Vec4f line;
std::vector<cv::Point2f> ptsF;
for (unsigned int i = 0; i < pts.size(); i++){
ptsF.push_back(pts[i]);
}
fitLine(ptsF, line, cv::DIST_L2, 0, 1e-2, 1e-2);
a = double(line[1]);
b = double(-line[0]);
c = double(line[0] * line[3] - line[1] * line[2]);
for (unsigned int i = 0; i < pts.size(); i++)
{
double resid_ = fabs(pts[i].x * a + pts[i].y * b + c);
res += resid_;
}
res /= pts.size();
}
bool Filter::getSample(std::vector<int> set, std::vector<int> &sset)
{
int i[2];
if (set.size() > 2)
{
do
{
for (int n = 0; n < 2; n++)
i[n] = int(uniformRandom() * (set.size() - 1));
} while (!(i[1] != i[0]));
for (int n = 0; n < 2; n++)
{
sset.push_back(i[n]);
}
}
else
{
return false;
}
return true;
}
bool Filter::verifyComposition(const std::vector<cv::Point2d> pts)
{
cv::Point2d pt1 = pts[0];
cv::Point2d pt2 = pts[1];
if (abs(pt1.x - pt2.x) < 5 && abs(pt1.y - pt2.y) < 5)
return false;
return true;
}
void Filter::filterLineRANSAC(std::vector<cv::Point2d> ptSet, double & a, double & b, double & c, std::vector<bool> & inlierFlag)
{
double residual_error = 2.99; //內(nèi)點(diǎn)閾值
bool stop_loop = false;
int maximum = 0; //最大內(nèi)點(diǎn)數(shù)
//最終內(nèi)點(diǎn)標(biāo)識(shí)及其殘差
inlierFlag = std::vector<bool>(ptSet.size(), false);
std::vector<double> resids_(ptSet.size(), 3);
int sample_count = 0;
int N = 500;
double res = 0;
// Filter
srand(uint(time(nullptr))); //設(shè)置隨機(jī)數(shù)種子
std::vector<int> ptsID;
for (unsigned int i = 0; i < ptSet.size(); i++){
ptsID.push_back(int(i));
}
while (N > sample_count && !stop_loop)
{
std::vector<bool> inlierstemp;
std::vector<double> residualstemp;
std::vector<int> ptss;
int inlier_count = 0;
if (!getSample(ptsID, ptss))
{
stop_loop = true;
continue;
}
std::vector<cv::Point2d> pt_sam;
pt_sam.push_back(ptSet[uint(ptss[0])]);
pt_sam.push_back(ptSet[uint(ptss[1])]);
if (!verifyComposition(pt_sam))
{
++sample_count;
continue;
}
// 計(jì)算直線方程
calcLinePara(pt_sam, a, b, c, res);
//內(nèi)點(diǎn)檢驗(yàn)
for (unsigned int i = 0; i < ptSet.size(); i++)
{
cv::Point2d pt = ptSet[i];
double resid_ = fabs(pt.x * a + pt.y * b + c);
residualstemp.push_back(resid_);
inlierstemp.push_back(false);
if (resid_ < residual_error)
{
++inlier_count;
inlierstemp[i] = true;
}
}
// 找到最佳擬合直線
if (inlier_count >= maximum)
{
maximum = inlier_count;
resids_ = residualstemp;
inlierFlag = inlierstemp;
}
// 更新Filter迭代次數(shù),以及內(nèi)點(diǎn)概率
if (inlier_count == 0)
{
N = 500;
}
else
{
double epsilon = 1.0 - double(inlier_count) / double(ptSet.size()); //野值點(diǎn)比例
double p = 0.99; //所有樣本中存在1個(gè)好樣本的概率
double s = 2.0;
N = int(log(1.0 - p) / log(1.0 - pow((1.0 - epsilon), s)));
}
++sample_count;
}
}
void Filter::filterOutLierPointMAD(std::vector<cv::Point2d> ptSet,double threshold,std::vector<bool> & inlierFlag)
{
double * data_x = new double[ptSet.size()];
double * data_y = new double[ptSet.size()];
for(size_t index = 0;index < ptSet.size();index++){
data_x[index] = ptSet[index].x;
data_y[index] = ptSet[index].y;
}
auto on_sort = [](double v1,double v2){
return v1 > v2;
};
std::sort(data_x,&data_x[ptSet.size()],on_sort);
std::sort(data_y,&data_y[ptSet.size()],on_sort);
double median_x = (ptSet.size() % 2 == 1) ? data_x[ptSet.size() / 2] : (data_x[ptSet.size() / 2] + data_x[ptSet.size() / 2 - 1]) / 2;
double median_y = (ptSet.size() % 2 == 1) ? data_y[ptSet.size() / 2] : (data_y[ptSet.size() / 2] + data_y[ptSet.size() / 2 - 1]) / 2;
cv::Point2d median = cv::Point2d(median_x,median_y);
auto distance = [](cv::Point2d p1,cv::Point2d p2) -> double{
return std::pow(std::pow(p1.x - p2.x,2) + std::pow(p1.y - p2.y,2),0.5);
};
inlierFlag.clear();
for(size_t index = 0;index < ptSet.size();index++){
inlierFlag.push_back(distance(ptSet[index],median) < threshold);
}
delete[] data_x;
delete[] data_y;
}
????????條形碼定位識(shí)別代碼,本文采用QZxing庫(kù)
QString decodeBarCode(cv::Mat & src)
{
cv::Mat gray,bilateral,laplacian,thresh;
cv::cvtColor(src,gray,cv::COLOR_BGR2GRAY);
cv::bilateralFilter(gray,bilateral,25,25,25);
cv::Laplacian(bilateral,laplacian,bilateral.depth(),3,3);
cv::threshold(laplacian,thresh,64,255,cv::THRESH_BINARY);
cv::imshow("gray",gray);
cv::imshow("bilateral",bilateral);
cv::imshow("laplacian",laplacian);
cv::imshow("thresh",thresh);
cv::Mat contourImage(src.rows,src.cols,CV_8UC3,cv::Scalar(0,0,0));
std::vector<std::vector<cv::Point>> contours;
std::vector<cv::Vec4i> hierarchy;
std::vector<cv::Point2d> ptSet;
std::vector<cv::RotatedRect> rectSet;
std::vector<std::vector<cv::Point>> contourSet;
cv::findContours(thresh,contours,hierarchy,cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
for(size_t index = 0;index < contours.size();index++){
std::vector<cv::Point> blob = contours[index];
cv::RotatedRect minRect = cv::minAreaRect(blob);
double area = cv::contourArea(blob);
if(area < 20){
continue;
}
float min = std::fmin(minRect.size.width,minRect.size.height);
float max = std::fmax(minRect.size.width,minRect.size.height);
float inertia_ratio = max/min;
if(inertia_ratio < 5){
continue;
}
if(area / double(min * max) < 0.1){
continue;
}
ptSet.push_back(minRect.center);
rectSet.push_back(minRect);
contourSet.push_back(blob);
cv::drawContours(contourImage,contours,int(index),cv::Scalar(0,0,255),-1);
}
if(ptSet.size() < 10){
return QString();
}
double A, B, C;
std::vector<bool> inliers_LINE;
Filter::GetInstance()->filterLineRANSAC(ptSet, A, B, C, inliers_LINE);
std::vector<std::vector<cv::Point>> contour_ok;
std::vector<cv::Point2d> keypoint_set;
for(size_t index = 0;index < inliers_LINE.size();index++){
if(inliers_LINE[index]){
double angle = std::min(double(rectSet[index].angle),90 - double(rectSet[index].angle));
double length = std::max(double(rectSet[index].size.width),double(rectSet[index].size.height));
keypoint_set.push_back(cv::Point2d(angle,length));
contour_ok.push_back(contourSet[index]);
cv::circle(contourImage,ptSet[index],10,cv::Scalar(255,255,255),2);
} else {
cv::circle(contourImage,ptSet[index],10,cv::Scalar(128,128,128),2);
}
}
cv::drawContours(contourImage,contour_ok,-1,cv::Scalar(0,255,0),-1);
std::vector<bool> inliers_POINT;
Filter::GetInstance()->filterOutLierPointMAD(keypoint_set,10,inliers_POINT);
std::vector<cv::Point> contours_barCode;
for(size_t index = 0;index < keypoint_set.size();index++){
if(inliers_POINT[index]){
contours_barCode.insert(contours_barCode.end(), contour_ok[index].begin(), contour_ok[index].end());
}
}
if(contours_barCode.empty()){
return QString();
}
cv::RotatedRect barCodeRect = cv::minAreaRect(contours_barCode);
cv::Size2f size = barCodeRect.size;
size.width = std::min(size.width * 1.2f,size.width + 30);
size.height = std::min(size.height * 1.2f,size.height + 30);
barCodeRect = cv::RotatedRect(barCodeRect.center,size,barCodeRect.angle);
drawRotatedRect(contourImage,barCodeRect,cv::Scalar(255,255,0),2);
cv::Point2f arr[4];
barCodeRect.points(arr);
cv::Mat dst_warp,dst_warpRotateScale,dst_warpTransformation,dst_warpFlip;
cv::Point2f srcPoints[4];
cv::Point2f dstPoints[4];
cv::Point2f center = barCodeRect.center;
if(fabs(B) > 0){
double v_A = B;
double v_B = -A;
double v_C = A * double(center.y) - B * double(center.x);
auto h_function = [A,B,C](double x)->double{
return -A/B*x - C/B;
};
auto v_function = [v_A,v_B,v_C](double y)->double{
return - y * v_B / v_A - v_C / v_A;
};
for(size_t index = 0;index < 4;index++){
double x = double(arr[index].x);
double y = double(arr[index].y);
if(y < h_function(x) && x > v_function(y)){
srcPoints[0] = arr[index];
cv::circle(contourImage,arr[index],10,cv::Scalar(255,255,128),-1);
}
if(y < h_function(x) && x < v_function(y)){
srcPoints[1] = arr[index];
cv::circle(contourImage,arr[index],10,cv::Scalar(255,128,255),-1);
}
if(y > h_function(x) && x < v_function(y)){
srcPoints[2] = arr[index];
cv::circle(contourImage,arr[index],10,cv::Scalar(255,128,128),-1);
}
if(y > h_function(x) && x > v_function(y)){
srcPoints[3] = arr[index];
cv::circle(contourImage,arr[index],10,cv::Scalar(0,128,255),-1);
}
}
cv::Point2d ptStart, ptEnd;
ptStart.x = 0;
ptStart.y = h_function(ptStart.x);
ptEnd.x = contourImage.cols;
ptEnd.y = h_function(ptEnd.x);
cv::line(contourImage, ptStart, ptEnd, cv::Scalar(0, 255, 255), 2, 8);
ptStart.x = v_function(0);
ptStart.y = 0;
ptEnd.x = v_function(contourImage.rows);
ptEnd.y = contourImage.rows;
cv::line(contourImage, ptStart, ptEnd, cv::Scalar(0, 255, 255), 2, 8);
} else {
for(size_t index = 0;index < 4;index++){
double x = double(arr[index].x);
double y = double(arr[index].y);
if(x > -C/A && y < double(center.y)){
srcPoints[0] = arr[index];
cv::circle(contourImage,arr[index],10,cv::Scalar(255,255,128),-1);
}
if(x < -C/A && y < double(center.y)){
srcPoints[1] = arr[index];
cv::circle(contourImage,arr[index],10,cv::Scalar(255,128,255),-1);
}
if(x < -C/A && y > double(center.y)){
srcPoints[2] = arr[index];
cv::circle(contourImage,arr[index],10,cv::Scalar(255,128,128),-1);
}
if(x > -C/A && y > double(center.y)){
srcPoints[3] = arr[index];
cv::circle(contourImage,arr[index],10,cv::Scalar(0,128,255),-1);
}
cv::circle(contourImage,arr[index],10,cv::Scalar(255,255,255),2);
}
cv::Point2d ptStart, ptEnd;
ptStart.x = -C/A;
ptStart.y = 0;
ptEnd.x = -C/A;
ptEnd.y = contourImage.rows;
cv::line(contourImage, ptStart, ptEnd, cv::Scalar(0, 255, 255), 2, 8);
ptStart.x = 0;
ptStart.y = double(center.y);
ptEnd.x = contourImage.cols;
ptEnd.y = double(center.y);
cv::line(contourImage, ptStart, ptEnd, cv::Scalar(0, 255, 255), 2, 8);
}
auto distance = [](cv::Point2f p1,cv::Point2f p2)->double{
return std::pow(std::pow(p1.x - p2.x,2) + std::pow(p1.y - p2.y,2),0.5);
};
int width = int(distance(srcPoints[0],srcPoints[1])) + int(distance(srcPoints[2],srcPoints[3]));
int height = int(distance(srcPoints[1],srcPoints[2])) + int(distance(srcPoints[0],srcPoints[3]));
dstPoints[0] = cv::Point2f(width,0);
dstPoints[1] = cv::Point2f(0,0);
dstPoints[2] = cv::Point2f(0,height);
dstPoints[3] = cv::Point2f(width,height);
cv::Mat M1 = cv::getPerspectiveTransform(srcPoints, dstPoints);
warpPerspective(src, dst_warp, M1, cv::Size(width,height));
cv::imshow("dst_warp",dst_warp);
cv::waitKey(0);
QZXing qzxing;
qzxing.setDecoder(QZXing::DecoderFormat_CODE_128);
return qzxing.decodeImage(Mat2Image(dst_warp));
}
參考博客:opencv練習(xí)--條形碼定位識(shí)別_opencv識(shí)別條形碼位置_ZZU-Hanqi_Duan的博客-CSDN博客
RANSAC估計(jì)——以直線擬合為例_隨機(jī)采樣一致性擬合直線_Gareth Wang的博客-CSDN博客文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-778008.html
C++去除離群值 MAD算法 - 月下叉猹 - 博客園 (cnblogs.com)文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-778008.html
到了這里,關(guān)于一種改進(jìn)的條形碼定位方案,基于openCV實(shí)現(xiàn),附完整源代碼的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!