0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

OpenCV筑基之图像的透视变换

OpenCV学堂 来源:OpenCV学堂 2024-03-15 09:51 次阅读

1. 图像的透视变换

1.1 简介

图像的透视变换(Perspective Transformation)是指将图像投影到一个新的视平面(Viewing Plane),也称作投影映射(Projective Mapping)。

透视变换是一种非线性变换,它可以将一个二维坐标系中的点映射到三维坐标系中的点,然后再将其投影到另一个二维坐标系中的点。透视变换可以改变图像中的形状,并可以模拟真实世界中的透视效果。

仿射变换可以看成是透视变换的特殊情况,下图是对几何变换的总结。

6e3c19b2-e216-11ee-a297-92fbcf53809c.jpg

几何变换的总结.png

透视变换的应用:

图像矫正透视变换可以用于矫正图像的透视失真,例如由于拍摄角度或镜头畸变导致的图像倾斜或拉伸。

图像配准透视变换可以用于将两张或多张图像进行配准,使其具有相同的几何形状。这在医学图像处理、卫星图像处理等领域有着重要的应用。

3D 建模透视变换可以用于将二维图像投影到三维空间,从而生成三维模型。

图像增强透视变换可以用于调整图像的视角,使其看起来更具吸引力。

图像合成透视变换可以用于将不同的图像合成在一起,创建新的图像。

特效透视变换可以用于创建各种特效,例如虚拟场景、3D 动画等。

1.2 原理

透视变换的定义为将图像中的所有点按照一定的透视关系映射到新的图像中。

6e529552-e216-11ee-a297-92fbcf53809c.jpg

透视变换.png

透视关系可以由一个3x3的透视变换矩阵来表示,透视变换的矩阵如下:

其中,、、、 表示线性变换,、 表示平移变换,、 表示透视变换。

透视变换的过程为:

此时,得到的不是最后的坐标,还需要进一步转换:

最终的坐标为:

重新回顾一下整个透视变换的过程:

不难看出看出仿射变换是透视变换的一种特殊情况。

2. 透视变换的应用

2.1 商品图位置矫正

下面的代码,对图中的没有摆正的商品通过透视变换将其对齐,然后在原图中将商品放正。主要用到了 OpenCV 的 findHomography()、warpPerspective()函数进行透视变换。findHomography()函数用于计算两个平面之间进行透视变换的矩阵,warpPerspective() 函数用于对图像进行透视变换。

#include
#include
#include
#include

usingnamespacestd;
usingnamespacecv;

boolascendSort(vectora,vectorb)
{
returncontourArea(a)>contourArea(b);
}

longpointSideLine(Point&lineP1,Point&lineP2,Point&point){
longx1=lineP1.x;
longy1=lineP1.y;
longx2=lineP2.x;
longy2=lineP2.y;
longx=point.x;
longy=point.y;
return(x-x1)*(y2-y1)-(y-y1)*(x2-x1);
}

vectorsortPointByClockwise(vectorpoints){
if(points.size()!=4){
returnpoints;
}
PointunFoundPoint;
vectorresult={unFoundPoint,unFoundPoint,unFoundPoint,unFoundPoint};
longminDistance=-1;
for(autopoint:points){
longdistance=point.x*point.x+point.y*point.y;
if(minDistance==-1||distance< minDistance) {
            result[0] = point;
            minDistance = distance;
        }
    }

    if (result[0] != unFoundPoint) {
        Point &leftTop = result[0];
        points.erase(std::remove(points.begin(), points.end(), leftTop));
        if ((pointSideLine(leftTop, points[0], points[1]) * pointSideLine(leftTop, points[0], points[2])) < 0) {
            result[2] = points[0];
        } else if ((pointSideLine(leftTop, points[1], points[0]) * pointSideLine(leftTop, points[1], points[2])) < 0) {
            result[2] = points[1];
        } else if ((pointSideLine(leftTop, points[2], points[0]) * pointSideLine(leftTop, points[2], points[1])) < 0) { result[2] = points[2]; } } if (result[0] != unFoundPoint && result[2] != unFoundPoint) { Point &leftTop = result[0]; Point &rightBottom = result[2]; points.erase(std::remove(points.begin(), points.end(), rightBottom)); if (pointSideLine(leftTop, rightBottom, points[0]) >0){
result[1]=points[0];
result[3]=points[1];
}else{
result[1]=points[1];
result[3]=points[0];
}
}

if(result[0]!=unFoundPoint&&result[1]!=unFoundPoint&&result[2]!=unFoundPoint&&result[3]!=unFoundPoint){
returnresult;
}

returnpoints;
}

