当前位置:网站首页>Opencv——几何空间变换(仿射变换和投影变换)
Opencv——几何空间变换(仿射变换和投影变换)
2022-06-28 00:38:00 【51CTO】
几何空间变换
- 【3】图像的仿射变换
- 1、平移变换
- 2、比例缩放
- 3、旋转
- 4、对称变换(不做展示)
- 1、关于X轴变换
- 2、关于Y轴变换
- 3、关于直线Y=X变换
- 4、关于直线Y=-X变换
【1】几何变换(空间变换)简述
图像的几何变换,又称空间变换,是图形处理的一个方面,是各种图形处理算法的基础。它将一幅图像中的坐标位置映射到另一幅图像中的新坐标位置,其实质是改变像素的空间位置,估算新空间位置上的像素值。
几何变换算法一般包括空间变换运算和插值算法。
【2】变换矩阵知识简述
齐次坐标的概念
图像一般是二维的,坐标形式为(x,y)。
这里我们将其扩展为3维形式的齐次坐标。形式如下:
第三个参数是尺度参数,控制尺度缩放。(1的时候表示尺度不变)
齐次坐标使用n+1维,来表示n维的坐标。它的优点如下所示:
●统一坐标的加法运算和乘法运算, 运算时提高效率。
●表示无穷远的点。 当z=0的时候,表示无穷远的点。
( x,y,z) ----->( x/z, y/z) ;齐次坐标和二维坐标的换算
如,(2,2,1),(4,4,2 )表示同样的点。
几何运算矩阵

