单应性变换是将一个平面内的点映射到另一个平面内的二维投影变换。
应用
单应性在计算机视觉领域是一个非常重要的概念,它在图像校正、视角变换、图像拼接、增强现实、相机位姿估计、视觉SLAM等领域有非常重要的作用。
SuperPoint: Self-Supervised Interest Point Detection and Description里也会用到单应性(Homographic Adaptation)。
via.SuperPoint_Self-Supervised_Interest_Point_Detection_and_Description_CVPRW_2018
具体变换
如下,有一个变换矩阵H--单应性矩阵。假设两张图像中的对应点对齐次坐标为(x',y',1)和(x,y,1),而中间的映射关系就是H。具体如下。
1、打印一张棋盘格标定图纸,将其贴在平面物体的表面。
2、拍摄一组不同方向棋盘格的图片,可以通过移动相机来实现,也可以移动标定图片来实现。
3、对于每张拍摄的棋盘图片,检测图片中所有棋盘格的特征点(角点,也就是下图中黑白棋盘交叉点,中间品红色的圆圈内就是一个角点)。我们定义打印的棋盘图纸位于世界坐标系Zw=0的平面上,世界坐标系的原点位于棋盘图纸的固定一角(比如下图中黄色点)。像素坐标系原点位于图片左上角。
4、因为棋盘标定图纸中所有角点的空间坐标是已知的,这些角点对应在拍摄的标定图片中的角点的像素坐标也是已知的,如果我们得到这样的N>=4个匹配点对(越多计算结果越鲁棒),就可以根据LM等优化方法,find其单应矩阵H。
当然计算单应矩阵一般不需要自己写函数实现,OpenCV中就有现成的函数Mat findHomography可以调用,对应的c++函数是:
Mat findHomography //返回类型是MAT
(
InputArray srcPoints, //二者对应points
InputArray dstPoints, //二者对应points
int method=0,
double ransacReprojThreshold=3,
OutputArray mask=noArray()
)
从函数定义来看,只要输入匹配点对,指定具体计算方法即可输出结果。
via.opencv official doc
图片校正案例
//---------------------------------------------
//环境:win10、opencv
//---------------------------------------------
#include <opencv2/opencv.hpp>
#include<opencv2/imgproc/imgproc_c.h>
using namespace cv;
using namespace std;
//设置差分值
int dx = 5;
int dy = dx;
//定义一个数据结构:图片+点集
struct userdata {
Mat im;
vector<Point2f> points;
};
void mouseHandler(int event, int x, int y, int flags, void* data_ptr)
{
userdata* data = ((userdata*)data_ptr);
//按下鼠标触发,4个点
if (event == EVENT_LBUTTONDOWN && data->points.size() <= 4)
{
// circle( src2_copy, tracked_points_1[i], 3, Scalar(0,0,255), -1, 8);
// 实心红点
circle(data->im, Point(x, y), 3, Scalar(0, 0, 255), 5, CV_AA);
CvPoint pt;
char text[16];
string msg;
msg = format("(%d, %d)", x, y);
// baseLine = 0;
// textSize = getTextSize(msg, 1, 0.5, 1, &baseLine);
// Point textOrigin(20, imgRGB2.rows - 20);
// putText(src2_copy, msg, textOrigin, 1, 1, Scalar(0,255,0));
// sprintf(text, "(%d, %d)", 1);
pt = cvPoint(x - 3 * dx, y - dy);
putText(data->im, msg, pt, CV_FONT_HERSHEY_SIMPLEX, 0.45, cvScalar(0, 255, 255, 0));
imshow("Image", data->im);
if (data->points.size() < 4)
{
data->points.push_back(Point2f(x, y));
}
}
}
int main()
{
// Read source image.
Mat im_src = imread("002.JPG");
//dst,物体的纵横比maybe是2:3
Size size(540, 240);
Mat im_dst = Mat::zeros(size, CV_8UC3);
// 创建方向点向量,并添加4点坐标
vector<Point2f> pts_dst;
pts_dst.push_back(Point2f(0, 0));
pts_dst.push_back(Point2f(size.width - 1, 0));
pts_dst.push_back(Point2f(size.width - 1, size.height - 1));
pts_dst.push_back(Point2f(0, size.height - 1));
//-------------------------------------------
// 设置鼠标事件数据,手动添加坐标
Mat im_temp = im_src.clone();
userdata data;
data.im = im_temp;
cout << "点击书的四个角——首先是左上角,然后" << endl
<< "左下角最后 -- 然后按 ENTER" << endl;
// 显示图像并等待 4 次点击.
imshow("Image", im_temp);
// 鼠标事件 设置回调函数
setMouseCallback("Image", mouseHandler, &data);
waitKey(0);
// 计算单应性
Mat h = findHomography(data.points, pts_dst);
// 将源图像扭曲到目标
warpPerspective(im_src, im_dst, h, size);
// 输出
imshow("Image", im_dst);
waitKey(0);
return 0;
}
STEP1、运行
STEP2、从预计的左上角依次顺时针标定
STEP3、最终生成校正结果