你有没有注意到,当你拍摄一个矩形平面物体的图像时,角落角度很少是90°?
这种现象是透视投影的一个特征,其中 3D 场景中的点(例如,矩形角)通过针孔投影到平面(相机图像传感器)上。靠近照相机的线段比距离照相机较远的相同长度的线段显示得更长。直角可能变得尖锐或钝,平行线可能看起来会聚到消失点。
在平面物体的自动检测中,透视畸变可能是一个问题。由于我们要检查对象特征是否具有正确的形状和大小,因此我们希望"反转"透视失真。如果我们能做到这一点,我们会得到一个图像,使物体看起来好像相机轴垂直于物体的表面。
上图显示了反转透视失真的效果(此处为代码)。在本例中,书的角被映射到正方形的任意点,并用红色圆圈标记。右侧的图像更适合自动检测,因为可以轻松定位特征并与模板进行比较。
OpenCV提供了两个可以做到这一点的函数:warpAffine()和warpPerspective()。它们具有相同的签名,除了 warpAffine() 需要一个 2x3 转换矩阵,而 warpPerspective() 需要一个 3x3 转换矩阵。这些矩阵分别由函数 getAffineTransform() 和 getPerspectiveTransform() 计算。前者需要三对匹配的坐标,后者需要四对匹配的坐标。直观地说,得到三对比四对更容易,所以问题出现了:在什么情况下我们可以逃脱三对坐标并使用仿射变换而不是透视变换?
为了回答这个问题,让我们考虑图 3 中的两个图像。
在右侧图像中,使用距离物体约7m的摄像头,线平行度比左侧图像中的清晰度保留得更好。如果我们计算仿射和透视变换,然后相应地扭曲这些图像,我们将获下图所示的图像。
我们应该使用的标准是保持线路平行度。仿射变换可保持线并行性。如果要检查的对象在3D世界中具有平行线,并且图像中的相应线是平行的,则仿射变换就足够了。相反,如果3D世界中的平行线在图像中发散,则仿射变换是不够的:我们需要透视变换。
void cv::warpAffine ( InputArray src,
OutputArray dst,
InputArray M,
Size dsize,
int flags = , INTER_LINEAR
int borderMode = , BORDER_CONSTANT
const Scalar & borderValue = Scalar()
)
Python:
cv.warpAffine( src, M, dsize[, dst[, flags[, borderMode[, borderValue]]]] ) -> dst
参数InputArray src:输入变换前的图像;
参数OutputArray dst:输出变换后图像,需要初始化一个空矩阵用来保存结果,不用设定矩阵尺寸;
参数InputArray M:变换矩阵;
参数Size dsize:设置输出图像大小;
参数int flags=INTER_LINEAR:设置插值方式,默认方式为线性插值;
M参数的获得Opencv中也有对应的函数
Mat getAffineTransform(const Point2f* src, const Point2f* dst)
参数const Point2f* src:原图像的变换点像素坐标;
参数const Point2f* dst:输出图像的变换点像素坐标;
string path = "D:\\QT\\Opencv\\Resources\\cards.jpg"; //"D:\QT\Opencv\Resources\cards.png"
Mat img = cv::imread(path);
Point2f src[4]= {{529,142},{771,190},{405,395},{674,457}};
Point2f dst[4]= {{0.0f,0.0f},{w,0.0f},{0.0f,h},{w,h}};
for(int i = 0; i < 4; i++)
{
circle(img,src[i], 10, Scalar(0, 69, 255), FILLED);
}
matrix1 = getAffineTransform(src, dst);
warpAffine(img, imgWarpAffine, matrix1, Point(w,h));
imshow("Image",img);
imshow("Image WarpAffine",imgWarpAffine);
waitKey(0);
void cv::warpPerspective ( InputArray src,
OutputArray dst,
InputArray M,
Size dsize,
int flags = , INTER_LINEAR
int borderMode = , BORDER_CONSTANT
const Scalar & borderValue = Scalar()
)
Python:
cv.warpPerspective( src, M, dsize[, dst[, flags[, borderMode[, borderValue]]]] ) -> dst
参数InputArray src:输入变换前的图像;
参数OutputArray dst:输出变换后图像,需要初始化一个空矩阵用来保存结果,不用设定矩阵尺寸;
参数InputArray M:变换矩阵;
参数Size dsize:设置输出图像大小;
参数int flags=INTER_LINEAR:设置插值方式,默认方式为线性插值;
M参数的获得Opencv中也有对应的函数
Mat cv::getPerspectiveTransform ( const Point2f src[],
const Point2f dst[]
)
参数const Point2f* src:原图像的变换点像素坐标;
参数const Point2f* dst:输出图像的变换点像素坐标;
string path = "D:\\QT\\Opencv\\Resources\\cards.jpg"; //"D:\QT\Opencv\Resources\cards.png"
Mat img = cv::imread(path);
Point2f src[4]= {{529,142},{771,190},{405,395},{674,457}};
Point2f dst[4]= {{0.0f,0.0f},{w,0.0f},{0.0f,h},{w,h}};
for(int i = 0; i < 4; i++)
{
circle(img,src[i], 10, Scalar(0, 69, 255), FILLED);
}
matrix = getPerspectiveTransform(src,dst);
warpPerspective(img, imgWarp, matrix, Point(w,h));
imshow("Image",img);
imshow("Image Warp",imgWarp);
waitKey(0);