图像处理:变换——仿射变换 OpenCV v4.8.0

上一个教程重映射

下一个教程直方图均衡化

原作者Ana Huamán
兼容性OpenCV >= 3.0

目标

在本教程中,您将学习如何

理论

什么是仿射变换?

  1. 可以矩阵乘法(线性变换)和矢量加法(平移)的形式表示的变换。
  2. 根据上述内容,我们可以使用仿射变换来表达:

a. 旋转(线性变换)
b. 平移(矢量加法)
c. 缩放操作(线性变换)

由此可见,从本质上讲,仿射变换表示两个图像之间的关系

  1. 表示仿射变换的通常方法是使用 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]

如何得到仿射变换?

  1. 我们提到,仿射变换基本上是两个图像之间的一种关系。关于这种关系的信息大致有两种来源:
    a. 我们知道 X 和 T,也知道它们是相关的。那么我们的任务就是找到 M
    b. 我们知道 M 和 X。要得到 T,我们只需应用 T=M⋅X。我们关于 M 的信息可以是明确的(即拥有 2×3 矩阵),也可以是点之间的几何关系。
  2. 让我们用更好的方法(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 个点所定义的三角形的大小和方向发生了变化。

 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 要旋转图像,我们需要知道两件事:

  1. 图像旋转的中心点
  2. 要旋转的角度。在 OpenCV 中,正角为逆时针方向
  3. 可选项: 比例因子

我们用以下代码段定义这些参数:
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 变换后,我们将得到

在这里插入图片描述

  • 最后,在应用负旋转(记住负表示顺时针方向)和缩放因子后,我们会得到

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值