1 图像翻转(图像镜像旋转)
在OpenCV中,图片的镜像旋转是以图像的中心为原点进行镜像翻转的。
cv2.flip(img,flipcode)
参数
img: 要翻转的图像
flipcode: 指定翻转类型的标志
flipcode=0: 垂直翻转,图片像素点沿x轴翻转
flipcode>0: 水平翻转,图片像素点沿y轴翻转
flipcode<0: 水平垂直翻转,水平翻转和垂直翻转的结合
参数是1,表示水平翻转
参数是0,表示垂直翻转
参数是-1,表示水平垂直翻转
2 图像仿射变换
仿射变换(Affine Transformation)是一种线性变换,保持了点之间的相对距离不变。
仿射变换的基本性质
保持直线
保持平行
比例不变性
不保持角度和长度
常见的仿射变换类型
旋转:绕着某个点或轴旋转一定角度。
平移:仅改变物体的位置,不改变其形状和大小。
缩放:改变物体的大小。
剪切:使物体发生倾斜变形。
仿射变换的基本原理
线性变换
二维空间中,图像点坐标为(x,y),仿射变换的目标是将这些点映射到新的位置 (x', y')。
为了实现这种映射,通常会使用一个矩阵乘法的形式:
(类似于y=kx+b)
a,b,c,d 是线性变换部分的系数,控制旋转、缩放和剪切。
t_x,t_y 是平移部分的系数,控制图像在平面上的移动。
输入点的坐标被扩展为齐次坐标形式[x,y,1],以便能够同时处理线性变换和平移
cv2.warpAffine()函数
仿射变换函数
cv2.warpAffine(img,M,dsize)
img:输入图像。
M:2x3的变换矩阵,类型为np.float32
。
dsize:输出图像的尺寸,形式为(width,height)
。
2.1 图像旋转
旋转图像可以将图像绕着某个点旋转一定的角度。
cv2.getRotationMatrix2D()函数
获取旋转矩阵
cv2.getRotationMatrix2D(center,angle,scale)
center:旋转中心点的坐标,格式为(x,y)
。
angle:旋转角度,单位为度,正值表示逆时针旋转负值表示顺时针旋转。
scale:缩放比例,若设为1,则不缩放。
返回值:M,2x3的旋转矩阵。
原图
旋转后图像
2.2 图像平移
移操作可以将图像中的每个点沿着某个方向移动一定的距离。
假设我们有一个点 P(x,y),希望将其沿x轴方向平移t_x*个单位,沿y轴方向平移t_y个单位到新的位置P′(x′,y′),那么平移公式如下:
x′=x+tx
y′=y+ty
对于图像的平移,矩阵的前两列
[a, b]
和[d, e]
通常设置为[1, 0]
和[0, 1]
,表示没有旋转或缩放。第三列[c, f]
则定义了平移的方向和距离:
c
是 x 轴方向的平移距离(水平方向)。
f
是 y 轴方向的平移距离(垂直方向)。
M = np.float32([[1,0,100],[0,1,50]])
是一个平移矩阵,它的作用是将图像向右平移 100 像素,向下平移 50 像素。通过cv.warpAffine
函数,这个矩阵被应用到图像上,生成平移后的结果。
2.3 图像缩放
缩放操作可以改变图片的大小。
假设要把图像的宽高分别缩放为0.5和0.8,那么对应的缩放因子sx=0.5,sy=0.8。
点P(x,y)对应到新的位置P'(x',y'),缩放公式为:
x′=s_x*x
y′=s_y*y
对于图像的缩放,矩阵的前两列
[a, b]
和[d, e]
通常设置为[s_x, 0]
和[0, s_y]
,其中:
s_x
是 x 轴方向的缩放因子(水平方向)。
s_y
是 y 轴方向的缩放因子(垂直方向)。
M = np.float32([[0.5,0,0],[0,0.8,0]])
是一个缩放矩阵,它的作用是将图像在 x 轴方向缩小为原来的 50%,在 y 轴方向缩小为原来的 80%。通过cv.warpAffine
函数,这个矩阵被应用到图像上,生成缩放后的结果。
2.4 图像剪切
剪切操作可以改变图形的形状,以便其在某个方向上倾斜,它将对象的形状改变为斜边平行四边形,而不改变其面积。
想象我们手上有一张矩形纸片,如果你固定纸片的一边,并沿着另一边施加一个平行于该边的力,这张纸片就会变形为一个平行四边形。这就是剪切变换的一个直观解释。
对于二维空间中的点P(x,y),对他进行剪切变换:
沿x轴剪切:x'=x+sh_y*y y'=y
沿y轴剪切:x'=x y'=sh_x*x+y
当需要同时沿两个方向进行剪切时,x'=x+sh_y*y , y'=sh_x*x+y
x方向剪切
M_x_shear = np.float32([[1, sh_y, 0],
[0, 1, 0]])
沿x轴剪切:
新的 x 坐标:
x' = x + sh_y * y
新的 y 坐标:
y' = y
y方向剪切
M_y_shear = np.float32([[1, 0, 0],
[sh_x, 1, 0]])
沿y轴剪切:
新的 x 坐标:
x' = x
新的 y 坐标:
y' = sh_x * x + y
xy方向剪切
M_xy_shear = np.float32([[1, sh_xy, 0],
[sh_xy, 1, 0]])
同时沿x和y轴剪切:
新的 x 坐标:
x' = x + sh_xy * y
新的 y 坐标:
y' = sh_xy * x + y
3 插值方法
在图像处理和计算机图形学中,插值(Interpolation)是一种通过已知数据点之间的推断或估计来获取新数据点的方法。它在图像处理中常用于处理图像的放大、缩小、旋转、变形等操作,以及处理图像中的像素值。
图像插值算法是为了解决图像缩放或者旋转等操作时,由于像素之间的间隔不一致而导致的信息丢失和图像质量下降的问题。当我们对图像进行缩放或旋转等操作时,需要在新的像素位置上计算出对应的像素值,而插值算法的作用就是根据已知的像素值来推测未知位置的像素值。
3.1 最近邻插值
CV2.INTER_NEAREST
new_img1=cv.warpAffine(img,M,(w,h),flags=cv.INTER_NEAREST)
首先给出目标点与原图像点之间坐标的计算公式:
dstX:目标图像中某点的x坐标,
dstY:目标图像中某点的y坐标,
srcWidth:原图的宽度,
dstWidth:目标图像的宽度;
srcHeight:原图的高度,
dstHeight:目标图像的高度。
而srcX和srcY:目标图像中的某点对应的原图中的点的x和y的坐标。
通俗的讲,该公式就是让目标图像中的每个像素值都能找到对应的原图中的像素值,这样才能根据不同的插值方法来获取新的像素值。根据该公式,我们就可以得到每一个目标点所对应的原图像的点,比如一个2*2的图像放大到4*4,如下图所示,其中红色的为每个像素点的坐标,黑色的则表示该像素点的像素值。
那么根据公式我们就可以计算出放大后的图像(0,0)点对应的原图像中的坐标为:
也就是原图中的(0,0)点,而最近邻插值的原则是:目标像素点的像素值与经过该公式计算出来的对应的像素点的像素值相同,如出现小数部分需要进行取整。那么放大后图像的(0,0)坐标处的像素值就是原图像中(0,0)坐标处的像素值,也就是10。接下来就是计算放大后图像(1,0)点对应的原图像的坐标,还是带入公式:
也就是原图中的(0.5,0)点,因此需要对计算出来的坐标值进行取整,取整后的结果为(0,0),也就是说放大后的图像中的(1,0)坐标处对应的像素值就是原图中(0,0)坐标处的像素值,其他像素点计算规则与此相同。
思考:
一张图的第一行像素点的值分别为: 10 10 20 30 40 放大一倍后 新图像的第一行的第3个像素点的值是多少?
点坐标:(2,0)
(1,0)==>10
4x2的图像放大到8x4,放大一倍
3.2 双线性插值
CV2.INTER_LINEAR
双线性插值是一种图像缩放、旋转或平移时进行像素值估计的插值方法。当需要对图像进行变换时,特别是尺寸变化时,原始图像的某些像素坐标可能不再是新图像中的整数位置,这时就需要使用插值算法来确定这些非整数坐标的像素值。
双线性插值的工作原理是这样的:
假设要查找目标图像上坐标为
(x', y')
的像素值,在原图像上对应的浮点坐标为(x, y)
。在原图像上找到四个最接近
(x, y)
的像素点,通常记作P00(x0, y0)
,P01(x0, y1)
,P10(x1, y0)
,P11(x1, y1)
,它们构成一个2x2的邻域矩阵。分别在水平方向和垂直方向上做线性插值:
水平方向:根据 x
与 x0
和 x1
的关系计算出 P00
和 P10
、 P01
和 P11
之间的插值结果。
垂直方向:将第一步的结果与 y
与 y0
和 y1
的关系结合,再在垂直方向上做一次线性插值。
综合上述两次线性插值的结果,得到最终位于
(x', y')
处的新像素的估计值。
总结: 4乘4的图像 变成6乘6的图像 那么目标图像的(3,3)点的像素是原图中(1.8333,1.8333)的像素颜色,但是坐标必须是整数 它周围有四个像素点 该取谁呢? 按照到各自的距离比例 来分配颜色值
首先要了解线性插值,而双线性插值本质上就是在两个方向上做线性插值。还是给出目标点与原图像中点的计算公式
比如我们根据上述公式计算出了新图像中的某点所对应的原图像的点P,其周围的点分别为Q12、Q22、Q11、Q21, 要插值的P点不在其周围点的连线上,这时候就需要用到双线性插值了。首先延申P点得到P和Q11、Q21的交点R1与P和Q12、Q22的交点R2,如下图所示:
然后根据Q11、Q21得到R1的插值,根据Q12、Q22得到R2的插值,然后根据R1、R2得到P的插值即可,这就是双线性插值。以下是计算过程:
首先计算R1和R2的插值:
然后根据R1和R2计算P的插值:
这样就得到了P点的插值。注意此处如果先在y方向插值、再在x方向插值,其结果与按照上述顺序双线性插值的结果是一样的。
双线性插值的对应关系看似比较清晰,但还是有2个问题。首先是根据坐标系的不同,产生的结果不同,这张图是左上角为坐标系原点的情况,我们可以发现最左边x=0的点都会有概率直接复制到目标图像中(至少原点肯定是这样),而且就算不和原图像中的点重合,也相当于进行了1次单线性插值(带入到权重公式中会发现结果)。
下面这张图是右上角为坐标系原点的情况,我们可以发现最右面的点都会有概率直接复制到目标图像中(至少原点肯定是这样),而且就算不和原图像中的点重合,也相当于进行了1次单线性插值。那么当我们采用不同的坐标系时产生的结果是不一样的,而且无论我们采用什么坐标系,最左侧和最右侧(最上侧和最下侧)的点是“不公平的”,这是第一个问题。
第二个问题时整体的图像相对位置会发生变化。如下图所示,左侧是原图像(3,3),右侧是目标图像(5,5),原图像的几何中心点是(1,1),目标图像的几何中心点是(2,2),根据对应关系,目标图像的几何中心点对应的原图像的位置是(1.2,1.2),那么问题来了,目标图像的原点(0,0)和原始图像的原点是重合的,但是目标图像的几何中心点相对于原始图像的几何中心点偏右下,那么整体图像的位置会发生偏移,所以参与计算的点相对都往右下偏移会产生相对的位置信息损失。这是第二个问题。
因此,在OpenCV中,为了解决这两个问题,将公式进行了优化,如下所示:
使用该公式计算出原图中的对应坐标后再进行插值计算,就不会出现上面的情况了。
数学公式
假设目标点 (x,y) 的四个相邻像素点为 (x1,y1)、(x2,y1)、(x1,y2) 和 (x2,y2),它们的像素值分别为 f(x1,y1)、f(x2,y1)、f(x1,y2) 和 f(x2,y2)。
水平方向插值:
垂直方向插值:
代码
3.3 像素区域插值
cv2.INTER_AREA
像素区域插值主要分两种情况,缩小图像和放大图像的工作原理并不相同。
当使用像素区域插值方法进行缩小图像时,它就会变成一个均值滤波器(滤波器其实就是一个核,这里只做简单了解,后面实验中会介绍),其工作原理可以理解为对一个区域内的像素值取平均值。
当使用像素区域插值方法进行放大图像时
如果图像放大的比例是整数倍,那么其工作原理与最近邻插值类似;
如果放大的比例不是整数倍,那么就会调用双线性插值进行放大。
其中目标像素点与原图像的像素点的对应公式如下所示:
3.4 双三次插值
cv2.INTER_CUBIC
双三次插值是一种高级的图像插值方法,它通过考虑目标像素点周围的 16 个邻近像素点,并使用三次多项式进行拟合,来预测目标像素点的值。这种方法能够生成更高质量的图像,尤其是在放大具有复杂纹理或精细细节的图像时,效果尤为明显。
原理
双三次插值的数学模型基于三次样条插值,其函数形式为:
BiCubic基函数也就是双三次插值的权重函数,它决定了如何根据距离对周围像素进行加权平均。
假如下图中的P点就是目标图像B在(X,Y)处根据上述公式计算出的对应于原图像A中的位置,P的坐标位置会出现小数部分,所以我们假设P点的坐标为(x+u,y+v),其中x、y表示整数部分,u、v表示小数部分,那么我们就可以得到其周围的最近的16个像素的位置,我们用a(i,j)(i,j=0,1,2,3)来表示,如下图所示。
然后给出BiCubic函数:
a
一般取-0.5或-0.75,用于控制插值函数的形状。
d
代表的是目标像素点与某个像素点之间的相对距离,d_h、d_w
我们要做的就是将上面的16个点相较于p点的位置距离算出来,获取16像素所对应的权重W(d)。然而BiCubic函数是一维的,所以我们需要将像素点的行与列分开计算,比如a00这个点,我们需要将d_x带入BiCubic函数中,计算a00点对于P点的x方向的权重,然后将d_y带入BiCubic函数中,计算a00点对于P点的y方向的权重,其他像素点也是这样的计算过程,最终我们就可以得到P所对应的目标图像B在(X,Y)处的像素值为:
依此办法我们就可以得到目标图像中所有的像素点的像素值。
刚刚我们说拿到了目标点的坐标为 (x+u,y+v) ,其中 x、y 表示整数部分,u、v表示小数部分。那么我们取坐标的整数部分作为参考点,也就是(x,y),小数部分表示目标像素相对于参考点的偏移量。
比如说目标点的坐标为(3.7,2.4),那么我们的参考点就是(3,2)。
目标部分相对于参考点的偏移量:
然后我们才需要来计算相邻像素和目标像素的距离:
比如我们的P点在行上的 四个像素(ii=-1,0,1,2),对应的距离就是:d_h=|ii-b_h|
ii=-1时: d_h=|-1-0.7|=1.7 w_i=-0.0315
ii=0时: d_h=|0-0.7|=0.7 w_i=0.2895
ii=1时: d_h=|1-0.7|=0.3 w_i=0.8155
ii=-1时: d_h=|2-0.7|=1.3 w_i=-0.0735
在列上的 四个像素(jj=-1,0,1,2),对应的距离就是:d_w=|ii-b_w|
jj=-1时: d_w=|-1-0.4|=1.4 w_j=-0.072
jj=0时: d_w=|0-0.4|=0.4 w_j=0.696
jj=1时: d_w=|1-0.4|=0.6 w_j=0.424
jj=-1时: d_w=|2-0.4|=1.6 w_j=-0.048
。
在 OpenCV 中的实现
总结
双三次插值是一种高质量的图像插值方法,适用于需要高分辨率和精细细节的图像处理场景。尽管计算复杂度较高,但其生成的图像质量通常优于双线性插值。
3.5 Lanczos插值
cv2.INTER_LANCZOS4
Lanczos插值方法与双三次插值的思想是一样的,不同的就是其需要的原图像周围的像素点的范围变成了8*8,并且不再使用BiCubic函数来计算权重,而是换了一个公式计算权重。
首先还是目标像素点与原图像的像素点的对应公式如下所示:
下面我们举例说明,假设原图像A大小为m*n,缩放后的目标图像B的大小为M*N。其中A的每一个像素点是已知的,B是未知的,我们想要求出目标图像B中每一个像素点(X,Y)的值,必须先找出像素(X,Y)在原图像A中对应的像素(x,y),再根据原图像A距离像素(x,y)最近的64个像素点作为计算目标图像B(X,Y)处像素值的参数,利用权重函数求出64个像素点的权重,图B像素(x,y)的值就等于64个像素点的加权叠加。
假如下图中的P点就是目标图像B在(X,Y)处根据上述公式计算出的对应于原图像A中的位置,P的坐标位置会出现小数部分,所以我们假设P点的坐标为(x+u,y+v),其中x、y表示整数部分,u、v表示小数部分,那么我们就可以得到其周围的最近的64个像素的位置,我们用a(i,j)(i,j=0,1,2,3,4,5,6,7)来表示,如下图所示。
然后给出权重公式:
其中a通常取2或者3,当a=2时,该算法适用于图像缩小。a=3时,该算法适用于图像放大。
与双三次插值一样,这里也需要将像素点分行和列分别带入计算权重值,其他像素点也是这样的计算过程,最终我们就可以得到P所对应的目标图像B在(X,Y)处的像素值为:
其中[x]、[y]表示对坐标值向下取整,通过该方法就可以计算出新的图像中所有的像素点的像素值。
3.6 小结
最近邻插值的计算速度最快,但是可能会导致图像出现锯齿状边缘和失真,效果较差。双线性插值的计算速度慢一点,但效果有了大幅度的提高,适用于大多数场景。双三次插值、Lanczos插值的计算速度都很慢,但是效果都很好。
在OpenCV中,关于插值方法默认选择的都是双线性插值,且一般情况下双线性插值已经能满足大部分需求。