最左边是变换后的齐次坐标,中间的是原图点的其次坐标,最右边是变换矩阵,有9个参数,分为4个子矩阵,每个子矩阵具有特殊意义。
T1:比例、旋转、对称、错切
T2:平移
T3:投影
T4:整体缩放(通常我们通过T1实现缩放,所以这里通常为1)
所谓的仿射变换其实就是通过T1、T2进行变换。
所谓的投影变换就是在仿射变换上多用到了T3。
这里我们忽略T4。
【3】图像的仿射变换
为了能够直观地了解参数对于变换的各种影响,我编写了一个程序,通过滑动条来控制参数,同时显示参数改变后的图像。
这里的参数我都是设的正的,你把滑动条从正最大移到0就相当于是逆操作了。
代码如下:
//为窗口标题定义的宏
using
namespace
cv;
using
namespace
std;
//*--------------------------【全局变量声明】-------------------------------------*/
//*--------------------------【T1】-------------------------------------*/
int
g_nValueA
=
100;
int
g_nValueB
=
0;
int
g_nValueC
=
0;
int
g_nValueD
=
100;
//*--------------------------【T2】-------------------------------------*/
int
g_nValueL
=
50;
int
g_nValueM
=
50;
//*--------------------------【T3】-------------------------------------*/
int
g_nValueP
=
0;
int
g_nValueQ
=
0;
//*--------------------------【T4】-------------------------------------*/
int
I_max
=
400;
int
g_nValueS
=
100;
int
theta
=
0;
int
change_switch
=
0;
int
center_x
=
I_max
/
2;
int
center_y
=
I_max
/
2;
Mat
g_srcImage,
g_dstImage;
void
on_change(
int,
void
*);
//回调函数
int
main()
{
SetConsoleTextAttribute(
GetStdHandle(
STD_OUTPUT_HANDLE),
FOREGROUND_INTENSITY
|
FOREGROUND_GREEN);
//字体为绿色
//原图,仿射变换后的图,旋转变换后的图
g_srcImage
=
Mat::zeros(
I_max,
I_max,
CV_8UC1);
g_dstImage
=
Mat::zeros(
I_max,
I_max,
CV_8UC1);
for (
int
i
=
I_max
/
2;
i
<
I_max
/
2
+
50;
i
++)
//行循环
{
for (
int
j
=
I_max
/
2;
j
<
I_max
/
2
+
50;
j
++)
//列循环
{
//-------【开始处理每个像素】---------------
g_srcImage.
at
<
uchar
>(
i,
j)
=
255;
//-------【处理结束】---------------
}
}
namedWindow(
WINDOW_NAME,
WINDOW_NORMAL);
//WINDOW_NORMAL允许用户自由伸缩窗口
imshow(
"原图",
g_srcImage);
//【4】创建滑动条来控制阈值
createTrackbar(
"a",
WINDOW_NAME,
&
g_nValueA,
150,
on_change);
createTrackbar(
"b",
WINDOW_NAME,
&
g_nValueB,
150,
on_change);
createTrackbar(
"c",
WINDOW_NAME,
&
g_nValueC,
150,
on_change);
createTrackbar(
"d",
WINDOW_NAME,
&
g_nValueD,
150,
on_change);
createTrackbar(
"l",
WINDOW_NAME,
&
g_nValueL,
150,
on_change);
createTrackbar(
"m",
WINDOW_NAME,
&
g_nValueM,
150,
on_change);
createTrackbar(
"p",
WINDOW_NAME,
&
g_nValueP,
150,
on_change);
createTrackbar(
"q",
WINDOW_NAME,
&
g_nValueQ,
150,
on_change);
createTrackbar(
"s",
WINDOW_NAME,
&
g_nValueS,
150,
on_change);
createTrackbar(
"角度",
WINDOW_NAME,
&
theta,
360,
on_change);
createTrackbar(
"switch",
WINDOW_NAME,
&
change_switch,
1,
on_change);
on_change(
0,
0);
//初始化回调函数
//【7】轮询等待用户按键,如果ESC键按下则退出程序
while (
1)
{
if (
waitKey(
10)
==
27)
break;
//按下Esc 退出
}
return
0;
}
//*--------------------------【on_Threshold 函数】-------------------------------------*/
void
on_change(
int,
void
*)
{
g_dstImage
=
Mat::zeros(
I_max,
I_max,
CV_8UC1);
float
a
=
g_nValueA
*
0.01;
float
b
=
g_nValueB
*
0.01;
float
c
=
g_nValueC
*
0.01;
float
d
=
g_nValueD
*
0.01;
int
l
=
g_nValueL;
int
m
=
g_nValueM;
float
p
=
g_nValueP
*
0.0005;
float
q
=
g_nValueQ
*
0.0005;
float
s
=
g_nValueS
*
0.01;
int
x_change,
y_change;
//将参数进行处理
//计算坐标
if (
change_switch
==
0)
{
for (
int
x
=
I_max
/
2;
x
<
I_max
/
2
+
50;
x
++)
//行循环
{
for (
int
y
=
I_max
/
2;
y
<
I_max
/
2
+
50;
y
++)
//列循环
{
x_change
= (
a
*
x
+
c
*
y
+
l)
/ (
p
*
x
+
q
*
y
+
1);
y_change
= (
b
*
x
+
d
*
y
+
m)
/ (
p
*
x
+
q
*
y
+
1);
//限幅
if (
x_change
>=
I_max)
x_change
=
I_max
-
1;
else
if (
x_change
<=
0)
x_change
=
0;
else
{
}
if (
y_change
>=
I_max)
y_change
=
I_max
-
1;
else
if (
y_change
<=
0)
y_change
=
0;
else
{
}
g_dstImage.
at
<
uchar
>(
x_change,
y_change)
=
255;
}
}
}
else
{
a
=
cos(
theta);
b
=
sin(
theta);
c
=
-
1
*
sin(
theta);
d
=
cos(
theta);
for (
int
x
=
I_max
/
2;
x
<
I_max
/
2
+
50;
x
++)
//行循环
{
for (
int
y
=
I_max
/
2;
y
<
I_max
/
2
+
50;
y
++)
//列循环
{
x_change
= (
x
-
center_x)
*
cos(
theta)
- (
y
-
center_y)
*
sin(
theta)
+
center_x;
y_change
= (
x
-
center_x)
*
sin(
theta)
+ (
y
-
center_y)
*
cos(
theta)
+
center_y;
//限幅
if (
x_change
>=
I_max)
x_change
=
I_max
-
1;
else
if (
x_change
<=
0)
x_change
=
0;
else
{
}
if (
y_change
>=
I_max)
y_change
=
I_max
-
1;
else
if (
y_change
<=
0)
y_change
=
0;
else
{
}
g_dstImage.
at
<
uchar
>(
x_change,
y_change)
=
255;
}
}
}
//更新效果图
imshow(
"效果图",
g_dstImage);
}
- 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.
- 110.
- 111.
- 112.
- 113.
- 114.
- 115.
- 116.
- 117.
- 118.
- 119.
- 120.
- 121.
- 122.
- 123.
- 124.
- 125.
- 126.
- 127.
- 128.
- 129.
- 130.
- 131.
- 132.
- 133.
- 134.
- 135.
- 136.
- 137.
- 138.
- 139.
- 140.
- 141.
- 142.
- 143.
- 144.
- 145.
- 146.
- 147.
原图如下:
接下来看具体变换:
1、平移变换

