微信公众号:幼儿园的学霸
个人的学习笔记,关于OpenCV,关于机器学习, …。问题或建议,请公众号留言;
前言
基本原理
OpenCV透视变换函数代码
在做车道线检测中用到了透视变换的一点内容,用于将相机拍摄的图像转换到道路平行的视角下,即鸟瞰图,然后在鸟瞰图中进行车道线检测。
如图1、图2所示分别为相机拍摄的原始图像和逆透视变换后的图像,一般来说逆透视变换后的两条车道线应该是平行的,此处不平行应是透视变换矩阵没有选好,在实际项目中发现没有影响,毕竟图2中过滤掉了大部分杂乱线段。此处对逆透视变换内容进行记录,方便日后查找。
图1 原始图像
图2 鸟瞰图
透视变换(Perspective Transformation)是将成像投影到一个新的视平面(Viewing Plane),也称作投影映射(Projective Mapping)。如图3,通过透视变换ABC变换到A'B'C'。
图3 透视变换示意
透视变换通用公式为:
其中为源图像的像素坐标,为源图像变换后的点所对应的像素坐标。透视变换矩阵可以拆程如下4个部分:
,表示图像的线性变换,比如scaling,shearing和ratotion,产生透视变换,用于平移。
根据透视变换的通用公式,可以得到透视变换的数学表达式为:
从上面的计算公式可以看到,透视变换部分是其分母,而线性变换和平移部分是作为分子存在的。
因此,给定透视变换对应的四对点坐标,可以求得透视变换矩阵;反之,给定透视变换矩阵,也可以对图像或者坐标点完成透视变换。
在我所写车道线检测代码中,透视变换部分用到了以下3个函数
//用于求得透视变换的变换矩阵,
//src::源图像上的四个顶点坐标
//dst::src的坐标在目标图像上的对应坐标
//返回值:3X3的透视变换矩阵
//在车道线检测代码中作用:得到将原始图转换到鸟瞰图的转换矩阵
cv::Mat getPerspectiveTransform(const Point2f* src, const Point2f* dst)
//求得点/点数组在经过变换矩阵m后的对应坐标
//src:目标点,如鸟瞰图中的坐标
//m:src到dst的转换矩阵
//dst:src经过m转换后的对应点
////在车道线检测代码中作用:将鸟瞰图中的车道线坐标转换到原始视图下的像素作弊码
void perspectiveTransform(InputArray src, OutputArray dst, InputArray m )
//对图像进行透视变换
//src:输入图像
//dst:输出图像
//M:变换矩阵,如getPerspectiveTransform函数得到的矩阵
//dsize:目标图像的大小
//flags:目标图像的插值方法
//borderMode:外推方法
//borderValue:常量边界时使用
//在车道线检测代码中作用:
// 1.将原始图像转换到鸟瞰图中,进行车道线检测;
// 2.将鸟瞰图转换到原始视图下,以进行结果展示等
void warpPerspective(InputArray src, OutputArray dst, InputArray M, Size dsize, int flags=INTER_LINEAR, int borderMode=BORDER_CONSTANT, const Scalar& borderValue=Scalar())
代码示例如下。该代码流程为:
1.通过4对点计算透视变换矩阵T;
2.利用T对图像进行逆透视变换得到鸟瞰图;
3.将鸟瞰图下的车道线坐标转换到正常视图下;
该流程和我项目中的流程一致,但展示的代码比较简单,仅做示例。
如图4所示为鸟瞰图下的车道线,图5位转换到原始视图视角下的车道线。
图4 鸟瞰图及其车道线
图5 原始视图及其车道线
具体代码:
//====================================================================//
// Created by liheng on 19-2-12.
//Program:将逆透视变换后的坐标点转换到原图中
//Data:2019.2.12
//Author:liheng
//Version:V1.0
//====================================================================//
#include
#include
#include
int main()
{
//首先读入图像
cv::Mat srcImage = cv::imread("../pictures/000177.png",cv::IMREAD_GRAYSCALE);
//定义源点和目标点,源点为正常读入的图像的点,目标点为转换后的鸟瞰图上的对应点
cv::Point2f srcPoints[4],dstPoints[4];
srcPoints[0] = cv::Point2f(369,375);
srcPoints[1] = cv::Point2f(545,221);
srcPoints[2] = cv::Point2f(650,221);
srcPoints[3] = cv::Point2f(793,375);
dstPoints[0] = cv::Point2f(339,375);
dstPoints[1] = cv::Point2f(339,211);
dstPoints[2] = cv::Point2f(823,211);
dstPoints[3] = cv::Point2f(823,375);
//1°求解变换矩阵
cv::Mat m_persctiveMat = cv::getPerspectiveTransform(srcPoints,dstPoints);//读入图像转换为鸟瞰图的矩阵
cv::Mat m_unPersctiveMat =cv::getPerspectiveTransform(dstPoints,srcPoints);//鸟瞰图到原始图像的转换矩阵
//2°求解鸟瞰图
cv::Mat birdViewImage;
cv::warpPerspective(srcImage,birdViewImage,m_persctiveMat,cv::Size(srcImage.cols,srcImage.rows),cv::INTER_LINEAR);
//鸟瞰图车道线上的两点.Note:此处为了简单,仅选择2点进行变换
std::vector leftLine,rightLine;
leftLine.push_back(cv::Point2f(661,0));
leftLine.push_back(cv::Point2f(366,376));
rightLine.push_back(cv::Point2f(1097,0));
rightLine.push_back(cv::Point2f(883,376));
//3°求解其在原始图像上对应的坐标
std::vector unWarpedLeftLine,unWarpedRightLine;
cv::perspectiveTransform(leftLine,unWarpedLeftLine,m_unPersctiveMat);
cv::perspectiveTransform(rightLine,unWarpedRightLine,m_unPersctiveMat);
//线段可视化
cv::cvtColor(srcImage,srcImage,CV_GRAY2BGR);
cv::line(srcImage,unWarpedLeftLine[0],unWarpedLeftLine[1],cv::Scalar(0,255,0),2);
cv::line(srcImage,unWarpedRightLine[0],unWarpedRightLine[1],cv::Scalar(0,255,0),2);
cv::cvtColor(birdViewImage,birdViewImage,CV_GRAY2BGR);
cv::line(birdViewImage,leftLine[0],leftLine[1],cv::Scalar(0,255,0),2);
cv::line(birdViewImage,rightLine[0],rightLine[1],cv::Scalar(0,255,0),2);
cv::imshow("srcImage",srcImage);
cv::imshow("birdViewImage",birdViewImage);
cv::waitKey(0);
return 0;
}
下面的是我的公众号二维码图片,欢迎关注。
图注:幼儿园的学霸