上一个教程 : 重映射
下一个教程 : 直方图均衡化
原作者 | Ana Huamán |
---|---|
兼容性 | OpenCV >= 3.0 |
目标
在本教程中,您将学习如何
- 使用 OpenCV 函数 cv::warpAffine 实现简单的重映射例程。
- 使用 OpenCV 函数 cv::getRotationMatrix2D 获取 2×3 旋转矩阵
理论
什么是仿射变换?
- 可以矩阵乘法(线性变换)和矢量加法(平移)的形式表示的变换。
- 根据上述内容,我们可以使用仿射变换来表达:
a. 旋转(线性变换)
b. 平移(矢量加法)
c. 缩放操作(线性变换)
由此可见,从本质上讲,仿射变换表示两个图像之间的关系。
- 表示仿射变换的通常方法是使用 2×3 矩阵。
A = [ a 00 a 01 a 10 a 11 ] 2 × 2 B = [ b 00 b 10 ] 2 × 1 A=\left[ \begin{matrix} a_{00}& a_{01}\\ a_{10}& a_{11}\\ \end{matrix} \right] _{2\times 2}\ \ B=\left[ \begin{array}{c} b_{00}\\ b_{10}\\ \end{array} \right] _{2\times 1} A=[a00a10a01a11]2×2 B=[b00b10]2×1
M = [ A B ] = [ a 00 a 01 b 00 a 10 a 11 b 10 ] 2 × 3 M=\left[ A\ \ \ B \right] =\left[ \begin{array}{c} \begin{matrix} a_{00}& a_{01}& b_{00}\\ \end{matrix}\\ \begin{matrix} a_{10}& a_{11}& b_{10}\\ \end{matrix}\\ \end{array} \right] _{2\times 3} M=[A B]=[a00a01b00a10a11b10]2×3
考虑到我们想用 A 和 B 来变换一个二维向量
X
=
[
x
y
]
X=\left[ \begin{array}{c} x\\ y\\ \end{array} \right]
X=[xy]
我们可以用以下方法进行变换:
T = A ⋅ [ x y ] + B o r T = M ⋅ [ x , y , 1 ] T T=A\cdot \left[ \begin{array}{c} x\\ y\\ \end{array} \right] +B\ or\ T=M\cdot \left[ \begin{matrix} x,& y,& 1\\ \end{matrix} \right] ^T T=A⋅[xy]+B or T=M⋅[x,y,1]T
T = [ a 00 x + a 01 y + b 00 a 10 x + a 11 y + b 10 ] T=\left[ \begin{array}{c} a_{00}x+a_{01}y+b_{00}\\ a_{10}x+a_{11}y+b_{10}\\ \end{array} \right] T=[a00x+a01y+b00a10x+a11y+b10]
如何得到仿射变换?
- 我们提到,仿射变换基本上是两个图像之间的一种关系。关于这种关系的信息大致有两种来源:
a. 我们知道 X 和 T,也知道它们是相关的。那么我们的任务就是找到 M
b. 我们知道 M 和 X。要得到 T,我们只需应用 T=M⋅X。我们关于 M 的信息可以是明确的(即拥有 2×3 矩阵),也可以是点之间的几何关系。 - 让我们用更好的方法(b)来解释这个问题。由于 M 关系到 2 幅图像,我们可以分析最简单的情况,即它关系到两幅图像中的三个点。请看下图:
点 1、2 和 3(在图像 1 中构成三角形)被映射到图像 2 中,仍然构成三角形,但现在它们发生了明显的变化。如果我们找到这 3 个点的仿射变换关系(您可以随意选择),那么我们就可以将找到的关系应用到图像中的所有像素。
代码
- 这个程序要做什么?
- 加载图像
- 对图像应用仿射变换。该变换由三点之间的关系获得。为此,我们使用了函数 cv::warveAffine。
- 对变换后的图像进行旋转。该旋转以图像中心为基准
- 等待用户退出程序
C++
教程代码如下所示。您也可以在此处下载
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include <iostream>
using namespace cv;
using namespace std;
int main( int argc, char** argv )
{
CommandLineParser parser( argc, argv, "{@input | lena.jpg | input image}" );
Mat src = imread( samples::findFile( parser.get<String>( "@input" ) ) );
if( src.empty() )
{
cout << "Could not open or find the image!\n" << endl;
cout << "Usage: " << argv[0] << " <Input image>" << endl;
return -1;
}
Point2f srcTri[3];
srcTri[0] = Point2f( 0.f, 0.f );
srcTri[1] = Point2f( src.cols - 1.f, 0.f );
srcTri[2] = Point2f( 0.f, src.rows - 1.f );
Point2f dstTri[3];
dstTri[0] = Point2f( 0.f, src.rows*0.33f );
dstTri[1] = Point2f( src.cols*0.85f, src.rows*0.25f );
dstTri[2] = Point2f( src.cols*0.15f, src.rows*0.7f );
Mat warp_mat = getAffineTransform( srcTri, dstTri );
Mat warp_dst = Mat::zeros( src.rows, src.cols, src.type() );
warpAffine( src, warp_dst, warp_mat, warp_dst.size() );
Point center = Point( warp_dst.cols/2, warp_dst.rows/2 );
double angle = -50.0;
double scale = 0.6;
Mat rot_mat = getRotationMatrix2D( center, angle, scale );
Mat warp_rotate_dst;
warpAffine( warp_dst, warp_rotate_dst, rot_mat, warp_dst.size() );
imshow( "Source image", src );
imshow( "Warp", warp_dst );
imshow( "Warp + Rotate", warp_rotate_dst );
waitKey();
return 0;
}
Java
教程代码如下所示。您也可以在此处下载
import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.MatOfPoint2f;
import org.opencv.core.Point;
import org.opencv.highgui.HighGui;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
class GeometricTransforms {
public void run(String[] args) {
String filename = args.length > 0 ? args[0] : "../data/lena.jpg";
Mat src = Imgcodecs.imread(filename);
if (src.empty()) {
System.err.println("Cannot read image: " + filename);
System.exit(0);
}
Point[] srcTri = new Point[3];
srcTri[0] = new Point( 0, 0 );
srcTri[1] = new Point( src.cols() - 1, 0 );
srcTri[2] = new Point( 0, src.rows() - 1 );
Point[] dstTri = new Point[3];
dstTri[0] = new Point( 0, src.rows()*0.33 );
dstTri[1] = new Point( src.cols()*0.85, src.rows()*0.25 );
dstTri[2] = new Point( src.cols()*0.15, src.rows()*0.7 );
Mat warpMat = Imgproc.getAffineTransform( new MatOfPoint2f(srcTri), new MatOfPoint2f(dstTri) );
Mat warpDst = Mat.zeros( src.rows(), src.cols(), src.type() );
Imgproc.warpAffine( src, warpDst, warpMat, warpDst.size() );
Point center = new Point(warpDst.cols() / 2, warpDst.rows() / 2);
double angle = -50.0;
double scale = 0.6;
Mat rotMat = Imgproc.getRotationMatrix2D( center, angle, scale );
Mat warpRotateDst = new Mat();
Imgproc.warpAffine( warpDst, warpRotateDst, rotMat, warpDst.size() );
HighGui.imshow( "Source image", src );
HighGui.imshow( "Warp", warpDst );
HighGui.imshow( "Warp + Rotate", warpRotateDst );
HighGui.waitKey(0);
System.exit(0);
}
}
public class GeometricTransformsDemo {
public static void main(String[] args) {
// Load the native OpenCV library
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
new GeometricTransforms().run(args);
}
}
Python
教程代码如下所示。您也可以在此处下载
from __future__ import print_function
import cv2 as cv
import numpy as np
import argparse
parser = argparse.ArgumentParser(description='Code for Affine Transformations tutorial.')
parser.add_argument('--input', help='Path to input image.', default='lena.jpg')
args = parser.parse_args()
src = cv.imread(cv.samples.findFile(args.input))
if src is None:
print('Could not open or find the image:', args.input)
exit(0)
srcTri = np.array( [[0, 0], [src.shape[1] - 1, 0], [0, src.shape[0] - 1]] ).astype(np.float32)
dstTri = np.array( [[0, src.shape[1]*0.33], [src.shape[1]*0.85, src.shape[0]*0.25], [src.shape[1]*0.15, src.shape[0]*0.7]] ).astype(np.float32)
warp_mat = cv.getAffineTransform(srcTri, dstTri)
warp_dst = cv.warpAffine(src, warp_mat, (src.shape[1], src.shape[0]))
# Rotating the image after Warp
center = (warp_dst.shape[1]//2, warp_dst.shape[0]//2)
angle = -50
scale = 0.6
rot_mat = cv.getRotationMatrix2D( center, angle, scale )
warp_rotate_dst = cv.warpAffine(warp_dst, rot_mat, (warp_dst.shape[1], warp_dst.shape[0]))
cv.imshow('Source image', src)
cv.imshow('Warp', warp_dst)
cv.imshow('Warp + Rotate', warp_rotate_dst)
cv.waitKey()
说明
- 加载图片
C++
CommandLineParser parser( argc, argv, "{@input | lena.jpg | input image}" );
Mat src = imread( samples::findFile( parser.get<String>( "@input" ) ) );
if( src.empty() )
{
cout << "Could not open or find the image!\n" << endl;
cout << "Usage: " << argv[0] << " <Input image>" << endl;
return -1;
}
Java
String filename = args.length > 0 ? args[0] : "../data/lena.jpg";
Mat src = Imgcodecs.imread(filename);
if (src.empty()) {
System.err.println("Cannot read image: " + filename);
System.exit(0);
}
Python
parser = argparse.ArgumentParser(description='Code for Affine Transformations tutorial.')
parser.add_argument('--input', help='Path to input image.', default='lena.jpg')
args = parser.parse_args()
src = cv.imread(cv.samples.findFile(args.input))
if src is None:
print('Could not open or find the image:', args.input)
exit(0)
- 仿射变换: 正如我们在上面几行所解释的,我们需要两组 3 点来推导仿射变换关系。请看
C++
Point2f srcTri[3];
srcTri[0] = Point2f( 0.f, 0.f );
srcTri[1] = Point2f( src.cols - 1.f, 0.f );
srcTri[2] = Point2f( 0.f, src.rows - 1.f );
Point2f dstTri[3];
dstTri[0] = Point2f( 0.f, src.rows*0.33f );
dstTri[1] = Point2f( src.cols*0.85f, src.rows*0.25f );
dstTri[2] = Point2f( src.cols*0.15f, src.rows*0.7f );
Java
Point[] srcTri = new Point[3];
srcTri[0] = new Point( 0, 0 );
srcTri[1] = new Point( src.cols() - 1, 0 );
srcTri[2] = new Point( 0, src.rows() - 1 );
Point[] dstTri = new Point[3];
dstTri[0] = new Point( 0, src.rows()*0.33 );
dstTri[1] = new Point( src.cols()*0.85, src.rows()*0.25 );
dstTri[2] = new Point( src.cols()*0.15, src.rows()*0.7 );
Python
srcTri = np.array( [[0, 0], [src.shape[1] - 1, 0], [0, src.shape[0] - 1]] ).astype(np.float32)
dstTri = np.array( [[0, src.shape[1]*0.33], [src.shape[1]*0.85, src.shape[0]*0.25], [src.shape[1]*0.15, src.shape[0]*0.7]] ).astype(np.float32)
您可能想画出这些点,以便更好地了解它们是如何变化的。它们的位置与示例图(理论部分)中的位置大致相同。您可能会注意到这 3 个点所定义的三角形的大小和方向发生了变化。
- 有了这两组点,我们就可以使用 OpenCV 函数 cv::getAffineTransform 计算仿射变换:
C++
Mat warp_mat = getAffineTransform( srcTri, dstTri );
Java
Mat warpMat = Imgproc.getAffineTransform( new MatOfPoint2f(srcTri), new MatOfPoint2f(dstTri) );
Python
warp_mat = cv.getAffineTransform(srcTri, dstTri)
我们将得到一个 2×3 矩阵作为输出(本例中为 warp_mat)
- 然后,我们将刚刚找到的仿射变换应用到源图像中
C++
Mat warp_dst = Mat::zeros( src.rows, src.cols, src.type() );
warpAffine( src, warp_dst, warp_mat, warp_dst.size() );
Java
Mat warpDst = Mat.zeros( src.rows(), src.cols(), src.type() );
Imgproc.warpAffine( src, warpDst, warpMat, warpDst.size() );
Python
warp_dst = cv.warpAffine(src, warp_mat, (src.shape[1], src.shape[0]))
使用以下参数
-
src: 输入图像
-
warp_dst: 输出图像
-
warp_mat: 仿射变换
-
warp_dst.size(): 输出图像的预期大小
我们刚刚得到了第一幅变换后的图像!我们将以一位显示。在此之前,我们还需要旋转它… -
Rotate 要旋转图像,我们需要知道两件事:
- 图像旋转的中心点
- 要旋转的角度。在 OpenCV 中,正角为逆时针方向
- 可选项: 比例因子
我们用以下代码段定义这些参数:
C++
Point center = Point( warp_dst.cols/2, warp_dst.rows/2 );
double angle = -50.0;
double scale = 0.6;
Java
Point center = new Point(warpDst.cols() / 2, warpDst.rows() / 2);
double angle = -50.0;
double scale = 0.6;
Python
center = (warp_dst.shape[1]//2, warp_dst.shape[0]//2)
angle = -50
scale = 0.6
- 我们使用 OpenCV 函数 cv::getRotationMatrix2D 生成旋转矩阵,它会返回一个 2×3 矩阵(本例中为 rot_mat)
C++
Mat rot_mat = getRotationMatrix2D( center, angle, scale );
Java
Mat rotMat = Imgproc.getRotationMatrix2D( center, angle, scale );
Python
rot_mat = cv.getRotationMatrix2D( center, angle, scale )
- 现在,我们将找到的旋转应用到之前变换的输出中:
C++
Mat warp_rotate_dst;
warpAffine( warp_dst, warp_rotate_dst, rot_mat, warp_dst.size() );
Java
Mat warpRotateDst = new Mat();
Imgproc.warpAffine( warpDst, warpRotateDst, rotMat, warpDst.size() );
Python
warp_rotate_dst = cv.warpAffine(warp_dst, rot_mat, (warp_dst.shape[1], warp_dst.shape[0]))
- 最后,我们在两个窗口中显示结果,并显示原始图像:
C++
imshow( "Source image", src );
imshow( "Warp", warp_dst );
imshow( "Warp + Rotate", warp_rotate_dst );
Java
HighGui.imshow( "Source image", src );
HighGui.imshow( "Warp", warpDst );
HighGui.imshow( "Warp + Rotate", warpRotateDst );
Python
cv.imshow('Source image', src)
cv.imshow('Warp', warp_dst)
cv.imshow('Warp + Rotate', warp_rotate_dst)
- 我们只需等待用户退出程序即可
C++
waitKey();
Java
HighGui.waitKey(0);
Python
cv.waitKey()
结果
- 编译完上述代码后,我们可以将图片的路径作为参数提供给它。例如
- 应用第一次 Affine 变换后,我们将得到
- 最后,在应用负旋转(记住负表示顺时针方向)和缩放因子后,我们会得到