[图像处理]图片的伸缩变换
要求
编写程序,从标准输入读取图片的四个坐标和 bmp 格式图片,将这四个坐标框出的四边形拉伸成 224*224 的正方形,显示,并以 bmp 格式输出到标准输出。
思路
给定图像的四个坐标 {xi,yi},(i=1,2,3,4) 并且已知每个源坐标经变换后的对应坐标 {xi′,yi′},(i=1,2,3,4)
普通线性变换
想到的第一个方法,对源坐标进行线性变换
[a11a21a12a22][xy]=[x′y′]
但是很快会发现基本的线性变换并不能满足要求,因为普通的线性变换只能做到平行四边形到平行四边形的变换,并且不能应对图像的平移变换。
仿射变换
接着来看看仿射变换,仿射变换能够应对一般的图像旋转(线性变换),平移(向量加),缩放(线性变换),错切,反转。
在二维下的仿射变换形式:
[a11a21a12a22][xy]+[b1b2]=[x′y′]
但是写成这样的形式并不利于求解。我们把上式展开写:
{a11x+a12y+b1=x′a21x+a22y+b2=y′
所以就可以先把二维的坐标换为三维齐次形式,并把仿射变换写成如下形式:
⎣⎡a11a210a12a220a13a230⎦⎤⎣⎡xy1⎦⎤=⎣⎡x′y′0⎦⎤
具体如何求解还是回到展开式:$$\left{\begin{matrix} a_{11}x + a_{12}y + a_{13} = x’\ a_{21}x + a_{22}y + a_{23} = y’ \end{matrix}\right.$$ 发现这是一个六元方程组于是给定图像中三个不同源点坐标的对应变换后的坐标就可以求解仿射变换矩阵,我们把方程组写成如下形式:
⎩⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎧a11x1+a12y1+a13+0+0+0=x1′0+0+0+a21x1+a22y1+a23=y1′a11x2+a12y2+a13+0+0+0=x2′0+0+0+a21x2+a22y2+a23=y2′a11x3+a12y3+a13+0+0+0=x3′0+0+0+a21x3+a22y3+a23=y3′
用矩阵来表达即为:
⎣⎢⎢⎢⎢⎢⎢⎡x10x20x20y10y20y201010100x10x20x20y10y20y2010101⎦⎥⎥⎥⎥⎥⎥⎤⎣⎢⎢⎢⎢⎢⎢⎡a11a12a13a21a22a23⎦⎥⎥⎥⎥⎥⎥⎤=⎣⎢⎢⎢⎢⎢⎢⎡x1′y1′x2′y2′x3′y3′⎦⎥⎥⎥⎥⎥⎥⎤
记为:XA=X′ 。则,可以解出 A=X−1X′
但是此变换仍有局限性,因为本质上仿射变换只是在普通线性变换的基础上添加了平移量,所以原图像中平行四边形在变换后图像中仍是平行四边形。由于是平行四边形,只需要指定三个顶点即可确定。这显然还是不满足要求,寻找新的方法。
透视变换(投影变换)
直观的理解透视变换,将二维平面中 (X,Y) 转换为三维中 (X,Y,Z),再到另外一个二维 (X′,Y′) 的映射。相较于仿射变换,透视变换不仅能够做到平移,线性变换,还能够提供更多的自由度实现更为复杂的变换。
齐次化
在进行透视变换之前,因为是将二维平面映射到三维中,仍需要像之前处理仿射变换一样将坐标齐次化。
[xy]→⎣⎡xy1⎦⎤
变换方程
透视变换的方程也就是将仿射变换进行扩展:
⎣⎡a11a21a31a12a22a32a13a231⎦⎤⎣⎡xy1⎦⎤=⎣⎡XYZ⎦⎤
⎣⎡x′y′1⎦⎤=⎣⎡ZXZY1⎦⎤
则可以将变换方程写为:
⎩⎨⎧X=a11x+a12y+a13Y=a21x+a22y+a23Z=a31x+a32y+1
⎩⎨⎧x′=a31x+a32y+1a11x+a12y+a13y′=a31x+a32y+1a21x+a22y+a231=ZZ
我们将第二个方程组的两边同时乘以分母再移相,可以将方程组化成以下形式:
{a11x+a12y+a13−a31xx′−a32yx′=x′a21x+a22y+a23−a31xy′−a32yy′=y′
因为x,y,x′,y′都是已知量所以这是一个八元方程组,因此如果有4对坐标点我们就可以通过方程组解出投影变换矩阵。
⎩⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎧a11x1+a12y1+a13−a31x1x1′−a32y1x1′=x1′a21x1+a22y1+a23−a31x1y1′−a32y1y1′=y1′a11x2+a12y2+a13−a31x2x2′−a32y1x2′=x2′a21x2+a22y2+a23−a31x2y2′−a32y1y2′=y2′a11x3+a12y3+a13−a31x3x3′−a32y1x3′=x3′a21x3+a22y3+a23−a31x3y3′−a32y1y3′=y3′a11x4+a12y4+a13−a31x4x4′−a32y1x4′=x4′a21x4+a22y4+a23−a31x4y4′−a32y1y4′=y4′
这样就可以把方程组写成矩阵形式:
⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎡x10x20x30x40y10y20y30y40101010100x10x20x30x40y10y20y30y401010101−x1x1′−x1y1′−x2x2′−x2y2′−x3x3′−x3y3′−x4x4′−x4y4′−y1x1′−y1y1′−y2x2′−y2y2′−y3x3′−y3y3′−y4x4′−y4y4′⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎤⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎡a11a12a13a21a22a23a31a32⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎤=⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎡x1′y1′x2′y2′x3′y4′x4′y4′⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎤
同样将上式记作AB=X ,则可以解得 B=A−1X
具体实现代码
编译依赖库
Eigen3(开源C++线性代数库),OpenCV3.8.4(其余版本未测试)。请在项目CMakeLists.txt文件中加入Eigen3和OpenCV依赖
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
| #include <iostream> #include <string> #include <vector> #include <Eigen/Dense>
#include <opencv2/core/core.hpp> #include <opencv2/features2d/features2d.hpp> #include <opencv2/highgui/highgui.hpp>
using namespace std; using namespace Eigen; using namespace cv;
const int row_count = 224; const int col_count = 224;
Matrix3d getPerspectiveTransform(vector<KeyPoint> Src, vector<KeyPoint> Dst) { if(Src.size() != Dst.size() && Src.size() != 4) { cerr << "input error" << endl; exit(1); }
MatrixXd A(8, 8), B(8, 1); A.fill(0), B.fill(0);
for(int i = 0; i < 4; ++i) { A(2*i, 0) = Src[i].pt.x, A(2*i, 1) = Src[i].pt.y, A(2*i, 2) = 1; A(2*i, 6) = -Src[i].pt.x*Dst[i].pt.x, A(2*i, 7) = -Src[i].pt.y*Dst[i].pt.x;
A(2*i + 1, 3) = Src[i].pt.x, A(2*i + 1, 4) = Src[i].pt.y, A(2*i + 1, 5) = 1; A(2*i + 1, 6) = -Src[i].pt.x*Dst[i].pt.y, A(2*i + 1, 7) = -Src[i].pt.y*Dst[i].pt.y;
B(2*i, 0) = Dst[i].pt.x, B(2*i + 1, 0) = Dst[i].pt.y; }
MatrixXd result = A.inverse() * B; Matrix3d WarpMatrix;
for(int i = 0; i < 3; i++) for(int j = 0; j < 3; j++) { if(i == 2 && j == 2) WarpMatrix(i, j) = 1.0; else WarpMatrix(i, j) = result(i * 3 + j, 0); } return WarpMatrix; }
int main(int argc, char** argv) {
string SrcFile; cout << "Please input the source image." << endl; cin >> SrcFile;
Mat _img = imread(SrcFile, CV_LOAD_IMAGE_COLOR); Mat draw_img; Mat out_img(row_count, col_count, CV_8UC3);
vector<KeyPoint> Src, Dst;
cout << "Please input points." << endl; for(int i = 0; i < 4; ++i) { int x, y; cin >> x >> y; Dst.emplace_back((double)x, (double)y, 10); }
Src.emplace_back(0, 0, 10);Src.emplace_back(0, col_count - 1, 10); Src.emplace_back(row_count - 1, col_count - 1, 10);Src.emplace_back(row_count - 1, 0, 10);
MatrixXd WarpMatrix = getPerspectiveTransform(Src, Dst);
for(int i = 0; i < Dst.size(); ++i) swap(Dst[i].pt.x, Dst[i].pt.y);
drawKeypoints(_img, Dst, draw_img);
Vector3d s, t; s(2) = 1; for(int i = 0; i < row_count; ++i) for(int j = 0; j < col_count; ++j) { s(0) = i; s(1) = j; t = WarpMatrix * s;
t(0) /= t(2); t(1) /= t(2);
out_img.at<Vec3b>(i, j) = _img.at<Vec3b>((uint)t(0), (uint)t(1)); }
imshow("Source Image", draw_img); imshow("Out Image", out_img); imwrite("OutImage.bmp", out_img);
waitKey(0); return 0; }
|