opencv 图像旋转 边缘填充 插值方法

目录

一、单点旋转

二、图像旋转

三、边缘填充

1、边界复制(BORDER_REPLICATE)

2、边界反射(BORDER_REFLECT)

3、边界反射101(BORDER_REFLECT_101)

4、边界常数(BORDER_CONSTANT, borderValue= )

5 、边界包裹(BORDER_WRAP)

四、插值方法(cv2.warpAffine(参数1, 参数2, 参数3, flags= ))

0、坐标映射

1、最近邻插值(cv2.INTER_NEAREST )

2、双线性插值(cv2.INTER_LINEAR )

3、像素区域插值(cv2.INTER_AREA )

4、双三次插值(cv2.INTER_CUBIC)

5、Lanczos插值(cv2.INTER_LANCZOS4 )

小结


图像旋转是指图像以某一点为旋转中心,将图像中的所有像素点都围绕该点旋转一定的角度,并且旋转后的像素点组成的图像与原图像相同,在了解图像旋转之前首先要明白单点旋转。

一、单点旋转

以最简单的情况举例,令旋转中心为坐标系中心O(0,0),假设有一点P0(x0, y0)离旋转中心O的距离为r,OP0与坐标轴x轴的夹角为alpha,P0绕O点顺时针旋转theta角后对应的点为P(x,y),如下图所示:

                      

则旋转后的坐标可以由以下公式求出:

                                        

对图像来说,就是图像中的单个像素坐标进行几何变换(旋转),仅仅是计算某个点旋转后的位置,不涉及像素值。所以图像旋转就是将图像中的每个像素坐标都进行旋转,从而得到旋转后的新图像。

二、图像旋转

单点旋转除了绕原点旋转也可以绕任意点旋转,同样的图像旋转也可以绕任意点旋转,这个时候就需要将旋转中心点平移到原点,将其化为绕原点旋转,再通过旋转矩阵求出新坐标点,然后将得到的新坐标点移回原来的位置,也就是说,在以任意点为旋转中心时,除了要进行旋转之外,还要进行平移操作,此时进行计算的矩阵就是包含平移+旋转的仿射矩阵。

用opencv解决的话需要两步:

第一步先生成旋转矩阵M,API为 cv2.getRotationMatrix2D(参数1,参数2,参数3),参数1为旋转中心的坐标,如以图像中心旋转,则坐标为(h/2, w/2),参数2为逆时针旋转的角度,参数3为缩放倍数;

第二步将矩阵应用到图像,API为 cv2.warpAffine(参数1,参数2,参数3),参数1为需要旋转的图像,参数2为旋转矩阵M,参数3为输出图像的尺寸,注意一定要按顺序传入(宽度w,高度h)

具体实现代码如下:

def test1():
    '''图像旋转'''
    img1 = cv2.imread("./src/mylogo.png")
    h,w,c = img1.shape #获得图像尺寸
    M = cv2.getRotationMatrix2D((h/2,w/2),45,0.5) #绕图像中心逆时针旋转45度且缩放0.5倍,生成旋转矩阵M
    img1_rotation = cv2.warpAffine(img1, M, (w, h)) #应用旋转矩阵
    cv2.imshow("img1", img1)
    cv2.imshow("img1_rotation", img1_rotation)
    cv2.waitKey(0)

运行结果图示:

可以看到,左图在逆时针旋转45度之后原图的右下角顶点在右图中已经看不到了,同时,右图空出来的区域其实是什么都没有的,因此我们需要对其进行填充,右图就是对空出来的区域进行了像素值为(0,0,0)也就是黑色像素值的填充,这就是一种边缘填充,下面介绍五种常用的边缘填充方法。

三、边缘填充

边缘填充是可选择的方式,使用方法是 borderMode=方式名

如下图所示,左图绕图像中心逆时针旋转45度缩放0.5倍后变成了右图,用以下五种方式解决黑色部分(虽然用黑色填充本身也是一种解决方式):

1、边界复制(BORDER_REPLICATE)

边界复制会将边界处的像素值进行复制,然后作为边界填充的像素值,用数组表示的话,可以看到四周的像素值都一样:

                

具体代码:

def test2():
    '''边缘填充'''
    img1 = cv2.imread("./src/9.jpg")
    h,w,c = img1.shape
    M = cv2.getRotationMatrix2D((h/2,w/2),45,0.5) #生成旋转矩阵
    img2 = cv2.warpAffine(img1, M, (w,h),borderMode=cv2.BORDER_REPLICATE) #边界复制
   
    cv2.imshow("img1", img1)
    cv2.imshow("img2", img2)
    cv2.waitKey(0)

