透视变换通过变换矩阵(3*3)将任意视角的照片转换为垂直视角,要得到变换矩阵需要提供两个坐标系的对应点
照片中取点的方法分为手动和自动,自动取点又可以分为检测强角点、检测直线角点、检测圆心等多种方式,随具体情况而定。
本项目要检测不规则工件轮廓,因此将工件置于标准矩形白板上,拍照整个白板,希望检测出白板的四个顶点
直接根据图形特征检测(角点、直线、圆)都会受到环境的干扰,尤其是背景的干扰,可以辅助以颜色分离(HSV空间)再进行图形识别
本程序检测四边直线,再进行延长求解直线交点
注意:检测出的交点是无序的,需要相对于中心的顺时针(or逆)排列
#include "stdafx.h"
#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
using namespace cv;
Point2f center(0,0);
//计算直线交点
Point2f computeIntersect(cv::Vec4i a,cv::Vec4i b)
{
int x1 = a[0],y1 = a[1],x2 = a[2],y2 = a[3],x3 = b[0],y3 = b[1],x4 = b[2],y4 = b[3];
if (float d = ((float)(x1 - x2)*(y3 - y4)-(y1 - y2)*(x3 - x4)))
{
cv::Point2f pt;
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;
}
else
return cv::Point2f(-1,-1);
}
//将corners绕center顺时针排列
void sortCorners(std::vector<cv::Point2f>& corners,cv::Point2f center)
{
std::vector<cv::Point2f> top,bot;
for (unsigned int i =0;i< corners.size();i++)
{
if (corners[i].y<center.y)
{
top.push_back(corners[i]);
}
else
{
bot.push_back(corners[i]);
}
}
cv::Point2f tl = top[0].x > top[1].x ? top[1] : top[0];
cv::Point2f tr = top[0].x > top[1].x ? top[0] : top[1];
cv::Point2f bl = bot[0].x > bot[1].x ? bot[1] : bot[0];
cv::Point2f br = bot[0].x > bot[1].x ? bot[0] : bot[1];
corners.clear();
//注意以下存放顺序是顺时针,当时这里出错了,如果想任意顺序下文开辟的四边形矩阵注意对应
corners.push_back(tl);
corners.push_back(tr);
corners.push_back(br);
corners.push_back(bl);
}
//透视变换
int main(int argc,char **argv)
{
Mat src = imread("poker.jpg",1);
if(src.empty())
return -1;
imshow("Source",src);
Mat gray;
cvtColor(src,gray,CV_BGR2GRAY);
//imshow("Gray",gray);
blur(gray,gray,Size(3,3));
Canny(gray,gray,100,100,3);
//imshow("Canny",gray);
std::vector<Vec4i> lines;
HoughLinesP(gray,lines,1,CV_PI/180,70,30,10);
//线段延长至整幅图像
for (unsigned int i = 0;i<lines.size();i++)
{
cv::Vec4i v = lines[i];
lines[i][0] = 0;
lines[i][1] = ((float)v[1] - v[3])/(v[0] - v[2])* -v[0] + v[1];
lines[i][2] = src.cols;
lines[i][3] = ((float)v[1] - v[3])/(v[0] - v[2])*(src.cols - v[2]) + v[3];
}
//存储线的交点
std::vector<Point2f> corners;
for (unsigned int i = 0;i<lines.size();i++)
{
for (unsigned int j=i+1;j<lines.size();j++)
{
Point2f pt = computeIntersect(lines[i],lines[j]);
if (pt.x >= 0 && pt.y >=0) //交点在图像内
corners.push_back(pt);
}
}
std::vector<cv::Point2f> approx;
approxPolyDP(cv::Mat(corners),approx,arcLength(Mat(corners),true)*0.02,true);
if (approx.size()!=4)
{
std::cout<<"The object is not quadrilateral!"<<std::endl;
return -1;
}
//get mass center
for (unsigned int i = 0;i < corners.size();i++)
{
center += corners[i];
}
center *=(1./corners.size());
sortCorners(corners,center); //corners绕center顺时针排列
//draw lines
for(int i=0;i<lines.size();++i)
line(src,Point(lines[i][0],lines[i][1]),Point(lines[i][2],lines[i][3]),Scalar(0,0,0),2,8,0);
imshow("Source",src);
//draw corner points
circle(src,corners[0],3,CV_RGB(255,0,0),2); //red
circle(src,corners[1],3,CV_RGB(0,255,0),2); //green
circle(src,corners[2],3,CV_RGB(0,0,255),2); //blue
circle(src,corners[3],3,CV_RGB(255,255,255),2); //white
circle(src,center,3,CV_RGB(255,255,0),2); //yellow
imshow("Source",src);
Mat quad = Mat::zeros(320,240,CV_8UC3);//校正后图片
//corners of the destination image
std::vector<Point2f> quad_pts;
quad_pts.push_back(Point2f(0,0));
quad_pts.push_back(Point2f(quad.cols,0));//(220,0)
quad_pts.push_back(Point2f(quad.cols,quad.rows));//(220,300)
quad_pts.push_back(Point2f(0,quad.rows));
// Get transformation matrix
Mat transmtx = getPerspectiveTransform(corners,quad_pts); //求源坐标系(已畸变的)与目标坐标系(转换后的)的转换矩阵
// Apply perspective transformation透视转换
warpPerspective(src,quad,transmtx,quad.size());
imshow("quadrilateral",quad);
waitKey(0);
}