一、介紹
算法功能:
對QR碼進行x,y方向定位和旋轉角度計算,並獲取QR碼的二進制內容
算法優勢:
1.計算速度快,可達4-7ms(使用cpu i7-8750)。
2.定位精度高,x,y方向精度爲±1mm,轉角精度爲±0.1°(使用某寶幾十元彩色相機,30w像素,噪聲較爲嚴重)。
3.採用自動閾值方法,對光照不敏感。
4.採用不規則四邊形輪廓提取和網格劃分,支持二維碼翻轉識別,最大翻轉傾角可達45°。
5.對QR碼的規模自動計算,可用於不同行列數的QR碼。
先看幾張效果圖
二、思路和代碼(共10步)
閱讀前注意!!!本算法使用C++11和Qt共同開發,某些數據類型(如字符串QString、鏈表QList)爲Qt專屬類型,可能需要稍加改動後才能供讀者使用。
本文重在分享思路,若需要源碼,請在評論中留言。
1.彩色圖轉灰度圖+高斯濾波
也可以不進行濾波,如果使用濾波算法,核不推薦過大(此處採用Size(1, 1))。
Mat src_gray;
cvtColor(srcImg1, src_gray, CV_BGR2GRAY); //彩色圖轉灰度圖
GaussianBlur(src_gray, src_gray, Size(1, 1),2, 2, BORDER_DEFAULT); //高斯濾波
imshow("1.彩色圖轉灰度圖+高斯濾波", src_gray);
2.二值化(Otsu自動閾值)
Mat threshold_output;
threshold(src_gray, threshold_output, 0, 255, THRESH_BINARY|THRESH_OTSU); //Otsu 二值化
imshow("2.二值化(Otsu自動閾值)", threshold_output);
3.形態學濾波(開運算+閉運算)
開運算和開運算是基於幾何運算的濾波器。
開運算(先腐蝕,後膨脹)能夠除去孤立的小點,毛刺和小橋,而總的位置和形狀不變。閉運算(先膨脹,後腐蝕)能夠填平小湖(即小孔),彌合小裂縫,而總的位置和形狀不變。
參數MORPH_ELLIPSE表示使用橢圓形運算核,可使處理後的邊界較爲圓滑。
Mat threshold_output_copy = threshold_output.clone();
Mat element = getStructuringElement(MORPH_ELLIPSE, Size(3, 3));
morphologyEx(threshold_output_copy, threshold_output_copy, MORPH_OPEN, element); //開運算
morphologyEx(threshold_output_copy, threshold_output_copy, MORPH_CLOSE, element); //閉運算
imshow("3.開運算+閉運算", threshold_output_copy);
4.邊緣檢測
使用Canny算子進行邊緣檢測,爲下一步提取輪廓做準備。
vector<vector<Point> > contours;
vector<Vec4i> hierarchy;
Mat canny_output;
Canny( threshold_output_copy, canny_output, 1, 3, 7,true ); //Canny檢測
imshow("4.Canny邊緣檢測", canny_output);
5.輪廓提取
使用findContours函數對邊緣提取後的圖像進行輪廓提取。
Mat image=canny_output.clone();
findContours(image,contours,hierarchy,RETR_TREE,CHAIN_APPROX_SIMPLE,Point());
Mat Contours=Mat::zeros(image.size(),CV_8UC1);
//繪製 //contours[i]代表的是第i個輪廓,contours[i].size()代表的是第i個輪廓上所有的像素點數
for(int i=0;i<contours.size();i++)
for(int j=0;j<contours[i].size();j++){
Point P=Point(contours[i][j].x,contours[i][j].y);
Contours.at<uchar>(P)=255;
}
imshow("5.findContours輪廓提取",Contours); //輪廓
6.篩選出三層獨立包含特點的輪廓
QList<vector<vector<Point>>> qrPointList;//每個節點表示一個回形點集
vector<vector<Point>> qrPoint;
Mat mat6=Contours.clone();
cvtColor(mat6, mat6, CV_GRAY2BGR);
int Parindex[3]={-1,-1,-1};
for (int i = 0; i < contours.size(); i++){
if (hierarchy[i][3] != -1 && hierarchy[i][2] == -1){
Parindex[0]=hierarchy[i][3];
if (hierarchy[(Parindex[0])][3] != -1){
Parindex[1]=hierarchy[(Parindex[0])][3];
if (hierarchy[(Parindex[1])][3] != -1){
Parindex[2]=hierarchy[(Parindex[1])][3];
if (hierarchy[(Parindex[2])][3] != -1){
if(!(i-1==Parindex[0]&&Parindex[0]-1==Parindex[1]&&Parindex[1]-1==Parindex[2]))
continue; //都是獨生輪廓
qrPoint.push_back(contours[i]);
qrPoint.push_back(contours[i-2]);
qrPoint.push_back(contours[i-4]);
for(int i=0;i<qrPoint.size();i++)
for(int j=0;j<qrPoint[i].size();j++)
circle(mat6,qrPoint[i][j],2,Scalar(0,255,0),-1);
qrPointList.push_back(qrPoint);
qrPoint.clear();
}
}
}
}
}
imshow("6.檢測出的三層輪廓",mat6); //輪廓
7.若篩選出的三層輪廓數目大於3,進一步篩選,最終只保留三個
此處代碼可自行編寫,我的思路爲:
1.將每組三層輪廓分別擬合最小外接矩形,然後根據同心度、三層周長比例(3:5:7)、最小允許周長進行初步篩選。
2.對剩下的進行最終篩選,篩選標準爲正確的QR碼三個角元素應有一定的平行度,尺寸也相近。
注意!!!此段代碼12,13行分別根據輪廓點集計算出最小外接多邊形和最小外接矩形,由於鏡頭與QR碼可能存在傾角,故後面使用最小外接四邊形進行定位,精度更高。
要注意兩者的區別,其中approxPolyDP的精度設定非常重要,精度不宜過高(此處選擇精度爲5,值越小精度越高),否則會使得擬合出的四邊形邊數過多。
QList<Point> pointList; //存儲角點中心
QList<RotatedRect> RectList; //存儲角元素最外層矩形
QList<vector<Point>> OutquadList; //存儲最外層擬合四邊形角點
vector<bool> qrPointListEnable(qrPointList.size()); //篩選時使用
for (int L=0;L<qrPointList.size(); L++)//遍歷每個可能的圖元
{
qrPoint=qrPointList.at(L);
vector<vector<Point>> contours_poly(qrPoint.size());
vector<RotatedRect> minRect(qrPoint.size()); //存儲了嵌套的最小外接矩形*****
vector<Point2f> rect_center(qrPoint.size());
for (int i = 0; i < qrPoint.size(); i++){
approxPolyDP(Mat(qrPoint[i]), contours_poly[i], 5, true);//用指定精度逼近多邊形曲線
minRect[i] = minAreaRect(Mat(qrPoint[i])); //得到最小外接矩形
rect_center[i]=minRect[i].center; //得到最小外接矩形中心
}
//根據同心度篩選
for (int i = 0; i < minRect.size()-1; i++){
Point P1=Point(rect_center[i].x,rect_center[i].y);
Point P2=Point(rect_center[i+1].x,rect_center[i+1].y);
float ConcenError_Set=(minRect[i].size.width+minRect[i].size.height)/12; //***最大允差設定***
if( sqrt(pow(P1.x-P2.x,2)+pow(P1.y-P2.y,2)) > ConcenError_Set ){
qrPointListEnable[L]=false;
break; }
else
qrPointListEnable[L]=true;
}
if(!qrPointListEnable[L])continue;
//根據三層周長比例進行篩選(3:5:7)
for (int i = 0; i < minRect.size()-1; i++) {
float circum1=(minRect[i].size.width+minRect[i].size.height)*2;
float circum2=(minRect[i+1].size.width+minRect[i+1].size.height)*2;
if( circum1/circum2>=0.5 && circum1/circum2<=0.8 ) //***周長比例設定***
qrPointListEnable[L]=true;
else{
qrPointListEnable[L]=false;
break; }
}
if(!qrPointListEnable[L])continue;
//周長不能過小
for (int i = 0; i < minRect.size(); i++){
float circum=(minRect[i].size.width+minRect[i].size.height)*2;
float circum_Set=20; //***有效周長最小值設定***
if( circum >= circum_Set )
qrPointListEnable[L]=true;
else{
qrPointListEnable[L]=false;
break; }
}
if(!qrPointListEnable[L])continue;
//篩選完畢!!!篩選出的個數可能爲任意自然數
for (int i = 0; i<qrPoint.size(); i++){
Point2f rect_points[4];
minRect[i].points(rect_points);
if(i==2)
RectList.push_back(minRect[i]); //RectList賦值 篩選後的最外層外接矩形
bool exsit=false;
Point P=Point(rect_center[i].x,rect_center[i].y);
for(int j=0;j<pointList.size();j++){
if( fabs(pointList.at(j).x-P.x)<10 && fabs(pointList.at(j).y-P.y)<10 ){
exsit=true; break; }
}
if(!exsit||pointList.size()==0)
pointList.append(P); //pointList賦值 篩選後的三層同心中心點
if(i==2)
OutquadList.append(contours_poly[i]); //OutquadList賦值 最外層外接四邊形
}
}
//8 //最終篩選,保留可能性最大的三個角點和輪廓
if(RectList.size()>3){
QList<float> RectSizeErrorList; //尺寸誤差
for(int i=0;i<RectList.size();i++){
float RectSizeError=0;
float RectSize1=( RectList.at(i).size.width + RectList.at(i).size.height )*2;
for(int j=0;j<RectList.size();j++ && j!=i){
float RectSize2=( RectList.at(j).size.width + RectList.at(j).size.height )*2;
float Error= fabs( RectSize1 - RectSize2 );
RectSizeError+=Error;
}
RectSizeErrorList.append(RectSizeError);
}
QList<float> RectAngleErrorList; //角度誤差
for(int i=0;i<RectList.size();i++){
float RectAngleError=0;
float RectAngle1=RectList.at(i).angle;
for(int j=0;j<RectList.size();j++ && j!=i){
float RectAngle2=RectList.at(j).angle;
float Error= fabs( RectAngle1 - RectAngle2 );
RectAngleError+=Error;
}
RectAngleErrorList.append(RectAngleError);
}
QList<float> RectErrorList; //綜合誤差
for(int i=0;i<RectList.size();i++)
RectErrorList.append(RectSizeErrorList.at(i)+RectAngleErrorList.at(i));
for(int i=RectErrorList.size()-2;i>=0;i--) //根據綜合誤差 對 矩形鏈表 進行排序(從小到大)
for(int j=0;j<=i;j++){
if(RectErrorList.at(j+1)<RectErrorList.at(j)){
RectErrorList.swap(j+1,j);
RectList.swap(j+1,j);
pointList.swap(j+1,j);
OutquadList.swap(j+1,j);
}
}
//剔除誤識別點
while(RectList.size()>3) RectList.removeLast();
while(pointList.size()>3) pointList.removeLast();
while(OutquadList.size()>3) OutquadList.removeLast();
}
else if(RectList.size()<3)
{
std::string text = "NULL";
available=false;
int font_face = cv::FONT_HERSHEY_COMPLEX;
Point origin;
double font_scale = 1;
int thickness = 2;
origin.x = 50; origin.y = 40;
cv::putText(srcImgF, text, origin, font_face, font_scale, cv::Scalar(0, 0, 255), thickness, 8, 0);
}
/*************************************重要代碼段******************************************
*至此已有 RectList: type=QList<RotatedRect> size=3 //存儲角元素最外層最小外接矩形
* pointList: type=QList<Point> size=3 //存儲角點中心
* OutquadList:type=QList<vector<Point>> size=3 //存儲最外層擬合四邊形角點
***************************************************************************************/
8.對QR碼三個角元中心點進行排序(左上0,右上1,左下2)
我的思路:將三個中心點連接爲三角形,分別計算每個對應內角,最接近90度的即爲左上角點,然後以左上角點爲起點,其餘兩個點爲終點作出兩個向量,計算兩向量夾角,通過夾角正負確定其餘兩個角點
//對角點和矩形進行位置排序(左上:1 右上:2 左下:3)
QList<float> angleList;
for(int i=0;i<pointList.size();i++) //計算每個點的內角
{
float angle=0;
Point thispoint=pointList.at(i); //本點
Point otherpoint[2]; //其餘兩個點
if(i==0){
otherpoint[0] = pointList.at(1);
otherpoint[1] = pointList.at(2);}
else if(i==1){
otherpoint[0] = pointList.at(0);
otherpoint[1] = pointList.at(2);}
else{
otherpoint[0] = pointList.at(0);
otherpoint[1] = pointList.at(1);}
float a=sqrt( pow(thispoint.x-otherpoint[1].x,2) + \
pow(thispoint.y-otherpoint[1].y,2) ); //邊a(otherpoint[0]的對邊)
float b=sqrt( pow(otherpoint[0].x-otherpoint[1].x,2) + \
pow(otherpoint[0].y-otherpoint[1].y,2) ); //邊b(thispoint的對邊)
float c=sqrt( pow(thispoint.x-otherpoint[0].x,2) + \
pow(thispoint.y-otherpoint[0].y,2) ); //邊c(otherpoint[1]的對邊)
angle=acos( ( a*a + c*c -b*b ) / (2*a*c) )*180/M_PI;
angleList.append(angle);
}
for(int i=angleList.size()-2;i>=0;i--) //確定0號點位置
for(int j=0;j<=i;j++)
{
float error1=fabs(angleList.at(j)-90);
float error2=fabs(angleList.at(j+1)-90);
if(error2 < error1){
angleList.swap(j+1,j);
pointList.swap(j+1,j);
RectList.swap(j+1,j);}
}
float Angle=getAngelOfTwoVector(pointList.at(1),pointList.at(2),pointList.at(0)); //以0爲中心,2到1的角度
if(Angle<0) //確定1,2號點位置
pointList.swap(1,2);
9.對QR碼進行精確定位
第一步:獲取三個角元共12個角點(通過之前的擬合四邊形結果)
第二步:計算出第四個角元的四個角點
第三步:計算出四個中心點,進一步計算出QR碼中心點
第四步:計算出QR碼正方向,並繪製箭頭進行表示。
//粗略計算QR碼中心
Point2f P0;
P0.x = ( pointList.at(1).x + pointList.at(2).x ) / 2;
P0.y = ( pointList.at(1).y + pointList.at(2).y ) / 2;
//取出OutquadList的4*3個角點 到 cornerPointList
vector<Point> cornerPointList;
for(int i=0;i<OutquadList.size();i++){
vector<Point> points(OutquadList.at(i).size());
points=OutquadList.at(i);
for(int j=0;j<points.size();j++)
cornerPointList.push_back(points[j]);
}
//針對cornerPointList的防抖算法
//antiShake(cornerPointList);
//按一定規則對這12個點重新排序
sortNeartofar(cornerPointList,0,12,pointList.at(0));
sortNeartofar(cornerPointList,4,12,pointList.at(1));
vector<Point> cornerPointList_0;
vector<Point> cornerPointList_1;
vector<Point> cornerPointList_2;
for(int i=0;i<4;i++){
cornerPointList_0.push_back(cornerPointList[i]);
cornerPointList_1.push_back(cornerPointList[i+4]);
cornerPointList_2.push_back(cornerPointList[i+8]);
}
Point P0_0=getFarestPoint(cornerPointList_0,P0);
Point P0_3=getNearestPoint(cornerPointList_0,P0);
Point P0_2=getFarestPoint(cornerPointList_0,pointList.at(1));
Point P0_1=getNearestPoint(cornerPointList_0,pointList.at(1));
Point P1_1=getFarestPoint(cornerPointList_1,P0);
Point P1_2=getNearestPoint(cornerPointList_1,P0);
Point P1_3=getFarestPoint(cornerPointList_1,pointList.at(0));
Point P1_0=getNearestPoint(cornerPointList_1,pointList.at(0));
Point P2_2=getFarestPoint(cornerPointList_2,P0);
Point P2_1=getNearestPoint(cornerPointList_2,P0);
Point P2_3=getFarestPoint(cornerPointList_2,pointList.at(0));
Point P2_0=getNearestPoint(cornerPointList_2,pointList.at(0));
Point P3_0=CrossPoint(P1_0,P1_2,P2_0,P2_1);
Point P3_1=CrossPoint(P1_1,P1_3,P2_0,P2_1);
Point P3_2=CrossPoint(P1_0,P1_2,P2_2,P2_3);
Point P3_3=CrossPoint(P1_1,P1_3,P2_2,P2_3);
circle(srcImgF, P0_0, 4,Scalar(0,255,255),4,1);
circle(srcImgF, P0_1, 4,Scalar(0,255,255),4,1);
circle(srcImgF, P0_2, 4,Scalar(0,255,255),4,1);
circle(srcImgF, P0_3, 4,Scalar(0,255,255),4,1);
circle(srcImgF, P1_0, 4,Scalar(0,255,255),4,1);
circle(srcImgF, P1_1, 4,Scalar(0,255,255),4,1);
circle(srcImgF, P1_2, 4,Scalar(0,255,255),4,1);
circle(srcImgF, P1_3, 4,Scalar(0,255,255),4,1);
circle(srcImgF, P2_0, 4,Scalar(0,255,255),4,1);
circle(srcImgF, P2_1, 4,Scalar(0,255,255),4,1);
circle(srcImgF, P2_2, 4,Scalar(0,255,255),4,1);
circle(srcImgF, P2_3, 4,Scalar(0,255,255),4,1);
circle(srcImgF, P3_0, 2,Scalar(0,255,255),4,1);
circle(srcImgF, P3_1, 2,Scalar(0,255,255),4,1);
circle(srcImgF, P3_2, 2,Scalar(0,255,255),4,1);
circle(srcImgF, P3_3, 2,Scalar(0,255,255),4,1);
//計算4箇中心點
Point2f P0_C,P1_C,P2_C,P3_C;
P0_C.x=float(P0_0.x+P0_1.x+P0_2.x+P0_3.x)/float(4);
P0_C.y=float(P0_0.y+P0_1.y+P0_2.y+P0_3.y)/float(4);
P1_C.x=float(P1_0.x+P1_1.x+P1_2.x+P1_3.x)/float(4);
P1_C.y=float(P1_0.y+P1_1.y+P1_2.y+P1_3.y)/float(4);
P2_C.x=float(P2_0.x+P2_1.x+P2_2.x+P2_3.x)/float(4);
P2_C.y=float(P2_0.y+P2_1.y+P2_2.y+P2_3.y)/float(4);
P3_C.x=float(P3_0.x+P3_1.x+P3_2.x+P3_3.x)/float(4);
P3_C.y=float(P3_0.y+P3_1.y+P3_2.y+P3_3.y)/float(4);
//重新賦值pointLists, size變化:size=3 -> size=4
QList<Point2f> poin2ftList;
poin2ftList.clear();
poin2ftList.append(P0_C);
poin2ftList.append(P1_C);
poin2ftList.append(P2_C);
poin2ftList.append(P3_C);
//重新計算中點
P0.x = ( poin2ftList.at(0).x + poin2ftList.at(1).x\
+ poin2ftList.at(2).x + poin2ftList.at(3).x) / 4;
P0.y = ( poin2ftList.at(0).y + poin2ftList.at(1).y\
+ poin2ftList.at(2).y + poin2ftList.at(3).y) / 4;
//繪製三角形連線
line(srcImgF,poin2ftList.at(0),poin2ftList.at(1),Scalar(0,255,255),1);
line(srcImgF,poin2ftList.at(1),poin2ftList.at(2),Scalar(0,255,255),2);
line(srcImgF,poin2ftList.at(0),poin2ftList.at(2),Scalar(0,255,255),1);
line(srcImgF,poin2ftList.at(0),poin2ftList.at(3),Scalar(0,255,255),2);
line(srcImgF,poin2ftList.at(1),poin2ftList.at(3),Scalar(0,255,255),1);
line(srcImgF,poin2ftList.at(2),poin2ftList.at(3),Scalar(0,255,255),1);
//計算屏幕中心點
Point2f PScreen0;
PScreen0.x = float(cols) / float(2);
PScreen0.y = float(rows) / float(2);
//繪製二維碼正方向箭頭
Point2f P0_C_1_C;
P0_C_1_C.x = ( poin2ftList.at(0).x + poin2ftList.at(1).x ) / float(2);
P0_C_1_C.y = ( poin2ftList.at(0).y + poin2ftList.at(1).y ) / float(2);
Point2f PFront;
PFront.x = ( P0_C_1_C.x + P0_C_1_C.x-P0.x );
PFront.y = ( P0_C_1_C.y + P0_C_1_C.y-P0.y );
drawArrow( srcImgF, P0, PFront, 17, 15, Scalar(0,255,0), 4, 4);
Point2f PX; //X軸正方向
PX.x = P0.x+10;
PX.y = P0.y;
float side01=sqrt( pow(P0_0.x-P1_1.x,2) + pow(P0_0.y-P1_1.y,2) ); //邊01
float side12=sqrt( pow(P1_1.x-P2_2.x,2) + pow(P1_1.y-P2_2.y,2) ); //邊12
float side23=sqrt( pow(P2_2.x-P3_3.x,2) + pow(P2_2.y-P3_3.y,2) ); //邊23
float side30=sqrt( pow(P3_3.x-P0_0.x,2) + pow(P3_3.y-P0_0.y,2) ); //邊30
float QRMeatrue=QRrealSize*4/(side01+side12+side23+side30);
QRX=(P0.x-PScreen0.x)*QRMeatrue; //QR碼在x方向偏差(像素)
QRY=(PScreen0.y-P0.y)*QRMeatrue; //QR碼在y方向偏差(像素)
QRAngle=getAngelOfTwoVector(P0_C_1_C,PX,P0); //QR碼正方向相對於X軸正方向的夾角
float getAngelOfTwoVector(Point2f pt1, Point2f pt2, Point2f c)
{
float theta = atan2(pt1.x - c.x, pt1.y - c.y) - atan2(pt2.x - c.x, pt2.y - c.y);
if (theta > CV_PI)
theta -= 2 * CV_PI;
if (theta < -CV_PI)
theta += 2 * CV_PI;
theta = theta * 180.0 / CV_PI;
return theta;
}
10.對QR碼進行內容識別
思路:
第一步:對QR碼內容區域分別進行網格化處理,定位到每個色塊中心點
第二步:判斷利用之前步驟中二值化後的圖像,計算每個色塊的灰度(灰度值0代表色塊爲黑色,灰度值255代表色塊爲白色),獲取QR碼的二進制內容。
第三步:對QR碼二進制內容進行解碼,得到真正的內容。
這裏我實現了一個簡易的冗餘校驗功能,即對二維碼信息重複四次,分別存儲在QR碼四個不同的區域,以此來保證數據的穩定性。
用到的函數:
1.任意四邊形網格化函數
//網格計算函數 //左上爲P0,P0-P3順時針或逆時針排列 //mode=1,2:返回網格角點,網格中點 //upex等爲向外擴展參數
vector<Point> Thread_CameraBelow::Gridding(Point P0,Point P1,Point P2,Point P3,\
int rows,int cols,int mode,\
int upex,int downex,int leftex,int rightex)
{
vector<Point> pointvector01; //在P0和P1方向上創建等距點
vector<Point> pointvector12; //在P1和P2方向上創建等距點
vector<Point> pointvector32; //在P3和P2方向上創建等距點
vector<Point> pointvector03; //在P0和P3方向上創建等距點
vector<Point> pointvector; //
if(mode==1) //返回格式爲網格角點
{
for(int j=0-leftex; j<cols+1+rightex; j++){
Point point;
point.x = P0.x+(P1.x-P0.x)*j/cols;
point.y = P0.y+(P1.y-P0.y)*j/cols;
pointvector01.push_back(point);
}
for(int j=0-upex; j<rows+1+downex; j++){
Point point;
point.x = P1.x+(P2.x-P1.x)*j/rows;
point.y = P1.y+(P2.y-P1.y)*j/rows;
pointvector12.push_back(point);}
for(int j=0-leftex; j<cols+1+rightex; j++){
Point point;
point.x = P3.x+(P2.x-P3.x)*j/cols;
point.y = P3.y+(P2.y-P3.y)*j/cols;
pointvector32.push_back(point);
}
for(int j=0-upex; j<rows+1+downex; j++){
Point point;
point.x = P0.x+(P3.x-P0.x)*j/rows;
point.y = P0.y+(P3.y-P0.y)*j/rows;
pointvector03.push_back(point);
}
for(int i=0; i<rows+1+downex+upex; i++) //依次求得交叉點
for(int j=0;j<cols+1+rightex+leftex;j++){
Point point=CrossPoint(pointvector01.at(j),pointvector32.at(j),\
pointvector03.at(i),pointvector12.at(i));
pointvector.push_back(point);
}
}
else if(mode==2)
{
for(int j=(0-leftex)*2; j<(cols+rightex)*2+1; j++){
Point point;
point.x = P0.x+(P1.x-P0.x)*j/(cols*2);
point.y = P0.y+(P1.y-P0.y)*j/(cols*2);
pointvector01.push_back(point);
}
for(int j=(0-upex)*2; j<(rows+downex)*2+1; j++){
Point point;
point.x = P1.x+(P2.x-P1.x)*j/(rows*2);
point.y = P1.y+(P2.y-P1.y)*j/(rows*2);
pointvector12.push_back(point);}
for(int j=(0-leftex)*2; j<(cols+rightex)*2+1; j++){
Point point;
point.x = P3.x+(P2.x-P3.x)*j/(cols*2);
point.y = P3.y+(P2.y-P3.y)*j/(cols*2);
pointvector32.push_back(point);
}
for(int j=(0-upex)*2; j<(rows+downex)*2+1; j++){
Point point;
point.x = P0.x+(P3.x-P0.x)*j/(rows*2);
point.y = P0.y+(P3.y-P0.y)*j/(rows*2);
pointvector03.push_back(point);
}
for(int i=0; i<(rows+downex+upex)*2+1; i++) //依次求得交叉點
for(int j=0;j<(cols+rightex+leftex)*2+1;j++){
if( i%2==1 && j%2==1 ){
Point point=CrossPoint(pointvector01.at(j),pointvector32.at(j),\
pointvector03.at(i),pointvector12.at(i));
pointvector.push_back(point);
}
}
}
return pointvector;
}
2.交點計算函數
//計算兩直線交點
Point Thread_CameraBelow::CrossPoint(Point P1, Point P2, Point P3, Point P4)
{
Point pt;
double x1=P1.x,y1=P1.y;
double x2=P2.x,y2=P2.y;
double x3=P3.x,y3=P3.y;
double x4=P4.x,y4=P4.y;
double D = (x1-x2)*(y3-y4)-(y1-y2)*(x3-x4);
if (D == 0){
pt.x=0;
pt.y=0;
}
else{
pt.x = ((x1*y2-y1*x2)*(x3-x4)-(x1-x2)*(x3*y4-y3*x4))/D;
pt.y = ((x1*y2-y1*x2)*(y3-y4)-(y1-y2)*(x3*y4-y3*x4))/D;
}
return pt;
}
11.小結
在家隔離的幾天,閒着無聊寫出了這個QR碼識別算法,還有一些有待改進的地方:如只考慮了圖像翻轉,而沒有考慮圖像畸變的影響等等,有一起學習的小夥伴可以聯繫我@_@1301951021。
最近武漢肺炎正當爆發期,形式是相當嚴峻,希望我們國家可以早日度過難關,雨過定會天晴!
2020.2.7於家中