效果展示:
2、比例缩放


效果展示:
3、旋转


这里的旋转是以原点为中心点的,当我们以(center_x,center_y)为中点,则需要修改公式为:
X’=(X-center_x)*cos(theta)-(Y-center_y)*sin(theta) + center_x;
Y’=(X-center_x)*sin(theta)+(Y-center_y)*cos(theta) +center_y ;
效果展示:
4、对称变换(不做展示)
1、关于X轴变换

2、关于Y轴变换

3、关于直线Y=X变换

4、关于直线Y=-X变换

5、错切变换


效果展示:
6、复合变换

【4】图像的投影变换

点共线特性:原本是一条直线,变换后还是一条直线
效果展示:
【5】应用

由原理可知,变换的本质就是通过对应点组的坐标来求解方程。一个变换是否理想,在公式不做调整的情况下就要看对应点的选择。
这里我们一般选择图像的特征点。这些知识会在以后展开讲,哲理不做过多扩展。(像上面的二维码变换,我们选取的特征点考虑那三个定位点,当然还要再找一个特征点。以后掌握了这方面知识再补充。)
【6】Opencv自带的变换函数:
Opencv中仿射变换的函数:warpAffine()函数
公式依据:
C++: void warpAffine (InputArray src, OutputArray dst, InputArray M, Size
dsize, int flags=INTER_LINEAR,intborderMode=BORDER_CONSTANT, const
Scalar& borderValue=Scalar() )
第一个参数,InputArray 类型的src,输入图像,即源图像,填Mat类的对
象即可。
第二个参数,OutputArray 类型的dst, 函数调用后的运算结果存在这里,
需和源图片有一样的尺寸和类型。
第三个参数,InputArray 类型的M,2x3 的变换矩阵。
第四个参数,Size 类型的dsize,表示输出图像的尺寸。
第五个参数,int 类型的flags, 插值方法的标识符。此参数有默认值
INTER_ LINEAR(线性插值),可选的插值方式如下图所示。
第六个参数,int类型的borderMode,边界像素模式,默认值为
BORDER CONSTANT。
第七个参数,const Scalar&类型的borderValue, 在恒定的边界情况下取的
值,默认值为Scalar(), 即0。
Opencv中计算二维旋转变换矩阵: getRotationMatrix2D()函数
C++: Mat getRotationMatrix2D (Point2fcenter, double angle, double scale)
第一个参数,Point2f 类型的center,表示源图像的旋转中心。
第二个参数,double类型的angle,旋转角度。角度为正值表示向逆时针旋转(坐标原点是左上角)。
第三个参数,double 类型的scale,缩放系数。
int
main()
{
SetConsoleTextAttribute(
GetStdHandle(
STD_OUTPUT_HANDLE),
FOREGROUND_INTENSITY
|
FOREGROUND_GREEN);
//字体为绿色
//【1】参数准备
//定义两组点,代表两个三角形
Point2f
srcTriangle[
3];
Point2f
dstTriangle[
3];
//定义Mat变量(变换矩阵)
Mat
rotMat(
2,
3,
CV_32FC1);
//CV_32FC1代表多少?
Mat
warpMat(
2,
3,
CV_32FC1);
//CV_32FC1代表多少?
Mat
srcImage,
dstImage_warp,
dstImage_warp_roate;
//原图,仿射变换后的图,旋转变换后的图
srcImage
=
imread(
"D:\\opencv_picture_test\\形态学操作\\黑白.jpg");
//判断图像是否加载成功
if (
srcImage.
empty())
{
cout
<<
"图像加载失败!"
<<
endl;
return
-
1;
}
else
cout
<<
"图像加载成功!"
<<
endl
<<
endl;
dstImage_warp
=
Mat::zeros(
srcImage.
rows,
srcImage.
cols,
srcImage.
type());
//转换图和原图像类型一样大小一样
//【2】利用三组对应点来计算参数
srcTriangle[
0]
=
Point2f(
0,
0);
//这些选择自己决定
srcTriangle[
1]
=
Point2f(
0,
0);
srcTriangle[
2]
=
Point2f(
0,
0);
dstTriangle[
0]
=
Point2f(
0,
0);
dstTriangle[
1]
=
Point2f(
0,
0);
dstTriangle[
2]
=
Point2f(
0,
0);
//【3】求得仿射变换参数
warpMat
=
getAffineTransform(
srcTriangle,
dstTriangle);
//利用对应点求得6个参数
//【4】对原图进行仿射变换
warpAffine(
srcImage,
dstImage_warp,
warpMat,
dstImage_warp.
size());
//【5】获取旋转信息
Point
center
=
Point(
dstImage_warp.
cols
/
2,
dstImage_warp.
rows
/
2);
//中心点
double
angle
=
-
30.0;
//顺时针30度
double
scale
=
0.8;
//【6】通过上面的旋转细节信息求得旋转矩阵
rotMat
=
getRotationMatrix2D(
center,
angle,
scale);
//【7】对缩放后的图像进行旋转
warpAffine(
dstImage_warp,
dstImage_warp_roate,
rotMat,
dstImage_warp.
size());
//【8】显示结果
namedWindow(
"原图像",
WINDOW_NORMAL);
//定义窗口显示属性
imshow(
"原图像",
srcImage);
namedWindow(
"缩放图",
WINDOW_NORMAL);
//定义窗口显示属性
imshow(
"缩放图",
dstImage_warp);
namedWindow(
"缩放旋转图",
WINDOW_NORMAL);
//定义窗口显示属性
imshow(
"缩放旋转图",
dstImage_warp_roate);
//创建三个窗口
waitKey(
0);
return
0;
}
- 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.
效果:
PPT是盗用的我们李竹老师的,嘿嘿。
边栏推荐
- Low code solution - a low code solution for digital after-sales service covering the whole process of work order, maintenance and Finance
- ScheduledThreadPoolExecutor源码解读(二)
- Shardingsphere-proxy-5.0.0 establish MySQL read / write separation connection (6)
- 云平台kvm迁移本地虚拟机记录
- The interviewer asked: can you simulate the new operator of JS
- geojson 格式说明(格式详解)
- From how to use to how to implement a promise
- 图灵机启动顺序
- KVM related
- [Yocto RM] 2 - Yocto Project Terms
猜你喜欢

