原文:https://courses.learnopencv.com/courses/227056/lectures/3797166
代码:https://courses.learnopencv.com/courses/227056/lectures/3804006
OpenCV图片变换
仿射变换
一张图片能够变换成不同的形状。如下所示就是几种不同的图片变形。
欧几里得变换(Euclidean Transform) 是指只包含移动(translation)和旋转(rotation)的变换。欧几里得变换保证了正交性(Orthogonality),原本垂直的线,在转换后也保持垂直。
仿射变换(Affine Transfrom)是指包含移动、旋转、放缩(x,y方向),以及一个叫做shear(剪切)的参数
上面的变换都是线性变换(Linear Transform),即所有的直线在变换后依旧是直线。
1. OpenCV中的仿射变换
在OpenCV中,仿射变换被存储在一个2x3大小的矩阵当中。前两列编码了旋转、缩放、剪切信息,最后一列编码了位移信息。
平移、旋转和欧几里得变换都是仿射变换的特例,在欧几里得变换中,缩放和剪切参数都为零。
WarpAffine example [ C++ ]
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>
#include <cmath>
#include <vector>
using namespace cv;
using namespace std;
//In this program, we will use OpenCV warpaffine function to transform
//a triangle from source image to another triangle as shown in the
//above figure.
int main(void)
{
// Read image
Mat source = imread("sample.jpg",1);
// Create 2 warp matrices for different transformations
Mat warpMat = (Mat_<double>(2,3) << 1.2, 0.2, 2, -0.3, 1.3, 1 );
// Another mask
Mat warpMat2 = (Mat_<double>(2,3) << 1.2, 0.3, 2, 0.2, 1.3, 1);
Mat result,result2;
// Use warp affine
cv::warpAffine(source, result, warpMat,
Size(1.5*source.rows,1.4*source.cols), INTER_LINEAR, BORDER_REFLECT_101);
cv::warpAffine(source, result2, warpMat2,
Size(1.5*source.rows, 1.4*source.cols), INTER_LINEAR, BORDER_REFLECT_101);
// Display images
imshow("Original", source);
imshow("Result", result);
imshow("Result2", result2);
waitKey(0);
destroyAllWindows();
}
2. 逆问题
C++ [ getAffineTransform example ] [ getAffine.cpp ]
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>
#include <cmath>
#include <vector>
using namespace cv;
using namespace std;
int main(void)
{
/*In this program, we will use OpenCV getAffineTransform function
to obtain the warping matrix using two triangles,
one being the input triangle and the other being the output triangle
*/
// Input triangle
vector <Point2f> tri1;
tri1.push_back(Point2f(50, 50));
tri1.push_back(Point2f(180, 140));
tri1.push_back(Point2f(150, 200));
// Output triangle
vector <Point2f> tri2;
tri2.push_back(Point2f(72, 51));
tri2.push_back(Point2f(246, 129));
tri2.push_back(Point2f(222, 216));
// Another output triangle
vector <Point2f> tri3;
tri3.push_back(Point2f(77, 76));
tri3.push_back(Point2f(260, 219));
tri3.push_back(Point2f(242, 291));
// Get the transformation matrices
Mat warp = cv::getAffineTransform(tri1,tri2);
Mat warp2 = cv::getAffineTransform(tri1,tri3);
// Display the matrices
cout << "Warp Matrix 1 : \n\n " << warp
<< "\n\n" << "Warp Matrix 2 : \n" << warp2<< endl;
}
Output
Output |
---|
[1.19, 0.20, 2; |
-0.30, 1.3, 1]|
|[1.2, 0.30, 2;
0.19, 1.3, 1]|
转换矩阵相应的的点通过下面的公式进行计算:
单应矩阵和透视变换(Homography & Perspective Transform)
1. 什么是单应矩阵Homography?
Homography是一个3x3的转换矩阵,将一张图片里的点映射到另一张图片里相应的点上。如下图:
图 1: 一个3D平面(即指书的封面所在平面)上的两张图片被单应矩阵关联起来了
我们可以把3x3的单应矩阵写作:
H=⎡⎣⎢⎢h00h10h20h01h11h21h02h12h22⎤⎦⎥⎥
H
=
[
h
00
h
01
h
02
h
10
h
11
h
12
h
20
h
21
h
22
]
左图中的(x1,y1)和右图中的(x2,y2)由下面的方式来映射:
⎡⎣⎢⎢x1y11⎤⎦⎥⎥=H⎡⎣⎢⎢x2y21⎤⎦⎥⎥=⎡⎣⎢⎢h00h10h20h01h11h21h02h12h22⎤⎦⎥⎥⎡⎣⎢⎢x2y21⎤⎦⎥⎥
[
x
1
y
1
1
]
=
H
[
x
2
y
2
1
]
=
[
h
00
h
01
h
02
h
10
h
11
h
12
h
20
h
21
h
22
]
[
x
2
y
2
1
]
2. 使用单应矩阵来做图片对齐
上面的公式适用于所有对应点落在同一平面上的情况。
这就意味着,我们可以拿上面图一中的左图进行对齐操作,如图2所示:
图2:能够使用单应矩阵把一个3D平面上的图片对齐到同一平面的另一张图片上
不在同一个平面上的点不会进行对齐,如上图所示。
假如在图片里包含两个平面,那么你将需要两个单应矩阵来进行操作。
3. 单应矩阵应用案例:全景图(Panarama)
根据两张水平移动拍摄的图片共享的一些区域,将绿化图片对齐并连接起来,这样就得到了一个简单的“全景图”(Panarama)。
4. 怎样计算单应矩阵
计算两个图像之间的单应矩阵,你需要了解至少4个两幅图像之间的对应点。如果超过4个对应点更好。OpenCV将鲁棒地估计一个最适合所有对应点的单应矩阵。通常来说,这些点可以通过例如SIFT或SURF这样的算法,自动找到图像之间的匹配特征点,但在这篇文章里,我们只是手动地点击你需要的点。
// pts_src and pts_dst are vectors of points in source
// and destination images. They are of type vector<Point2f>.
// We need at least 4 corresponding points.
Mat h = findHomography(pts_src, pts_dst);
// The calculated homography can be used to warp
// the source image to destination. im_src and im_dst are
// of type Mat. Size is the size (width,height) of im_dst.
warpPerspective(im_src, im_dst, h, size);
4.1 OpenCV单应矩阵案例
图片2 中的效果能够使用下面的C++代码实现,下面的代码展示了怎样使用4个对应点来进行变换。
#include "opencv2/opencv.hpp"
using namespace cv;
using namespace std;
int main( int argc, char** argv)
{
// Read source image.
Mat im_src = imread("book2.jpg");
// Four corners of the book in source image
vector<Point2f> pts_src;
pts_src.push_back(Point2f(141, 131));
pts_src.push_back(Point2f(480, 159));
pts_src.push_back(Point2f(493, 630));
pts_src.push_back(Point2f(64, 601));
// Read destination image.
Mat im_dst = imread("book1.jpg");
// Four corners of the book in destination image.
vector<Point2f> pts_dst;
pts_dst.push_back(Point2f(318, 256));
pts_dst.push_back(Point2f(534, 372));
pts_dst.push_back(Point2f(316, 670));
pts_dst.push_back(Point2f(73, 473));
// Calculate Homography
Mat h = findHomography(pts_src, pts_dst);
// Output image
Mat im_out;
// Warp source image to destination based on homography
warpPerspective(im_src, im_out, h, im_dst.size());
// Display images
imshow("Source Image", im_src);
imshow("Destination Image", im_dst);
imshow("Warped Source Image", im_out);
waitKey(0);
}
下面是Python版本
#!/usr/bin/env python
import cv2
import numpy as np
if __name__ == '__main__' :
# Read source image.
im_src = cv2.imread('book2.jpg')
# Four corners of the book in source image
pts_src = np.array([[141, 131], [480, 159], [493, 630],[64, 601]])
# Read destination image.
im_dst = cv2.imread('book1.jpg')
# Four corners of the book in destination image.
pts_dst = np.array([[318, 256],[534, 372],[316, 670],[73, 473]])
# Calculate Homography
h, status = cv2.findHomography(pts_src, pts_dst)
# Warp source image to destination based on homography
im_out = cv2.warpPerspective(im_src, h, (im_dst.shape[1],im_dst.shape[0]))
# Display images
cv2.imshow("Source Image", im_src)
cv2.imshow("Destination Image", im_dst)
cv2.imshow("Warped Source Image", im_out)
cv2.waitKey(0)
5. 单应矩阵的应用
5.1 透视校正
将上面图一的照片校正成如图3所示
图3. 透视校正
下面是步骤。
1. 写一个用户界面来收集书的四个角的坐标,命名为pts_src.
2. 我们需要知道书本的宽高比,这本书大小为300x400,宽高比为3/4,因此,我们设定变换后的终点坐标pts_dst为(0,0),(299,0),(299,399)和(0,399)
3. 使用pts_src和pts_dst来获取单应矩阵
4. 对原图片应用单应矩阵来获得图3中的图片
Project Idea
透视校正的技术已经被大量文档扫描类app所使用,比如有着5千万下载量的CamScanner,这款应用允许你从任意角度拍摄你的文档或黑板、书籍等等,之后就可以通过透视转换来修正角度,从而达到扫描的效果。快试着用如此简单但又充满影响力以及实用的技术来做点什么吧!
5.2 虚拟公告板
我们将替换掉图5中的一块公告牌为我们的图4
图4. 上传到互联网上的第一张图片
图5. 时代广场
操作步骤:
1. 编写一个用户界面来收集公告牌中的四个角,命名这些点为pts_dst.
2. 测量你想放上公告板的图片的w×h,那么原图片的四点(pts_src)为(0,0),(w-1,0),(w-1,h-1) 和(0,h-1)
3. 使用pts_src和pts_det获得单应矩阵
4. 在原图片上应用单应矩阵并把它和目标图片混合起来得到图6
图6. 虚拟公告牌。左边的一块公告牌被替换了。