python-opencv核心库模块core(下)
本章节主要记录opencv核心库模块core的图像旋转,图像拼接,图像仿射变换,图像roi区域提取和图像傅里叶变换等操作。
1 图像旋转
opencv提供了将图像沿着坐标轴旋转的函数flip,dst = flip(src, flipcode) flipcode表示旋转的标志,等于0表示沿着x轴旋转,正数表示沿着y轴旋转,负数表示沿着x和y轴旋转。此外,opencv还提供了一种将图像按照角度旋转的函数 rotate,dst = rotate(src, rotateCode) rotatecode旋转标志,等于ROTATE_90_CLOCKWISE表示顺时针旋转90度,等于ROTATE_180表示顺时针旋转180度,等于ROTATE_90_COUNTERCLOCKWISE表示逆时针旋转90度。
import cv2 import numpy as np #图像旋转 img = cv2.imread('./cat.jpg') flip_x = cv2.flip(img,0) #图片沿着x轴翻转(参数为0) flip_y = cv2.flip(img,2) #图片沿着y轴翻转(参数为正数) flip_xy = cv2.flip(img,-1) #图片沿着x轴和y轴翻转(参数为负数) rotate_90 = cv2.rotate(img,cv2.ROTATE_90_CLOCKWISE) #顺时针旋转90度 rotate_180 = cv2.rotate(img,cv2.ROTATE_180) #顺时针旋转180度 rotate_270 = cv2.rotate(img,cv2.ROTATE_90_COUNTERCLOCKWISE) #顺时针旋转270度(逆时针旋转90度) cv2.imshow('cat',img) cv2.imshow('flip_x',flip_x) cv2.imshow('flip_y',flip_y) cv2.imshow('flip_xy',flip_xy) cv2.imshow('rotate_90',rotate_90) cv2.imshow('rotate_180',rotate_180) cv2.imshow('rotate_270',rotate_270) cv2.waitKey(0) cv2.destroyAllWindows()
2 图像拼接
opencv提供了用于图像拼接的算法,hconcat函数是在水平方向上做图像拼接,vconcat函数是在垂直方向上做图像拼接。对于输入图像组,hconcat函数中的图像必须具有相同的高度和深度;对于vconcat函数,图像必须具有相同的宽度和深度。除了opencv提供的函数,numpy包中也提供了hstack和vstack函数对图像或数组进行水平和垂直方向的拼接。
import cv2 import numpy as np #图像拼接 img_cat = cv2.imread('./cat.jpg') img_lena = cv2.imread('./lena.jpg') img_cat2 = cv2.resize(img_cat,(480,480)) #对图像进行缩放 img_lena2 = cv2.resize(img_lena,(480,480)) hconcat_res = cv2.hconcat([img_cat2,img_lena2]) #图像水平拼接,图像必须有相同的高度和深度 vconcat_res = cv2.vconcat([img_cat2,img_lena2]) #图像垂直拼接,图像必须有相同的宽度和深度 cv2.imshow('hconcat_res',hconcat_res) cv2.imshow('vconcat_res',vconcat_res) #cv2.imshow('hstack',np.hstack([img_cat2,img_lena2])) #也可以用numpy的hstack和vstack函数来拼接 #cv2.imshow('vstack',np.vstack([img_cat2,img_lena2])) cv2.waitKey(0) cv2.destroyAllWindows()
3)图像边界拓展
opencv中提供了图像边界拓展(给图像增加边框)的函数copyMaskBorder,dst = copyMaskBorder(src,top,bottom,left,right,borderType,value=None)
参数说明:
top表示上边界边框的尺寸,bottom表示下边界边框的尺寸,left表示左边界边框尺寸,right表示有边界边框尺寸。borderType是图像边界拓展策略(BORDER_CONSTANT=0 用指定像素值边界;BORDER_REPLICATE=1 复制边界像素;BORDER_REFLECT=2 反射复制边界像素;BORDER_WRAP=3 用另一边的像素补偿填充;BORDER_REFLECT_101=4 用边界为对称轴反射复制边界;BORDER_TRANSPARENT=5 透明边界;BORDER_ISOLATED=16 不看ROI之外的部分)value是指定边界像素值。
import cv2 import numpy as np #图像边界拓展 img = cv2.imread('./cat.jpg') img_border0 = cv2.copyMakeBorder(img,30,30,30,30,cv2.BORDER_CONSTANT,value=[0,0,0]) #边界拓展,边框拓展类型为常值方式 img_border2 = cv2.copyMakeBorder(img,30,30,30,30,cv2.BORDER_REFLECT) #反射复制边界像素 img_border1 = cv2.copyMakeBorder(img,30,30,30,30,cv2.BORDER_REPLICATE) #复制边界像素值 img_border3 = cv2.copyMakeBorder(img,30,30,30,30,cv2.BORDER_WRAP) #用另一边的像素补偿填充 cv2.imshow('cat',img) cv2.imshow('img_border0',img_border0) cv2.imshow('img_border1',img_border1) cv2.imshow('img_border2',img_border2) cv2.imshow('img_border3',img_border3) cv2.waitKey(0) cv2.destroyAllWindows()
4)图像傅里叶变换
傅里叶变换是应用最广泛的一种频率变换,它能够将图像从空间域变换到频率域,在频率域进行处理(比如图像锐化或图像去噪),然后通过傅里叶反变换能够将频率域信息变换到空间域内。
数字图像经过傅里叶变换后,得到的频域值是复数。因此,显示傅里叶变换的结果需要使用实数图像(real image)加虚数图像(complex image),或者幅度图像(magnitude image)加相位图像(phase image)的形式。对图像进行傅里叶变换后,我们会得到图像中的低频和高频信息。低频信息对应图像内变化缓慢的灰度分量。高频信息对应图像内变化越来越快的灰度分量,是由灰度的尖锐过渡造成的。
傅里叶频谱图上我们看到的明暗不一的亮点,其意义是指图像上某一点与领域点差异的强弱,即梯度的大小。也即该点的频率的大小(图像中低频部分指低梯度的点,高频部分相反)。一般来说,梯度大则该点的亮度强,否则该点的亮度弱。
对频谱移频到原点以后,可以看出图像的频率分布是以原点为圆心,对称分布的。将频谱移频到圆心除了可以清晰的看出图像频率分布以外,还有一个好处,它可以分离出周期性规律的干扰信号,比如正弦干扰。
opencv提供了实现离散傅里叶变换的函数dft,dst = dft(src, flags=None, nonzeroRows=None) flags表示变换标志,nonzeroRows设置非零行。
在使用该函数的时候,先转换为灰度图像,然后使用np.float32()函数将图像转换成np.float32格式。
注意:由于输出的频谱结果是一个复数,需要调用 cv2.magnitude() 函数将傅里叶变换的双通达结果转换为0到255的范围。opencv实现傅里叶变换和逆变换如下:
import cv2 import numpy as np #opencv实现图像傅里叶变换 from matplotlib import pyplot as plt %matplotlib inline img = cv2.imread('./cat.jpg') img_gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) #把图像转换为灰度图像 img_fc1 = np.float32(img_gray) #把灰度图像的元素值类型转换float型,方便后续计算 dft_img = cv2.dft(img_fc1,flags=cv2.DFT_COMPLEX_OUTPUT) #傅里叶变换,转换标识通常为cv2.DFT_COMPLEX_OUTPUT,用来输出一个复数阵列。 dftshift = np.fft.fftshift(dft_img) #把零频率分量从左上角移到频谱的中心 # print('dftshift',dftshift) # 由于输出的频谱结果是一个复数,需要调用 cv2.magnitude() 函数将傅里叶变换的双通道复数结果转换为单通道浮点型的幅度值 res_1 = cv2.magnitude(dftshift[:,:,0],dftshift[:,:,1]) # print('magnitude',res_1) result = 20*np.log(res_1) #需要将幅度值映射到灰度图像的灰度空间【0,255】内,但此时还是浮点型 # print('dft',result) #result = result.astype(np.uint8) result = np.round(result).astype('uint8') #result是浮点数,把数据类型转换为uint8 # 傅里叶逆变换 ishift = np.fft.ifftshift(dftshift) iimg = cv2.idft(ishift) #print('iimg',iimg) #逆变换输出还是浮点数,需要调用 cv2.magnitude() 函数将傅里叶变换的双通道复数结果转换为单通道浮点型的幅度值 iresult = cv2.magnitude(iimg[:, :, 0], iimg[:, :, 1]) print('iresult',iresult) #iresult的结果是浮点数,用normalize把数值归一化到[0,255]的整数之间,CV_8U:8位无符号整数(0…255) iresult2 = cv2.normalize(iresult,None,0,255,cv2.NORM_MINMAX,dtype=cv2.CV_8U) print('iresult2',iresult2) cv2.imshow('img_gray',img_gray) #cv显示图片 cv2.imshow('dft_image',result) cv2.imshow('idft_image',iresult2) plt.figure(figsize=(14,8)) #plt显示图片 plt.subplot(131),plt.imshow(img_gray, cmap='gray'),plt.title('image') plt.subplot(132),plt.imshow(result, cmap='gray'),plt.title('dft_image') plt.subplot(133),plt.imshow(iresult, cmap='gray'),plt.title('idft_image') plt.show() cv2.waitKey(0) cv2.destroyAllWindows()
此外还可以用numpy来实现傅里叶变换和逆变换:
import cv2 import numpy as np import matplotlib.pyplot as plt #numpy实现傅里叶变换 %matplotlib inline img = cv2.imread('cat.jpg', 0) #numpy进行傅里叶变换 f = np.fft.fft2(img) fshift = np.fft.fftshift(f) #print(fshift) #numpy进行傅里叶逆变换 ishift = np.fft.ifftshift(fshift) iimg = np.fft.ifft2(ishift) iimg = np.abs(iimg) print(iimg) plt.subplot(121),plt.imshow(img, cmap='gray'),plt.title('original') plt.subplot(122),plt.imshow(iimg, cmap='gray'),plt.title('result') plt.show()
5)图像仿射变换
仿射变换是实现图像旋转,平移和缩放的常见操作,opencv提供了用于仿射变换的函数warpAffine,dst = warpAffine(src,M,dsize,flags=None) M是2*3的变换矩阵,dsize是输出图像尺寸。flags是插值方式,由interpolationFlags定义。
图像平移:图像平移需要借助变换矩阵[[1,0,h],[0,1,w]] ,平移后的坐标(x',y')=((x+h),(y+w))
import cv2 #图像仿射变换--图像平移,需要借助变换矩阵[[1,0,h],[0,1,w]] ,平移后的坐标(x',y')=((x+h),(y+w)) import numpy as np #放射变换:缩放,平移,旋转,翻转的组合称为仿射变换 img_cat = cv2.imread('./cat.jpg') h,w,c =img_cat.shape M = np.float32([[1,0,100],[0,1,100]]) #变换矩阵,要float32 print(M) new_cat = cv2.warpAffine(img_cat,M,dsize=(w,h)) #平移操作,往右下移动100 cv2.imshow('img_cat',img_cat) cv2.imshow('new_cat',new_cat) cv2.waitKey(0) cv2.destroyAllWindows()
图像旋转:图像旋转需要通过函数getRotationMatrix2D生成变换矩阵, retval = getRotationMatrix2D(center, angle,scale) center是输入图像旋转中心,angle是旋转角度,正数为逆时针旋转,scale是缩放系数。 然后再调用warpAffine进行旋转变换。
import cv2 #图像仿射变换--图像旋转,需要借助opencv自定义的旋转变换矩阵函数getRotationMatrix2D(旋转中心,旋转角度,缩放比例) import numpy as np img_cat = cv2.imread('./cat.jpg') h,w,c =img_cat.shape M = cv2.getRotationMatrix2D((int(w/2),int(h/2)),70,1) #旋转操作矩阵,按照中心点逆时针转,旋转角度为70度,不进行缩放。 print(M) new_cat = cv2.warpAffine(img_cat,M,dsize=(w,h)) #按照中心点逆时针旋转 cv2.imshow('img_cat',img_cat) cv2.imshow('new_cat',new_cat) cv2.waitKey(0) cv2.destroyAllWindows()
图像自定义变换:需要借助opencv自定义的旋转变换矩阵函数getAffineTransform(原图三个坐标点,变换后的对应三个坐标点),根据变换前后坐标点的对应关系来进行图像变换(可以平移,旋转,拉伸等),自定义变换矩阵后,还是用warpAffine来进行图像变换。
import cv2 #图像仿射变换--图像旋转拉伸平移 import numpy as np img_cat = cv2.imread('./cat.jpg') h,w,c =img_cat.shape src = np.float32([(100,100),(120,50),(200,300)]) #原始图像随机三个坐标点 dst = np.float32([(120,120),(150,80),(200,350)]) #变换后对应的三个坐标点 M = cv2.getAffineTransform(src,dst) #图像变换矩阵 print(M) new_cat = cv2.warpAffine(img_cat,M,dsize=(w,h)) cv2.imshow('img_cat',img_cat) cv2.imshow('new_cat',new_cat) cv2.waitKey(0) cv2.destroyAllWindows()
变换后的结果与自定义的坐标点有关,有可能不是自己想要的结果,需要根据需要不断调试三对坐标点。
透视变换:透视变换时在二维平面获得接近真实三维物体的视觉效果的一种算法,即把一个坐标系变换成另外一个坐标系,可以把一张“斜”的图变“正”,把图像投影到一个新的视平面。opencv提供了用于透视变换的函数warpPerspective,dst = warpPerspective(src,M, dsize, flags=None)。3*3的变换矩阵可以通过函数getPerspectiveTransform(src, dst) 获取。src和dst是输入图像和输出图像对应的四边形顶点坐标。
如下可以把一本斜着的书变得”正“一些(输出的矩阵形状与定义的四对坐标点关系比较大):
import cv2 #图像仿射变换--透视变换,即把一个坐标系变换成另外一个坐标系,可以把一张“斜”的图变“正”,把图像投影到一个新的视平面 import numpy as np #getPerspectiveTransform(src,dst)获取矩阵,需要原图四个坐标点,变换后的对应四个坐标点。warpPerspective透视变换 img_book = cv2.imread('./book.jpg') h,w,c =img_book.shape src = np.float32([[10,200],[600,0],[700,500],[200,900]])#四个坐标和对应坐标比较麻烦,需要多次实验判断确定怎么设置效果好(四个角坐标比较好) dst = np.float32([[30,800],[20,60],[600,180],[700,900]]) M = cv2.getPerspectiveTransform(src,dst) #透视变换矩阵 print(M) new_book = cv2.warpPerspective(img_book,M,dsize=(800,960)) #透视变换 二维平面获得接近三维物体的视觉效果的算法 cv2.imshow('img_book',img_book) cv2.imshow('new_book',new_book) cv2.waitKey(0) cv2.destroyAllWindows()
6)更改图片背景颜色
上图的签名图片背景是灰色,插入某些文档中会比较突兀,如果可以把背景变成白色或者需要的颜色就好了,通过分析:前景签名的像素值小于100,背景像素值都高于100,可以通过对像素值的比较进行分离,然后单独对背景颜色进行处理。
import cv2 import numpy as np #更改图片背景颜色(前景签名的像素值小于100,背景像素值都高于100,通过对像素值的比较进行分离) def erase_background(img): height = img.shape[0] width = img.shape[1] channel = img.shape[2] #图像的bgr三通道 print('width:%s,height:%s,channel:%s'%(width,height,channel)) for row in range(height): for col in range(width): for c in range(channel): if img[row,col,c] >100: # img[row,col,c]=255 # 将对应通道对应像素坐标的像素值都改为255,背景变为[255,255,255],背景变白色 img[row,col,0]=0 #将红色通道的背景像素值全改为0 img[row,col,1]=0 #将蓝色通道的背景像素值改为0,背景变为[0,:,0],背景变为绿色 cv2.imshow('erase_background',img) src = cv2.imread('handwrite.jpg') cv2.imshow('origin',src) erase_background(src) cv2.waitKey(0) cv2.destroyAllWindows()
把背景改成白色,效果如下:
也可以把背景改成其他颜色,比如红色:
7)图像roi区域提取
图像roi提取,用鼠标选取感兴趣区域,按enter分割保存。手动提取感兴趣区域,这个应用有时候挺有用的,opencv提供了一个函数 selectROI,dst = selectROI(windowname, img)
windowname表示选取的区域被显示在窗口的名字,img表示被选取图像。返回值dst是一个元组(x, y, w, h)表示选取roi的左上角坐标和矩形的宽高。
import cv2 #图像roi提取,用鼠标选取感兴趣区域,按enter分割保存 img = cv2.imread('./cat.jpg') cv2.imshow("original", img) # 选择ROI roi = cv2.selectROI('original', img) #鼠标选取感兴趣区域,返回tuple元组(左上角坐标,roi区域的宽高) print(roi) x, y, w, h = roi # 显示ROI并保存图片 if roi != (0, 0, 0, 0): crop = img[y:y+h, x:x+w] # 切片img[高,宽,通道] cv2.imshow("crop", crop) cv2.imwrite("./crop.jpg", crop) print("Saved!") # 退出 cv2.waitKey(0) cv2.destroyAllWindows()