运行结果图示:

2、边界反射(BORDER_REFLECT)

如下图数组所示,会根据原图的边缘进行反射,相当于在边界放了面镜子:

主要代码(其余都跟上面一样):

# img2 = cv2.warpAffine(img1, M, (w,h),borderMode=cv2.BORDER_REPLICATE) #边界复制
  img2 = cv2.warpAffine(img1, M, (w,h),borderMode=cv2.BORDER_REFLECT) #边界反射

运行结果图示:

3、边界反射101(BORDER_REFLECT_101)

与边界反射不同的是不再反射边缘像素的像素点,如下图所示:

                 

主要代码:

#img2 = cv2.warpAffine(img1, M, (w,h),borderMode=cv2.BORDER_REFLECT) #边界反射
img2 = cv2.warpAffine(img1, M, (w,h),borderMode=cv2.BORDER_REFLECT_101) #边界反射101

运行结果图示:

(我自己感觉跟边界反射没啥区别,可能是这张图处理后不明显)

4、边界常数(BORDER_CONSTANT, borderValue= 

当选择边界常数时,要指定常数值是多少,用 borderValue指定,默认的填充常数值为0,即用黑色填充,当然也可以用其他颜色,主要代码如下,我这里用个蓝色填充:

#img2 = cv2.warpAffine(img1, M, (w,h),borderMode=cv2.BORDER_REFLECT_101) #边界反射101
img2 = cv2.warpAffine(img1, M, (w,h),borderMode=cv2.BORDER_CONSTANT,borderValue=(127,0,0)) #边界常数

运行结果图示:

5 、边界包裹(BORDER_WRAP)

就是对边界的一种平铺,主要代码如下:

#img2 = cv2.warpAffine(img1, M, (w,h),borderMode=cv2.BORDER_CONSTANT,borderValue=(127,0,0)) #边界常数
img2 = cv2.warpAffine(img1, M, (w,h),borderMode=cv2.BORDER_WRAP) #边界包裹

运行结果图示:

边缘填充方式就介绍到这里。现在旋转后的图片的边缘我们是可以处理了,但是还有一个问题,用旋转矩阵计算旋转后的坐标点时会出现小数(因为三角函数有小数),但图像像素位置必须是整数,例如原图某个点旋转后落在(100.3,64.9),但像素坐标只能是(100,50)或(101,51)这样的整数,直接取整可能会导致边缘不平滑以及某些位置没有对应像素值从而出现空洞等问题,所以这时就需要插值来估算小数坐标的合理像素值。

四、插值方法(cv2.warpAffine(参数1, 参数2, 参数3, flags= ))

API对应参数:参数1是原图,参数2是旋转矩阵M,参数3是输出图像的尺寸,同样一定要按顺序传入(宽度w,高度h),flags的可选参数是不同的插值方法

图像插值算法是为了解决图像缩放或者旋转等操作时,由于像素之间的间隔不一致而导致的信息丢失和图像质量下降的问题。当我们对图像进行缩放或旋转等操作时,需要在新的像素位置上计算出对应的像素值,而插值算法的作用就是根据已知的像素值来推测未知位置的像素值,本质就是如何把原图的像素值,合理地填充到新图像的对应位置上。在应用插值方法之前,我们首先要知道一个东西叫 “坐标映射”

0、坐标映射

这个“坐标映射”指的是将新图像的某个像素点的坐标映射在原图上,计算公式为:

                                     

src是原图,dst是目标图像,即原图的x坐标=目标图的x坐标*原图与目标图的宽度比,原图的y坐标=目标图的y坐标*原图与目标图的高度比,举个实际例子:

现在有一张4*4的图,将它缩放为8*8,则原图与目标图的宽度比=高度比=4/8=0.5,假设计算目标图中坐标为(dstX=3,dstY=3)的像素点在原图中的坐标是多少以确定它的颜色,则

srcX = 3*0.5=1.5, srcY = 3*0.5=1.5,所以此时它在原图中的坐标是(1.5,1.5),但是我们知道像素坐标只能是整数,那么这时插值方法就派上用场了,它就是来解决坐标点包含小数时的情况,不同的插值方法对(srcX, srcY)的处理方式不同,有些是直接计算像素值,下面将介绍五种常见的插值算法。

1、最近邻插值(cv2.INTER_NEAREST )

最近邻插值的解决办法是四舍五入,确保选取的整数坐标距离目标点最近,比如上面例子中原图坐标是(1.5,1.5),用最近邻插值的话就会取(2,2)的像素值填入目标图中(3,3)的位置,它的优点是计算速度快,但缺点是精确度不是特别高且容易使边缘产生锯齿,示例代码如下:

def test3():
    '''插值方法'''
    img1 = cv2.imread("./src/9.jpg")
    h,w,c = img1.shape
    M = cv2.getRotationMatrix2D((h/2, w/2), 45, 0.5) #旋转矩阵
    img2 = cv2.warpAffine(img1, M, (w,h), flags=cv2.INTER_NEAREST) #最近邻插值

    cv2.imshow("img1", img1)
    cv2.imshow("img2", img2)
    cv2.waitKey(0)

插值部分的图示就不放了,差别其实不是特别大,可以自己动手实践一下

2、双线性插值(cv2.INTER_LINEAR )

它的解决方式就不是直接处理坐标点,而是找(srcX, srcY)周围的四个像素点,在水平和垂直方向上分别按距离进行加权求和直接求出像素值,比如下面这张图:

P点是我们已经求得的目标图的某个像素点在原图的坐标,Q11,Q21,Q12,Q22分别是它周围的四个点,此时就用到双线性插值,先根据Q11、Q21得到R1的插值(即像素值),根据Q12、Q22得到R2的插值,然后根据R1、R2得到P点的插值即可,计算公式如下:

首先计算R1,R2的插值(在哪个方向上先进行插值结果都是一样的):

          

然后根据R1和R2计算得到P点的插值:

                     

注意如果是彩色图的话,每个颜色通道需要进行独立插值计算,即此时:

Q11 = (B11, G11, R11),Q21 = (B21, G21, R21),Q12 = (B12, G12, R12),Q22 = (B22, G22, R22),则先计算P点的蓝色通道的插值:

                       

绿色和红色通道同理,最终合并三个通道数得到 P(BP, GP, GP)。但是在opencv中会自动处理多通道插值,这里只是解释数学原理,主要代码如下(其他跟上面的相同):

img2 = cv2.warpAffine(img1, M, (w,h), flags=cv2.INTER_LINEAR) #双线性插值

3、像素区域插值(cv2.INTER_AREA )

像素区域插值主要分两种情况,缩小图像和放大图像的工作原理并不相同。当进行缩小图像时,会用均值滤波器对核内的像素值取平均值作为中心点的像素;当进行放大图像时,如果放大的比例是整数倍,那么其工作原理与最近邻插值类似,如果放大的比例不是整数倍,那么就会调用双线性插值进行放大。主要代码如下:

img2 = cv2.warpAffine(img1, M, (w,h), flags=cv2.INTER_AREA) #像素区域插值

4、双三次插值(cv2.INTER_CUBIC)

与双线性插值法原理相同,不同的是双三次插值法需要原图像中近邻的16个点来加权,且使用的BiCubic这个函数来计算权重,如下图所示:

        

如何使用BiCubic函数计算权重的这里就不细说,主要代码如下:

img2 = cv2.warpAffine(img1,M,(2*w,2*h),flags=cv2.INTER_CUBIC) #双三次插值

5、Lanczos插值(cv2.INTER_LANCZOS4 )

Lanczos插值方法与双三次插值的思想是一样的,不同的就是其需要的原图像周围的像素点的范围变成了8*8,并且不再使用BiCubic函数来计算权重,而是换了一个更复杂的公式计算权重,这里也不过多介绍,主要代码如下:

img2 = cv2.warpAffine(img1,M,(2*w,2*h),flags=cv2.INTER_LANCZOS4) #Lanczos插值

小结

最近邻插值的计算速度最快,但是可能会导致图像出现锯齿状边缘和失真,效果较差;双线性插值的计算速度慢一点,但效果有了大幅度的提高,适用于大多数场景;双三次插值、Lanczos插值的计算速度都很慢,但是效果都很好,适合高质量需求。

在OpenCV中,关于插值方法默认选择的都是双线性插值,且一般情况下双线性插值已经能满足大部分需求。

这一部分就到此结束,下一篇是图像的镜像旋转、缩放、矫正以及重点的噪点消除。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值