intmain(intargc,char*argv[])
{
Matsrc=imread(".../product.jpg");
imshow("src",src);

Matgray,binary;
cvtColor(src,gray,COLOR_BGR2GRAY);
threshold(gray,binary,0,255,THRESH_BINARY|THRESH_OTSU);
imshow("binary",binary);

vector>contours;
vectorhierarchy;
findContours(binary,contours,hierarchy,RETR_TREE,CHAIN_APPROX_SIMPLE);

sort(contours.begin(),contours.end(),ascendSort);//ascendingsort

RotatedRectrrt=minAreaRect(contours[0]);
Rectbbox=rrt.boundingRect();

if(bbox.height>2000){
rrt=minAreaRect(contours[1]);
bbox=rrt.boundingRect();
}

Matroi;
try{
roi=src(bbox);
}
catch(...){
}
imshow("roi",roi);

intwidth=static_cast(rrt.size.width);
intheight=static_cast(rrt.size.height);
floatangle=rrt.angle;

printf("height%d,width:%d,angle:%f
",height,width,angle);

Point2fvertices[4];
rrt.points(vertices);
vectorsrc_pts;

for(inti=0;i< 4; i++) {
        printf("x=%.2f, y=%.2f
", vertices[i].x, vertices[i].y);
        src_pts.push_back(vertices[i]);
    }

    src_pts = sortPointByClockwise(src_pts); // 将顶点按照顺时针方向进行排序

    vectordst_pts;
dst_pts.push_back(Point(0,0));
dst_pts.push_back(Point(width,0));
dst_pts.push_back(Point(width,height));
dst_pts.push_back(Point(0,height));

MatM=findHomography(src_pts,dst_pts);
Matresult=Mat::zeros(Size(width,height),CV_8UC3);
warpPerspective(src,result,M,result.size());

imshow("result",result);

resize(result,result,roi.size());

result.copyTo(roi);

imshow("final",src);
waitKey(0);
return0;
}
简单介绍一下 warpPerspective() 函数:

voidwarpPerspective(InputArraysrc,OutputArraydst,

InputArrayM,Sizedsize,
intflags=INTER_LINEAR,
intborderMode=BORDER_CONSTANT,
constScalar&borderValue=Scalar());

第一个参数 src: 输入图像。

第二个参数 dst: 输出图像,与 src 具有相同的类型和大小。

第三个参数 M: 3x3 的透视变换矩阵。

第四个参数 dsize: 输出图像的大小。

上述代码,还需要注意调用 findHomography() 函数时,输入点的集合和输出点的集合顺序要一致。

2.2 广告牌内容替换

透视变换还有一个比较经典的例子,就是替换一张图像中广告牌的内容,下面的代码展示了这个例子:

#include
#include
#include
#include

usingnamespacestd;
usingnamespacecv;

boolascendSort(vectora,vectorb)
{
returncontourArea(a)>contourArea(b);
}

longpointSideLine(Point&lineP1,Point&lineP2,Point&point){
longx1=lineP1.x;
longy1=lineP1.y;
longx2=lineP2.x;
longy2=lineP2.y;
longx=point.x;
longy=point.y;
return(x-x1)*(y2-y1)-(y-y1)*(x2-x1);
}

vectorsortPointByClockwise(vectorpoints){
if(points.size()!=4){
returnpoints;
}
PointunFoundPoint;
vectorresult={unFoundPoint,unFoundPoint,unFoundPoint,unFoundPoint};
longminDistance=-1;
for(autopoint:points){
longdistance=point.x*point.x+point.y*point.y;
if(minDistance==-1||distance< minDistance) {
            result[0] = point;
            minDistance = distance;
        }
    }

    if (result[0] != unFoundPoint) {
        Point &leftTop = result[0];
        points.erase(std::remove(points.begin(), points.end(), leftTop));
        if ((pointSideLine(leftTop, points[0], points[1]) * pointSideLine(leftTop, points[0], points[2])) < 0) {
            result[2] = points[0];
        } else if ((pointSideLine(leftTop, points[1], points[0]) * pointSideLine(leftTop, points[1], points[2])) < 0) {
            result[2] = points[1];
        } else if ((pointSideLine(leftTop, points[2], points[0]) * pointSideLine(leftTop, points[2], points[1])) < 0) { result[2] = points[2]; } } if (result[0] != unFoundPoint && result[2] != unFoundPoint) { Point &leftTop = result[0]; Point &rightBottom = result[2]; points.erase(std::remove(points.begin(), points.end(), rightBottom)); if (pointSideLine(leftTop, rightBottom, points[0]) >0){
result[1]=points[0];
result[3]=points[1];
}else{
result[1]=points[1];
result[3]=points[0];
}
}

if(result[0]!=unFoundPoint&&result[1]!=unFoundPoint&&result[2]!=unFoundPoint&&result[3]!=unFoundPoint){
returnresult;
}

returnpoints;
}

intmain(){
Matbillboard=imread(".../billboard.jpg");
imshow("billboard",billboard);

Mathsv;
cvtColor(billboard,hsv,cv::COLOR_BGR2HSV);//BGR转换到HSV色彩空间
imshow("hsv",hsv);

cv::Scalarlower_white(0,0,0);
cv::Scalarupper_white(180,30,255);

Matmask;
inRange(hsv,lower_white,upper_white,mask);//通过inRange函数实现二值化
imshow("mask",mask);

MatstructureElement=getStructuringElement(MORPH_RECT,Size(105,105),Point(-1,-1));
morphologyEx(mask,mask,MORPH_OPEN,structureElement,Point(-1,-1),1);

imshow("mask2",mask);

vector>contours;
vectorhierarchy;
findContours(mask,contours,hierarchy,RETR_EXTERNAL,CHAIN_APPROX_SIMPLE);
sort(contours.begin(),contours.end(),ascendSort);//ascendingsort

RotatedRectrrt=minAreaRect(contours[0]);//获取最大轮廓的最小外接矩形
Rectbbox=rrt.boundingRect();
intwidth=static_cast(rrt.size.width);
intheight=static_cast(rrt.size.height);

printf("width%d,height:%d
",width,height);

Point2fpt[4];
rrt.points(pt);

Matroi;
try{
roi=billboard(bbox);
}
catch(...){
}
imshow("roi",roi);

Matgirl=imread(".../girl.jpg");
imshow("girl",girl);

intwidth_girl=girl.cols;
intheight_girl=girl.rows;

vectorsrc_pts;
src_pts.push_back(Point(0,0));
src_pts.push_back(Point(width_girl,0));
src_pts.push_back(Point(width_girl,height_girl));
src_pts.push_back(Point(0,height_girl));

vectordst_pts;
for(inti=0;i< 4; i++) {
        printf("x=%.2f, y=%.2f
", pt[i].x, pt[i].y);
        dst_pts.push_back(pt[i]);
    }

    dst_pts = sortPointByClockwise(dst_pts); // 将顶点按照顺时针方向进行排序

    Mat M = findHomography(src_pts,dst_pts);
    Mat result;
    warpPerspective(girl, result, M, billboard.size());
    imshow("result", result);

    result.copyTo(billboard,mask);
    imshow("final", billboard);

    waitKey(0);
    return 0;
}
3. 总结

透视变换是一种重要的图像处理技术,它具有广泛的应用价值。它可以改变图像的视角,从而使图像更加符合人眼的视觉感受,或满足特定的应用需求。它可以用于图像矫正、图像配准、3D 建模、增强现实等领域。

透视变换是一种非线性变换,因此它可能会导致图像变形。例如,如果透视变换矩阵不合适,可能会使图像中的物体看起来拉伸或压缩。此外,透视变换也可能会导致图像中的物体出现重叠或遮挡。在使用透视变换时,需要考虑这些局限性,并选择合适的参数来获得最佳效果。

审核编辑:黄飞

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • OpenCV
    +关注

    关注

    29

    文章

    611

    浏览量

    40786
  • 透视变换
    +关注

    关注

    0

    文章

    3

    浏览量

    1330

原文标题:OpenCV筑基之图像的透视变换

文章出处:【微信号:CVSCHOOL,微信公众号:OpenCV学堂】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    【Rayeager PX2分享】OpenCV入门线段检测

    )。最基本的霍夫变换是从黑白图像中检测直线(线段)。在OpenCV编程中,实现线段检测主要使用cvHoughLines2函数。函数原型:CvSeq* cvHoughLines2(CvArr* image
    发表于 05-26 11:22

    【DragonBoard 410c试用体验】 OpenCV图像角点检测实现

    ( WINDOW_NAME2, scaledImage );}这里代码稍微有点多,不过我们主要看cornerHarris 函数,它这才是用于在OpenCV中运行Harris角点检测算子处理图像函数
    发表于 09-13 19:46

    OpenCV图像

    之前学过一段时间OpenCV,跟着网上的资料,自己瞎搞了个上位机,可以对摄像头采集的图像简单处理,在此献丑了,hhhhhh
    发表于 07-11 19:07

    图像频率域分析傅里叶变换

    文章目录傅里叶变换基础傅里叶级数傅里叶积分傅里叶变换一维连续傅里叶变换一维离散傅里叶变换二维离散傅里叶变换
    发表于 05-22 07:41

    关于OpenCV图像如何处理

    OpenCV-图像处理(25、直方图比较)
    发表于 04-27 15:14

    【EASY EAI Nano开源套件试用体验】3. OpenCV 图像处理开发测试

    :素描效果:边缘扫描效果:浮雕效果:立体效果:图像白平衡效果: 六、OpenCV 红外伪彩变换 有了上面开发测试的基础,可以进一步,利用openc
    发表于 03-08 00:03

    基于透视变换的车辆位置和方向提取方法

    利用快速标定模型进行了车载成像装置的标定,研究了基于导引标志的车辆位置和方向参数提取,其中改进了一种基于透视变换提取车辆位置方向的方法,即利用图像中车道平行线和灭影点
    发表于 02-08 15:46 31次下载
    基于<b class='flag-5'>透视</b><b class='flag-5'>变换</b>的车辆位置和方向提取方法

    使用opencv进行图像处理

    使用opencv进行图像处理_于仕琪,感兴趣的可以看看。
    发表于 05-03 14:45 0次下载

    基于图像透视畸变校正的调炮速度测量系统

    基于图像透视畸变校正的调炮速度测量系统基于图像透视畸变校正的调炮速度测量系统
    发表于 05-20 15:37 0次下载

    opencv透视变换原理及实例

    透视变换(Perspective Transformation)是将图片投影到一个新的视平面(Viewing Plane),也称作投影映射(Projective Mapping)。
    发表于 12-04 15:15 1.9w次阅读
    <b class='flag-5'>opencv</b><b class='flag-5'>透视</b><b class='flag-5'>变换</b>原理及实例

    使用Numpy和OpenCV实现傅里叶和逆傅里叶变换

      文章从实际出发,讲述了什么是傅里叶变换,它的理论基础以及Numpy和OpenCV实现傅里叶和逆傅里叶变换,并最终用高通滤波和低通滤波的示例。
    的头像 发表于 07-05 16:04 1255次阅读

    浅析OpenCV中的透视变换

    透视变换是将图像从一个视平面投影到另外一个视平面的过程,所以透视变换也被称为投影映射(Projection Mapping)。
    的头像 发表于 05-18 16:18 1262次阅读
    浅析<b class='flag-5'>OpenCV</b>中的<b class='flag-5'>透视</b><b class='flag-5'>变换</b>

    OpenCV库在图像处理和深度学习中的应用

    本文深入浅出地探讨了OpenCV库在图像处理和深度学习中的应用。从基本概念和操作,到复杂的图像变换和深度学习模型的使用,文章以详尽的代码和解释,带领大家步入
    的头像 发表于 08-18 11:33 499次阅读

    浅谈透视变换的硬件实现策略

    需要着重说明地是,(u,v)是透视变换图像的坐标,(x,y)是源图像坐标。我们进行实际的透视变换
    的头像 发表于 03-05 10:19 150次阅读
    浅谈<b class='flag-5'>透视</b><b class='flag-5'>变换</b>的硬件实现策略

    OpenCV筑基之图像的仿射变换方法总结

    图像的几何变换是指将一幅图像中的坐标位置映射到另一幅图像中的新坐标位置,其实质是改变像素的空间位置,估算新空间位置上的像素值。
    的头像 发表于 03-19 11:11 204次阅读
    <b class='flag-5'>OpenCV</b>筑基之<b class='flag-5'>图像</b>的仿射<b class='flag-5'>变换</b>方法总结