仿射变换
问题描述:读取图像,然后对图像多种仿射变换。
仿射变换-缩放与平移
现在我们可以利用 3 × 3 3\times3 3×3的矩阵来对图像进行多种仿射变换。
现,我们记原图像为 ( x , y ) (x,y) (x,y),变换后的图像为 ( x ′ , y ′ ) (x',y') (x′,y′)。
对于二维空间上的坐标操作,其变换关系满足(在此处,我们可以将此变换操作理解为图像的放大缩小矩阵)下式:
(
x
′
y
′
)
=
(
a
b
c
d
)
(
x
y
)
\left( \begin{matrix} x'\\ y' \end{matrix} \right)= \left( \begin{matrix} a&b\\ c&d \end{matrix} \right)\ \left( \begin{matrix} x\\ y \end{matrix} \right)
(x′y′)=(acbd) (xy)
另一方面,二维空间中的平移操作可按照下面的式子计算:
(
x
′
y
′
)
=
(
x
y
)
+
(
t
x
t
y
)
\left( \begin{matrix} x'\\ y' \end{matrix} \right)= \left( \begin{matrix} x\\ y \end{matrix} \right)+ \left( \begin{matrix} t_x\\ t_y \end{matrix} \right)
(x′y′)=(xy)+(txty)
把上面两个式子在三维空间中张开,就可以写作以下等式:
(
x
′
y
′
1
)
=
(
a
b
t
x
c
d
t
y
0
0
1
)
(
x
y
1
)
\left( \begin{matrix} x'\\ y'\\ 1 \end{matrix} \right)= \left( \begin{matrix} a&b&t_x\\ c&d&t_y\\ 0&0&1 \end{matrix} \right)\ \left( \begin{matrix} x\\ y\\ 1 \end{matrix} \right)
⎝⎛x′y′1⎠⎞=⎝⎛ac0bd0txty1⎠⎞ ⎝⎛xy1⎠⎞
但是在实际操作的过程中,如果一个一个地计算原图像的像素的话,处理后的像素可能没有在原图像中有对应的坐标。因此,我们有必要对处理后的图像中各个像素进行仿射变换逆变换,取得变换后图像中的像素在原图像中的坐标。仿射变换的逆变换如下:
(
x
y
)
=
1
a
d
−
b
c
(
d
−
b
−
c
a
)
(
x
′
y
′
)
−
(
t
x
t
y
)
\left( \begin{matrix} x\\ y \end{matrix} \right)= \frac{1}{a\ d-b\ c}\ \left( \begin{matrix} d&-b\\ -c&a \end{matrix} \right)\ \left( \begin{matrix} x'\\ y' \end{matrix} \right)- \left( \begin{matrix} t_x\\ t_y \end{matrix} \right)
(xy)=a d−b c1 (d−c−ba) (x′y′)−(txty)
这回的平行移动操作使用下面的式子计算。
t
x
t_x
tx和
t
y
t_y
ty是像素移动的距离。
(
x
′
y
′
1
)
=
(
1
0
t
x
0
1
t
y
0
0
1
)
(
x
y
1
)
\left( \begin{matrix} x'\\ y'\\ 1 \end{matrix} \right)= \left( \begin{matrix} 1&0&t_x\\ 0&1&t_y\\ 0&0&1 \end{matrix} \right)\ \left( \begin{matrix} x\\ y\\ 1 \end{matrix} \right)
⎝⎛x′y′1⎠⎞=⎝⎛100010txty1⎠⎞ ⎝⎛xy1⎠⎞
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <math.h>
cv::Mat affine(cv::Mat img, double a, double b, double c, double d, double tx, double ty)
{
int row = img.rows;
int col = img.cols;
int channel = img.channels();
// get detriment
double det = a * d - b * c;
// Resize width and height
int resized_col = (int)(col * a);
int resized_row = (int)(row * d);
cv::Mat new_image = cv::Mat::zeros(resized_row, resized_col, CV_8UC3);
int pos_before_x, pos_before_y;
// 仿射变换
for (int i = 0; i < resized_row; i++)
{
for (int j = 0; j < resized_col; j++)
{
pos_before_x = (int)((d * j - b * i) / det - tx);
if (pos_before_x < 0 || pos_before_x > col)
{
continue;
}
pos_before_y = int((-c * j + a * i) / det - ty);
if (pos_before_y < 0 || pos_before_y > row)
{
continue;
}
for (int chan = 0; chan < channel; chan++)
{
new_image.at<cv::Vec3b>(i, j)[chan] = img.at<cv::Vec3b>(pos_before_y, pos_before_x)[chan];
}
}
}
return new_image;
}
int main(){
// read image
cv::Mat img = cv::imread("../fate.jpeg", cv::IMREAD_COLOR);
// x向向右平移100,y向向上平移100
// cv::Mat new_image = affine(img, 1, 0, 0, 1, 100, -100);
// x向放大1.3倍再向右平移100,y向放大0.8倍再向上平移100
cv::Mat new_image = affine(img, 1.3, 0, 0, 0.8, 100, -100);
// cv::imwrite("../1-10/fate_affine.jpeg", new_image);
cv::imwrite("../1-10/fate_affine1.jpeg", new_image);
cv::imshow("vv", new_image);
cv::waitKey(0);
cv::destroyAllWindows();
return 0;
}
输入图像 (fate.jpeg) | 输出图像 (fate_trans.jpeg) | 输出图像 (fate_trans_scale.jpeg) |
---|---|---|
![]() | ![]() | ![]() |
仿射变换-旋转
- 使用仿射变换,逆时针旋转 30 30 30度。
- 使用仿射变换,逆时针旋转 30 30 30度并且能让全部图像显现(也就是说,单纯地做仿射变换会让图片边缘丢失,这一步中要让图像的边缘不丢失)。
可以借助二维旋转矩阵,利用下面的式子进行逆时针方向旋转
A
A
A度的仿射变换:
(
x
′
y
′
1
)
=
(
cos
(
A
)
−
sin
(
A
)
t
x
sin
(
A
)
cos
(
A
)
t
y
0
0
1
)
(
x
y
1
)
\left( \begin{matrix} x'\\ y'\\ 1 \end{matrix} \right)= \left( \begin{matrix} \cos(A)&-\sin(A)&t_x\\ \sin(A)&\cos(A)&t_y\\ 0&0&1 \end{matrix} \right)\ \left( \begin{matrix} x\\ y\\ 1 \end{matrix} \right)
⎝⎛x′y′1⎠⎞=⎝⎛cos(A)sin(A)0−sin(A)cos(A)0txty1⎠⎞ ⎝⎛xy1⎠⎞
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <math.h>
cv::Mat affine(cv::Mat img, double a, double b, double c, double d, double tx, double ty, double theta)
{
int row = img.rows;
int col = img.cols;
int channel = img.channels();
// get detriment
double det = a * d - b * c;
if (theta != 0)
{
// 如果存在旋转就重新定义仿射矩阵参量
double rad = theta / 180 * M_PI;
a = std::cos(rad);
b = -std::sin(rad);
c = std::sin(rad);
d = std::cos(rad);
tx = 0;
ty = 0;
double det = a * d - b * c;
// 定义旋转前后的中心,以及两个中心的偏移量
double center_x = col / 2.;
double center_y = row / 2.;
double rot_center_x = (d * center_x - b * center_y) / det;
double rot_center_y = (-c * center_x + a * center_y) / det;
tx = rot_center_x - center_x;
ty = rot_center_y - center_y;
}
// Resize width and height
int resized_col = (int)(col * a);
int resized_row = (int)(row * d);
if (theta != 0) {
resized_col = (int)(col);
resized_row = (int)(row);
}
cv::Mat new_image = cv::Mat::zeros(resized_row, resized_col, CV_8UC3);
int pos_before_x, pos_before_y;
// 仿射变换
for (int i = 0; i < resized_row; i++)
{
for (int j = 0; j < resized_col; j++)
{
pos_before_x = (int)((d * j - b * i) / det - tx);
if (pos_before_x < 0 || pos_before_x >= col)
{
continue;
}
pos_before_y = int((-c * j + a * i) / det - ty);
if (pos_before_y < 0 || pos_before_y >= row)
{
continue;
}
for (int chan = 0; chan < channel; chan++)
{
new_image.at<cv::Vec3b>(i, j)[chan] = img.at<cv::Vec3b>(pos_before_y, pos_before_x)[chan];
}
}
}
return new_image;
}
int main(){
// read image
cv::Mat img = cv::imread("../Neopolitan.png", cv::IMREAD_COLOR);
// 先旋转50度,将其旋转后的图像拉回图像中心
cv::Mat new_image = affine(img, 1, 0, 0, 1, 0, 0, 50);
// cv::imwrite("../1-10/fate_affine.jpeg", new_image);
cv::imwrite("../1-10/Neopolitan_rot.png", new_image);
cv::imshow("vv", new_image);
cv::waitKey(0);
cv::destroyAllWindows();
return 0;
}
输入图像 (Neopolitan.png) | 输出图像 (Neopolitan_rot.png) |
---|---|
![]() | ![]() |
仿射变换-倾斜
- 使用仿射变换,输出 x x x轴倾斜 30 30 30度的图像( t x = 30 t_x=30 tx=30),这种变换被称为X-sharing。
- 使用仿射变换,输出y轴倾斜 30 30 30度的图像( t y = 30 t_y=30 ty=30),这种变换被称为Y-sharing。
- 使用仿射变换,输出 x x x轴、 y y y轴都倾斜 30 30 30度的图像( t x = 30 t_x = 30 tx=30, t y = 30 t_y = 30 ty=30)。
引入非正交参量 a a a来表达倾斜,其值定义为 t x t y t_x\ t_y tx ty与原图像的大小 h w h\ w h w的比值,可以使用下面各式进行仿射变换:
-
X-sharing
a = t x h [ x ′ y ′ 1 ] = [ 1 a t x 0 1 t y 0 0 1 ] [ x y 1 ] a=\frac{t_x}{h}\\ \left[ \begin{matrix} x'\\ y'\\ 1 \end{matrix} \right]=\left[ \begin{matrix} 1&a&t_x\\ 0&1&t_y\\ 0&0&1 \end{matrix} \right]\ \left[ \begin{matrix} x\\ y\\ 1 \end{matrix} \right] a=htx⎣⎡x′y′1⎦⎤=⎣⎡100a10txty1⎦⎤ ⎣⎡xy1⎦⎤ -
Y-sharing
a = t y w [ x ′ y ′ 1 ] = [ 1 0 t x a 1 t y 0 0 1 ] [ x y 1 ] a=\frac{t_y}{w}\\ \left[ \begin{matrix} x'\\ y'\\ 1 \end{matrix} \right]=\left[ \begin{matrix} 1&0&t_x\\ a&1&t_y\\ 0&0&1 \end{matrix} \right]\ \left[ \begin{matrix} x\\ y\\ 1 \end{matrix} \right] a=wty⎣⎡x′y′1⎦⎤=⎣⎡1a0010txty1⎦⎤ ⎣⎡xy1⎦⎤
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <math.h>
cv::Mat affine(cv::Mat img, double a, double b, double c, double d, double tx, double ty, double theta, double dx, double dy)
{
int row = img.rows;
int col = img.cols;
int channel = img.channels();
// get detriment
double det = a * d - b * c;
if (dx != 0){
b = dx / row;
}
if (dy != 0){
c = dy / col;
}
if (theta != 0)
{
// 如果存在旋转就重新定义仿射矩阵参量
double rad = theta / 180 * M_PI;
a = std::cos(rad);
b = -std::sin(rad);
c = std::sin(rad);
d = std::cos(rad);
tx = 0;
ty = 0;
double det = a * d - b * c;
// 定义旋转前后的中心,以及两个中心的偏移量
double center_x = col / 2.;
double center_y = row / 2.;
double rot_center_x = (d * center_x - b * center_y) / det;
double rot_center_y = (-c * center_x + a * center_y) / det;
tx = rot_center_x - center_x;
ty = rot_center_y - center_y;
}
// Resize width and height
int resized_col = (int)(col * a + dx);
int resized_row = (int)(row * d + dy);
if (theta != 0) {
resized_col = (int)(col + dx);
resized_row = (int)(row + dy);
}
cv::Mat new_image = cv::Mat::zeros(resized_row, resized_col, CV_8UC3);
int pos_before_x, pos_before_y;
// 仿射变换
for (int i = 0; i < resized_row; i++)
{
for (int j = 0; j < resized_col; j++)
{
pos_before_x = (int)((d * j - b * i) / det - tx);
if (pos_before_x < 0 || pos_before_x >= col)
{
continue;
}
pos_before_y = int((-c * j + a * i) / det - ty);
if (pos_before_y < 0 || pos_before_y >= row)
{
continue;
}
for (int chan = 0; chan < channel; chan++)
{
new_image.at<cv::Vec3b>(i, j)[chan] = img.at<cv::Vec3b>(pos_before_y, pos_before_x)[chan];
}
}
}
return new_image;
}
int main(){
// read image
cv::Mat img = cv::imread("../fate.jpeg", cv::IMREAD_COLOR);
// 倾斜
cv::Mat new_image = affine(img, 1, 0, 0, 1, 0, 0, 0, 60, 0);
// cv::imwrite("../1-10/fate_affine.jpeg", new_image);
cv::imwrite("../1-10/fate_test12.jpeg", new_image);
cv::imshow("vv", new_image);
cv::waitKey(0);
cv::destroyAllWindows();
return 0;
}
输入图像 (fate.jpeg) | 输出图像 (fate_x.jpeg) | 输出图像 (fate_y.jpeg) | 输出图像 (fate_xy.jpeg) |
---|---|---|---|
![]() | ![]() | ![]() | ![]() |