File transfer protocol --ftp

Cvpr22 collected papers | hierarchical residual multi granularity classification network based on label relation tree

Jenkins - Copy Artifact 插件 Build 之间数据共享传递

Teach you how to realize pynq-z2 bar code recognition

Adding text labels to cesium polygons the problem of polygon center point offset is solved

CVPR22收录论文|基于标签关系树的层级残差多粒度分类网络

Jenkins - Groovy Postbuild 插件丰富 Build History 信息

Mysql大合集,你要内容的这里全都有

Skills in schematic merging

设计电商秒杀系统
随机推荐
How to use data-driven "customer lifecycle management" to improve lead conversion rate and customer satisfaction?
Based on am335x development board arm cortex-a8 -- acontis EtherCAT master station development case
Cesium Click to obtain longitude and latitude (2D coordinates)
SQL 注入绕过(四)
STM32的通用定时器与中断
How to handle computer security certificate errors
JS 随机数(随机数 小数)
The interviewer asked: can you simulate the new operator of JS
JS 数组随机取值(随机数组取值)
Leetcode topic [array] -228- summary interval
SQL injection bypass (V)
LeetCode - Easy - 197
To understand what is synchronous, asynchronous, serial, parallel, concurrent, process, thread, and coroutine
JS实现滑动拼图验证
SQL 注入绕过(五)
SQL 注入绕过(三)
Cloud platform KVM migration local virtual machine records
Jenkins - access the Jenkins user-defined parameter variable, and handle the variable value containing spaces
启牛开户安全吗?怎么线上开户?
毕业总结


