在一张图片上显示多幅图片
参考博客:matplotlib中的plt.figure()、plt.subplot()、plt.subplots()、add_subplots以及add_axes的使用
import matplotlib.pyplot as plt
import cv2
img1=cv2.imread(r'C:\Users\lenovo\Desktop\pythonn\ss\8.JPG')
img2=cv2.imread(r'C:\Users\lenovo\Desktop\pythonn\ss\9.JPG')
img3=cv2.imread(r'C:\Users\lenovo\Desktop\pythonn\ss\16.JPG')
img4=cv2.imread(r'C:\Users\lenovo\Desktop\pythonn\ss\20.JPG')
plt.figure()#定义画板相关参数
plt.subplot(2,2,1)#两行两列的第一行第一列
plt.imshow(img1)
plt.title('1')
plt.axis('off')
plt.subplot(2,2,2)#两行两列的第一行第二列
plt.imshow(img2)
plt.title('2')
plt.axis('off')
plt.subplot(2,2,3)#两行两列的第二行第一列
plt.imshow(img3)
plt.title('3')
plt.axis('off')
plt.subplot(2,2,4)#两行两列的第二行第二列
plt.imshow(img4)
plt.title('4')
plt.axis('off')
plt.show()
参考博客:OpenCV 例程 200 篇
cv.imread(filename,flags)
参数说明:
filename:读取图像的文件路径和文件名
flags:读取图片的方式,可选项
flag=1:始终将图像转换为 3 通道BGR彩色图像,如果未定义,默认flag=1
flag=0:始终将图像转换为单通道灰度图像
flag=-1:按原样返回加载的图像(使用Alpha通道)
flag=8:在输入具有相应深度时返回16位/ 32位图像,否则将其转换为8位
flag=4:以任何可能的颜色格式读取图像
返回值:读取的 OpenCV 图像,nparray 多维数组
函数 cv2.imwrite() 用于将图像保存到指定的文件夹。
参数:
filename:要保存的文件的路径和名称,包括文件扩展名
img:要保存的 OpenCV 图像,nparray 多维数组。
注意事项:
(1).cv2.imwrite() 保存的是 OpenCV 图像(多维数组),不是 cv2.imread() 读取的图像文件,所保存的文件格式是由 filename 的扩展名决定的,与读取的图像文件的格式无关。
(2).对 4 通道 BGRA 图像,可以使用 Alpha 通道保存为 PNG 图像。
(3).cv2.imwrite() 指定图片的存储路径和文件名,在 python3 中不支持中文和空格(但并不会报错)。必须使用中文时,可以使用 cv2.imencode() 处理。
imshow(winname, img)
参数:
winname:字符串,显示窗口的名称。
img:所显示的 OpenCV 图像,nparray 多维数组。
显示图像的缩放取决于图像深度:
对 8 位无符号图像,按原样显示;
对 16 位无符号或 32 位整数图像,将像素值范围 [0,255 * 256] 映射到 [0,255] 显示;
对 32 位浮点图像,将像素值范围 [0,1] 映射到 [0,255] 显示;
如果指定窗口尚未创建,则创建一个自适应图像大小的窗口;
如果要显示大于屏幕分辨率的图像,需要先调用 namedWindow(“”,WINDOW_NORMAL)。
注意事项:
(1).函数 cv2.imshow() 之后要用 waitKey() 函数设定图像窗口的显示时长,否则不会显示图像窗口。
(2).图像窗口将在 waitKey() 函数所设定的时长(毫秒)后自动关闭,waitKey(0) 表示窗口显示时长为无限。
(3).可以创建多个不同的显示窗口,每个窗口必须命名不同的 filename。
(4).可以用 destroyWindow() 函数关闭指定的显示窗口,也可以用 destroyAllWindows() 函数关闭所有的显示窗口。
#imread/imwrite/imshow
import cv2
import numpy as np
img1_path=r'C:\Users\lenovo\Desktop\imagstudy\1.jfif'
img2_path=r'C:\Users\lenovo\Desktop\imagstudy\2.jfif'
a1=cv2.imread(img1_path,flags=0)
a2=cv2.imread(img1_path,flags=1)
b1=cv2.imread(img2_path,flags=0)
b2=cv2.imread(img2_path,flags=1)
cv2.imshow('image1-0',a1)
cv2.imshow('image1-1',a2)
cv2.imshow('image2-0',b1)
cv2.imshow('image2-1',b2)
key = cv2.waitKey(0)#函数 cv2.imshow()之后要用waitKey()函数设定图像窗口的显示时长,否则不会显示图像窗口。waitKey(0) 表示窗口显示时长为无限。
cv2.imread()接口读图像,读进来直接是BGR 格式数据格式在 0~255
cv2.cvtColor(p1,p2) 是颜色空间转换函数,p1是需要转换的图片,p2是转换成何种格式。
cv2.COLOR_BGR2RGB 将BGR格式转换成RGB格式
cv2.COLOR_BGR2GRAY 将BGR格式转换成灰度图片
plt.imshow() 可以直接显示 OpenCV 灰度图像,不需要格式转换,但需要使用 cmap=‘gray’ 进行参数设置。
参考博客:cv2.imread()和cv2.cvtColor() 的使用
import matplotlib.pyplot as plt
import cv2
img1=cv2.imread(r'C:\Users\lenovo\Desktop\imgstudy\1.jfif')
plt.subplot(2,2,1)
plt.imshow(img1)
plt.title('BGR')
plt.axis('off')
img2=cv2.cvtColor(img1, cv2.COLOR_BGR2RGB)
plt.subplot(2,2,2)
plt.imshow(img2)
plt.title('RGB')
plt.axis('off')
img3=cv2.cvtColor(img1,cv2.COLOR_BGR2GRAY)
plt.subplot(2,2,3)
plt.imshow(img3)
plt.title('GRAY')#未设置 cmap=‘gray’,灰度图像的颜色显示异常
plt.axis('off')
plt.subplot(2,2,4)
plt.imshow(img3,cmap='gray')
plt.title('cmap=gray')
plt.axis('off')
plt.show()
运行结果
(如果用cv2.imshow()显示图片,不用做BGR与RGB的转换,如果用plt.imshow()显示图片,需要使用cv2.cvtColor原图片的色彩)
img.ndim:查看图像的维数,彩色图像的维数为 3,灰度图像的维数为 2。
img.shape:查看图像的形状,即图像栅格的行数(高度)、列数(宽度)、通道数。
img.size:查看图像数组元素总数,灰度图像的数组元素总数为像素数量,彩色图像的数组元素总数为像素数量与通道数的乘积
img.dtype:元素类型
#图像属性
import cv2
img1=cv2.imread(r'C:\Users\lenovo\Desktop\imgstudy\1.jfif',flags=1)#彩色图像
img2=cv2.imread(r'C:\Users\lenovo\Desktop\imgstudy\1.jfif',flags=0)#灰度图像
print('img1的维度是:{},img2的维度是:{}'.format(img1.ndim,img2.ndim))
print('img1的形状是:{},img2的形状是:{}'.format(img1.shape,img2.shape))
print('img1的像素数量是:{},img2的像素数量是:{}'.format(img1.size,img2.size))
print('img1的元素类型是:{},img2的元素类型是:{}'.format(img1.dtype,img2.dtype))
运行结果:
img1的维度是:3,img2的维度是:2
img1的形状是:(799, 1200, 3),img2的形状是:(799, 1200)
img1的像素数量是:2876400,img2的像素数量是:958800
img1的元素类型是:uint8,img2的元素类型是:uint8
通过访问数组元素,直接获取像素值,通过img[a,b]操作可以直接定位到对应的pixel(像素)
import cv2
img1=cv2.imread(r'C:\Users\lenovo\Desktop\crop.jpg',flags=1)
px=img1[10,10]
print('img1[10,10]的像素值是:{}'.format(px))
运行结果:
img1[10,10]的像素值是:[168 38 61]
也可通过for循环遍历B G R三个通道
import cv2
img1=cv2.imread(r'C:\Users\lenovo\Desktop\crop.jpg',flags=1)
x,y=10,10
#通过for循环遍历数组
print('img1[{},{}]的像素值是'.format(x,y))
for i in range(3):
print(img1[x,y,i],end=' ')# i=0,1,2 对应 B,G,R 通道
运行结果:
img1[10,10]的像素值是
168 38 61
从结果可看出此两种方法等同。
img.item()也是可以直接定位到对应的pixel(像素)
import cv2
img1=cv2.imread(r'C:\Users\lenovo\Desktop\crop.jpg',flags=1)
x,y=10,10
#print(img1.item(x,y))
#通过for循环遍历数组
print('img1[{},{}]的像素值是'.format(x,y))
for i in range(3):
print(img1.item(x,y,i),end=' ')# i=0,1,2 对应 B,G,R 通道
但是需注意:
直接print(img1.item(x,y))会报错!!!参考博客img.item()跟img[x,y]
img.itemset语法:img.itemset((x,y,channel),newvalue)
将 [x,y,channel] 的值修改为 newValue,channel可为0,1,2
import cv2
img1=cv2.imread(r'C:\Users\lenovo\Desktop\crop.jpg',flags=1)
x,y=10,10
#print(img1.item(x,y))
#通过for循环遍历数组
print('img1[{},{}]的像素值是'.format(x,y))
for i in range(3):
print(img1.item(x,y,i),end=' ')# i=0,1,2 对应 B,G,R 通道
img1.itemset((10,10,2),234)#img.itemset语法:img.itemset((x,y,channel),newvalue)
#将 [x,y,channel] 的值修改为 newValue
print('\n修改后的像素值是:{}'.format(img1[x,y]))
运行结果:
img1[10,10]的像素值是
168 38 61
修改后的像素值是:[168 38 234]
使用 Numpy 的 np.copy() 函数可以进行图像的复制(不能通过直接赋值进行图像的复制,这样会改变原图像!!!)
(1)Python 中的 “复制” 有无拷贝、浅拷贝和深拷贝之分,无拷贝相当于引用,浅拷贝只是对原变量内存地址的拷贝,深拷贝是对原变量(ndarray数组)的所有数据的拷贝。
(2)Numpy 直接赋值是无拷贝,np.copy() 方法是深拷贝,切片操作是特殊的浅拷贝。
(3)直接赋值得到的新图像相当于引用,改变新图像的值时原图像的值也发生改变;np.copy() 方法复制图像(ndarray数组)得到的新图像才是深拷贝,改变复制图像的形状或数值,原来图像并不会发生改变。
#图像拷贝
import cv2
import matplotlib.pyplot as plt
imgg = cv2.imread(r'C:\Users\lenovo\Desktop\pythonn\dog_bike_car.jpg', flags=1) # flags=1 读取彩色图像(BGR)
img1=cv2.cvtColor(imgg,cv2.COLOR_BGR2RGB)
img2 = img1.copy()
print("img2=img1.copy(), img2 is img1?", img2 is img1)
for col in range(200):
for row in range(200):
img2[col, row, :] = 255 #将此处的像素修改为255
imggg = cv2.imread(r'C:\Users\lenovo\Desktop\pythonn\dog_bike_car.jpg', flags=1) # flags=1 读取彩色图像(BGR)
img3=cv2.cvtColor(imggg,cv2.COLOR_BGR2RGB)
img4 = img3
print("img4=img3, img4 is img3?", img4 is img3)
for col in range(300):
for row in range(300):
img4[col, row, :] = 0 #将此处的像素修改为0
plt.subplot(2,2,1)
plt.imshow(img1)
plt.title('img1')
plt.axis('off')
plt.subplot(2,2,2)
plt.imshow(img2)
plt.title('img2')
plt.axis('off')
plt.subplot(2,2,3)
plt.imshow(img3)
plt.title('img3')
plt.axis('off')
plt.subplot(2,2,4)
plt.imshow(img4)
plt.title('img4')
plt.axis('off')
plt.show()
key = cv2.waitKey(0) # 等待按键命令
运行结果:
img2=img1.copy(), img2 is img1? False
img4=img3, img4 is img3? True
图片结果:
从图片结果可以看出,使用copy函数,原图不会改变,而使用直接赋值,原图也会改变。
用 Numpy 的切片方法可以进行图像的裁剪,操作简单方便。
img[y:y+h, x:x+w].copy()
img:图像数据,nparray 多维数组
x, y:整数,像素值,裁剪矩形区域左上角的坐标值
w, h:整数,像素值,裁剪矩形区域的宽度、高度
返回值 :裁剪后获得的 OpenCV 图像,nparray 多维数组
import cv2
img1=cv2.imread(r'C:\Users\lenovo\Desktop\pythonn\dog_bike_car.jpg',flags=1)# flags=1 读取彩色图像(BGR)
ymin,xmin,h,w=0,84,576,768 # 矩形裁剪区域 (ymin:ymin+h, xmin:xmin+w) 的位置参数
img2=img1[ymin:ymin+h,xmin:xmin+w].copy()# 切片获得裁剪后保留的图像区域
cv2.imshow("img2",img2)
print(img2.shape) #打印图像的高度,宽度,通道数
cv2.waitKey(0)
结果如下:
(576, 684, 3)
函数 cv2.selectROI() 可以通过鼠标选择感兴趣的矩形区域(ROI)参考博客:cv2.selectROI用法、参数 、返回值的解读
cv2.selectROI(windowName, img, showCrosshair=None, fromCenter=None):
参数windowName:选择的区域被显示在的窗口的名字
参数img:要在什么图片上选择ROI
参数showCrosshair:是否在矩形框里画十字线
参数fromCenter:是否是从矩形框的中心开始画
代码如下:
import cv2
img3=cv2.imread(r'C:\Users\lenovo\Desktop\pythonn\dog_bike_car.jpg',flags=1)
roi = cv2.selectROI(img3, showCrosshair=True, fromCenter=False)
print(roi)
xmin, ymin, w, h = roi # 矩形裁剪区域(ymin:ymin+h, xmin:xmin+w) 的位置参数
imgROI = img1[ymin:ymin+h, xmin:xmin+w].copy() # 切片获得裁剪后保留的图像区域
cv2.imshow("DemoRIO", imgROI)
cv2.imwrite(r'C:\Users\lenovo\Desktop\ROI.jpg',imgROI)
cv2.waitKey(0)
运行结果:
(97, 123, 482, 426)
鼠标选定要裁剪的区域,按enter
裁剪结果如下:
(1)np.hstack() 按水平方向(列顺序)拼接 2个或多个图像,图像的高度(数组的行)必须相同。
(2)np.vstack() 按垂直方向(行顺序)拼接 2个或多个图像,图像的宽度(数组的列)必须相同。
(3)np.hstack() 和 np.vstack() 只是简单地将几张图像直接堆叠而连成一张图像,并未对图像进行特征提取和边缘处理,因而并不能实现图像的全景拼接。
水平拼接代码如下:
import cv2
import numpy as np
img1=cv2.imread(r'C:\Users\lenovo\Desktop\pythonn\dog_bike_car.jpg',flags=1)
img2=cv2.imread(r'C:\Users\lenovo\Desktop\imgstudy\1.jfif',flags=1)
img1_resize=cv2.resize(img1,(400,400))
img2_resize=cv2.resize(img2,(400,400))
img_hstack=np.hstack((img1_resize,img2_resize))
cv2.imshow('hstack',img_hstack)
cv2.waitKey(0)
运行结果如图:
函数 cv2.split() 将 3 通道 BGR 彩色图像分离为 B、G、R 单通道图像
cv2.split(img[, mv]) -> retval # 图像拆分为 BGR 通道
img:图像数据,nparray 多维数组
mv:指定的分拆通道(可选)
(1)对于 openCV 使用的 BGR 格式图像,返回的分拆通道的次序为 B、G、R 通道。
(2)BGR 彩色图像的数据形状为 (width, height, channels=3),返回的 B/G/R 通道的数据形状为 (width, height),不能按照 BGR 彩色图像直接显示。
(3)如果直接用 imshow 显示返回的单通道对象,将被视为 (width, height) 形状的灰度图像显示。如果要正确显示某一颜色分量,需要增加另外两个通道值(置 0)转换为 BGR 三通道格式,再用 imshow 才能显示为拆分通道的颜色。
(4)cv2.split() 操作复杂耗时,可以直接使用 NumPy 切片得到分离通道。
import cv2
import numpy as np
img1=cv2.imread(r'C:\Users\lenovo\Desktop\pythonn\dog_bike_car.jpg',flags=1)
B_img,G_img,R_img=cv2.split(img1)
cv2.imshow('BGR',img1)
cv2.imshow('B_img',B_img)
img_zeros=np.zeros_like(img1)#np.zeros_like() 等方法创建与已有图像大小、类型相同的新图像。
img_zeros[:,:,1]=G_img #img_zeros[:,:,1]:0,1,2分别对应B,G,R三个通道
cv2.imshow('img_zeros',img_zeros)
cv2.waitKey(0)
显示BGR彩色图像:
imshow 显示返回的单通道对象,将被视为 (width, height) 形状的灰度图像显示:
如果要正确显示某一颜色分量,需要增加另外两个通道值(置 0)转换为 BGR 三通道格式,再用 imshow 才能显示为拆分通道的颜色。下图是G通道G_img:
使用 NumPy 切片得到分离通道更为简便,而且运行速度比 cv2.split 更快。
#numpy切片分离三通道
import cv2
import matplotlib.pyplot as plt
import numpy as np
img1=cv2.imread(r'C:\Users\lenovo\Desktop\pythonn\dog_bike_car.jpg',flags=1)
B_img=img1.copy()
B_img[:,:,1]=0
B_img[:,:,2]=0
G_img=img1.copy()
G_img[:,:,0]=0
G_img[:,:,2]=0
R_img=img1.copy()
R_img[:,:,0]=0
R_img[:,:,1]=0
#消除B通道:
GR_img=img1.copy()
GR_img[:,:,0]=0
plt.subplot(2,2,1)
plt.imshow(B_img)
plt.title('1. B channel')
plt.axis('off')
plt.subplot(2,2,2)
plt.imshow(G_img)
plt.title('2. G channel')
plt.axis('off')
plt.subplot(2,2,3)
plt.imshow(R_img)
plt.title('3. R channel')
plt.axis('off')
plt.subplot(2,2,4)
plt.imshow(GR_img)
plt.title('4. GR channel')
plt.axis('off')
plt.show()
(1)进行合并的 B、G、R 单通道图像分量,数据形状必须为 (width, height),而不是形状为 (width, height, channels=3) 的蓝色/绿色/红色图像。
(2)单通道图像分量的图像大小 (width, height) 必须相同才能进行合并。
(3)颜色通道要按照 B、G、R 通道次序合并,才能得到 BGR 格式的合并结果。
(4)cv2.merge() 操作复杂耗时,推荐使用 NumPy 数组合并函数 np.stack() 生成合成图像。
#图像通道的合并
import cv2
import numpy as np
img1=cv2.imread(r'C:\Users\lenovo\Desktop\pythonn\dog_bike_car.jpg',flags=1)
B_img,G_img,R_img=cv2.split(img1)
# cv2.merge 实现图像通道的合并
img_merge=cv2.merge([B_img,G_img,R_img])
cv2.imshow('merge',img_merge)
#np.stack()实现通道的合并
img_stack=np.stack([B_img,G_img,R_img],axis=2)#不太理解参数axis
cv2.imshow('stack',img_stack)
print(img_merge.shape,img_stack.shape)
cv2.waitKey(0)
运行结果:
(576, 768, 3) (576, 768, 3)
merge:
np.stack:
Merge 与 Stack 不仅形状相同,而且每个位置的元素相等,表明 cv2.merge() 与 np.stack() 方法合并图像通道的结果是相同的。
语法:
cv2.add(img1, img2)
img1为图片1,img2为图片2
(1)OpenCV 加法和 numpy 加法之间有区别:cv2.add() 是饱和运算(相加后如大于 255 则结果为 255),而 Numpy 加法是模运算。
(2)使用 cv2.add() 函数对两张图片相加时,图片的大小和类型(通道数)必须相同
#图像的加法:
import cv2
import matplotlib.pyplot as plt
img=cv2.imread(r'C:\Users\lenovo\Desktop\pythonn\dog_bike_car.jpg',flags=1)
imgg=cv2.imread(r'C:\Users\lenovo\Desktop\bridge.jpg',flags=1)
img1=cv2.resize(img,(400,400))
img2=cv2.resize(imgg,(400,400))
img_cvadd=cv2.add(img1,img2)
img_npadd=img1+img2
plt.subplot(1,2,1)
plt.imshow(cv2.cvtColor(img_cvadd,cv2.COLOR_BGR2RGB))
plt.title('1. img_cvadd')
plt.axis('off')
plt.subplot(1,2,2)
plt.imshow(cv2.cvtColor(img_npadd,cv2.COLOR_BGR2RGB))
plt.title('2,img_npadd')
plt.axis('off')
plt.show()
运行结果:
饱和加法cv2.add()以 255 为上限,所有像素只会变的更白(大于原值);取模加法numpy以 255 为模,会导致部分像素变黑 (小于原值)。
因此,一般情况下应使用 cv2.add 进行饱和加法操作,不宜使用 numpy 取模加法。
参考博客:【OpenCV 例程200篇】14. 图像与标量相加(cv2.add)
语法:cv2.addWeighted(src1, alpha, src2, beta, gamma)
scr1, scr2:ndarray 多维数组,表示一个灰度或彩色图像
alpha:第一张图像 scr1 的权重,通常取为 0~1 之间的浮点数
beta:第二张图像 scr2 的权重,通常取为 0~1 之间的浮点数
gamma: 灰度系数,图像校正的偏移量,用于调节亮度
#图像的加权加法:
import cv2
import matplotlib.pyplot as plt
img=cv2.imread(r'C:\Users\lenovo\Desktop\pythonn\dog_bike_car.jpg',flags=1)
imgg=cv2.imread(r'C:\Users\lenovo\Desktop\bridge.jpg',flags=1)
img1=cv2.resize(img,(400,400))
img2=cv2.resize(imgg,(400,400))
img_cvadd1=cv2.addWeighted(img1,0.8,img2,0.2,0)
img_cvadd2=cv2.addWeighted(img1,0.6,img2,0.4,0)
img_cvadd3=cv2.addWeighted(img1,0.4,img2,0.6,0)
plt.subplot(2,2,1)
plt.imshow(cv2.cvtColor(img_cvadd1,cv2.COLOR_BGR2RGB))
plt.title('1. img_cvadd1')
plt.axis('off')
plt.subplot(2,2,2)
plt.imshow(cv2.cvtColor(img_cvadd2,cv2.COLOR_BGR2RGB))
plt.title('2. img_cvadd2')
plt.axis('off')
plt.subplot(2,2,3)
plt.imshow(cv2.cvtColor(img_cvadd3,cv2.COLOR_BGR2RGB))
plt.title('3. img_cvadd3')
plt.axis('off')
plt.show()
参考博客不同尺寸的图像加法
#图像的渐变切换
import cv2
import numpy as np
img=cv2.imread(r'C:\Users\lenovo\Desktop\pythonn\dog_bike_car.jpg',flags=1)
imgg=cv2.imread(r'C:\Users\lenovo\Desktop\bridge.jpg',flags=1)
img1=cv2.resize(img,(400,400))
img2=cv2.resize(imgg,(400,400))
wList = np.arange(0.0, 1.0, 0.05) # start, end, step
for w in wList:
imgAddW = cv2.addWeighted(img1, w, img2, (1 - w), 0)
cv2.imshow("imgAddWeight", imgAddW)
cv2.waitKey(1000)
上述代码可以观察到img1到img2的渐变过程(img1的权重从0开始,每次以0.05叠加,直至img1的权重为1)
此部分暂时用不着,略过不学
具体参考博客:OpenCV 例程 200 篇
仿射变换,指一个向量空间进行线性变换+平移变成另外一个向量空间,它需要一个变换矩阵,而由于仿射变换较为复杂,一般很难找出这个矩阵,于是opencv提供了cv2.getAffineTransform()
cv2.getAffineTransForm()通过找原图像中三个点的坐标和变换图像的相应三个点坐标,创建一个2X3的矩阵。最后这个矩阵会被传给函数cv2.warpAffine()
(1)cv2.getAffineTransform( pts1 , pts2)
函数作用:构建变换矩阵
pts1 原图像三个点的坐标
pts2 原图像三个点在变换后相应的坐标
(2)cv2.warpAffine()
cv2.warpAffine(img, MA, (cols, rows))
img:原图像
MA:变换矩阵
(cols,rows):此图像的长、宽。
#图像的仿射变换
import cv2
import matplotlib.pyplot as plt
import numpy as np
img1=cv2.imread(r'C:\Users\lenovo\Desktop\pythonn\dog_bike_car.jpg',flags=1)
rows, cols, ch = img1.shape#rows:行数, cols:列数,ch:通道数
pts1 = np.float32([[50, 50], [200, 50], [50, 200]]) # 初始位置
pts2 = np.float32([[50, 100], [200, 50], [150, 250]]) # 终止位置
MA = cv2.getAffineTransform(pts1, pts2) # 计算 2x3 变换矩阵 MA
dst = cv2.warpAffine(img1, MA, (cols, rows)) # 实现仿射变换
plt.figure(figsize=(9,6))
plt.subplot(121), plt.imshow(cv2.cvtColor(img1, cv2.COLOR_BGR2RGB)), plt.title("Original")
plt.subplot(122), plt.imshow(cv2.cvtColor(dst, cv2.COLOR_BGR2RGB)), plt.title("warpAffine")
plt.show()
运行结果:
原理参考博客:图像的平移
(1)、变换矩阵 MA 反映平移或旋转的关系,是 np.float32 类型 ndarray 二维数组(2行*3列)。
(2)、平移变换矩阵 M = [(1,0,dx), (0,1,dy)],Tx 表示向右(负值向左)移动像素点数,Ty 表示向下(负值向上)移动像素点数。
(3)、输出图像的大小 dsize 的格式为元组 (width,height)。
import matplotlib.pyplot as plt
import numpy as np
import cv2
img=cv2.imread(r'C:\Users\lenovo\Desktop\pythonn\dog_bike_car.jpg',flags=1)
rows,cols,channel=img.shape
dx,dy=100,20
MA=np.float32([[1,0,dx],[0,1,dy]])#构造平移矩阵
dst=cv2.warpAffine(img,MA,[cols,rows],borderValue=(128,128,128))#borderValue:128即采用灰色填充
plt.figure(figsize=(9,6))
plt.subplot(121),plt.imshow(cv2.cvtColor(img,cv2.COLOR_BGR2RGB)),plt.title('Before translation')
plt.subplot(122),plt.imshow(cv2.cvtColor(dst,cv2.COLOR_BGR2RGB)),plt.title('After translation')
plt.show()
运行结果如下:
此节可以略过,直接跳到第21节(以任意点为中心的旋转),原理参考博客图像的旋转(以原点为中心)
#图像的旋转(以原点为中心旋转), (以原点 (0,0) 为中心顺时针旋转)
import cv2
import matplotlib.pyplot as plt
import numpy as np
img=cv2.imread(r'C:\Users\lenovo\Desktop\pythonn\dog_bike_car.jpg',flags=1)
rows,cols,ch=img.shape
theta=np.pi/30
costheta=np.cos(theta)
sintheta=np.sin(theta)
MA=np.float32([[costheta,-sintheta,0],[sintheta,costheta,0]])#构造绕原点顺时针旋转的矩阵
dst=cv2.warpAffine(img,MA,(cols,rows),borderValue=(128,128,128))
plt.figure(figsize=(9,6))
plt.subplot(121),plt.imshow(cv2.cvtColor(img,cv2.COLOR_BGR2RGB)),plt.title('Before ROtation')
plt.subplot(122),plt.imshow(cv2.cvtColor(dst,cv2.COLOR_BGR2RGB)),plt.title('After ROtation')
plt.show()
运行结果:
原理参考博客:图像的旋转(以任意点为中心)
OpenCV 提供了 cv2.getRotationMatrix2D 函数, 根据旋转角度和位移计算旋转变换矩阵 MA。
cv2.getRotationMatrix2D(center, angle, scale) → MA
语法:
import cv2
import matplotlib.pyplot as plt
img=cv2.imread(r'C:\Users\lenovo\Desktop\pythonn\dog_bike_car.jpg',flags=1)
height,width,channel=img.shape
#cv2.getRotationMatrix2D(center, angle, scale) → M
theta1,theta2=30,90 #定义旋转角度
x0,y0=width//2,height//2 #定义旋转中心
MA1=cv2.getRotationMatrix2D((x0,y0),theta1,1)
MA2=cv2.getRotationMatrix2D((x0,y0),theta2,1)
dst1=cv2.warpAffine(img,MA1,(width,height),borderValue=(255,255,255))
dst2=cv2.warpAffine(img,MA2,(width,height),borderValue=(255,255,255))
plt.figure(figsize=(9,6))
plt.subplot(131),plt.imshow(cv2.cvtColor(img,cv2.COLOR_BGR2RGB)),plt.title('original')
plt.subplot(132),plt.imshow(cv2.cvtColor(dst1,cv2.COLOR_BGR2RGB)),plt.title('Rotation 30')
plt.subplot(133),plt.imshow(cv2.cvtColor(dst2,cv2.COLOR_BGR2RGB)),plt.title('Rotation 90')
plt.show()
cv2.flip(src, flipCode)
语法:
#图像的翻转:
import cv2
import matplotlib.pyplot as plt
#img=cv2.imread(r'C:\Users\lenovo\Desktop\ExImage_beam_data_183925_00001.jpg',flags=1)
img=cv2.imread(r'C:\Users\lenovo\Desktop\pythonn\dog_bike_car.jpg',flags=1)
#flipCode:控制参数,整型(int),flipCode>0 水平翻转,flipCode=0 垂直翻转,flipCode<0 水平和垂直翻转
imgflip1=cv2.flip(img,flipCode=-1)
imgflip2=cv2.flip(img,flipCode=0)
imgflip3=cv2.flip(img,flipCode=1)
plt.figure(figsize=(30,20))
plt.subplot(221),plt.imshow(cv2.cvtColor(img,cv2.COLOR_BGR2RGB)),plt.title('Before Flip'),plt.axis('off')
plt.subplot(222),plt.imshow(cv2.cvtColor(imgflip1,cv2.COLOR_BGR2RGB)),plt.title(' Flip1'),plt.axis('off')
plt.subplot(223),plt.imshow(cv2.cvtColor(imgflip2,cv2.COLOR_BGR2RGB)),plt.title(' Flip2'),plt.axis('off')
plt.subplot(224),plt.imshow(cv2.cvtColor(imgflip3,cv2.COLOR_BGR2RGB)),plt.title(' Flip3'),plt.axis('off')
plt.show()
运行结果:
cv2.resize(src, dst, dsize, fx, fy, interpolation)
语法:
#图像的缩放
import cv2
import matplotlib.pyplot as plt
img=cv2.imread(r'C:\Users\lenovo\Desktop\pythonn\dog_bike_car.jpg',flags=1)
img_resize1=cv2.resize(img,(416,416))
img_resize2=cv2.resize(img,None,fx=0.3,fy=0.4)
img_resize3=cv2.resize(img,(500,200),interpolation=cv2.INTER_NEAREST)
plt.figure(figsize=(30,10))
plt.subplot(221),plt.imshow(cv2.cvtColor(img,cv2.COLOR_BGR2RGB)),plt.title('Before Resize'),plt.axis('off')
plt.subplot(222),plt.imshow(cv2.cvtColor(img_resize1,cv2.COLOR_BGR2RGB)),plt.title('resize1'),plt.axis('off')
plt.subplot(223),plt.imshow(cv2.cvtColor(img_resize2,cv2.COLOR_BGR2RGB)),plt.title('resize2'),plt.axis('off')
plt.subplot(224),plt.imshow(cv2.cvtColor(img_resize3,cv2.COLOR_BGR2RGB)),plt.title('resize3'),plt.axis('off')
plt.show()
运行结果:
参考博客:图像金字塔(cv2.pyrDown)
参考博客:https://blog.csdn.net/youcans/article/details/121416883
按照颜色对图像进行分类,可以分为二值图像、灰度图像和彩色图像。
import cv2
img_original=cv2.imread(r'C:\Users\lenovo\Desktop\ExImage_beam_data_183925_00001.jpg',flags=1)
img_flags=cv2.imread(r'C:\Users\lenovo\Desktop\ExImage_beam_data_183925_00001.jpg',flags=0)#读取图像文件时flags=0直接读取为灰度图像
img_Gray=cv2.cvtColor(img_original,cv2.COLOR_BGR2GRAY)
img_RGB=cv2.cvtColor(img_original,cv2.COLOR_BGR2RGB)
cv2.imshow('flags=0',img_flags)
cv2.imshow('oringinal',img_original)
cv2.imshow('RGB',img_RGB)
cv2.imshow('Gray',img_Gray)
cv2.waitKey(0)
cv2.threshold(src, thresh, maxval, type[, dst]) → retval, dst
语法:
#图像的二值化处理
import cv2
import matplotlib.pyplot as plt
img_Gray=cv2.imread(r'C:\Users\lenovo\Desktop\pythonn\dog_bike_car.jpg',flags=0)
ret1,img1=cv2.threshold(img_Gray,75,255,cv2.THRESH_BINARY)#大于63时置为255,否则置为0
ret2,img2=cv2.threshold(img_Gray,75,255,cv2.THRESH_OTSU)#使用 OTSU 算法选择阈值
ret3,img3=cv2.threshold(img_Gray,75,255,cv2.THRESH_BINARY_INV)#大于160时置为0,否则置为255
ret4,img4=cv2.threshold(img_Gray,80,255,cv2.THRESH_TRUNC)#大于阈值时置为阈值 thresh,否则不变(保持原色)
ret5,img5=cv2.threshold(img_Gray,75,255,cv2.THRESH_TOZERO)#大于阈值时不变,保持原色,否则置零
plt.figure(figsize=(50,6))
titlelist=['1.threshold=75','2.threshold=75(OTSU)','3.threshold=75(BINARY_INV)','4.threshold=75(TRUNC)','5.threshold=75(TOZERO)','6.original']
imglist=[img1,img2,img3,img4,img5,img_Gray]
for i in range(6):
plt.subplot(2,3,i+1)
plt.imshow(imglist[i],'gray')
plt.title(titlelist[i])
plt.axis('off')
plt.show()
运行结果:
函数 threshold() 可以将灰度图像转换为二值图像,图像完全由像素 0 和 255 构成,呈现出只有黑白两色的视觉效果。
灰度阈值化通过选取的灰度阈值 thresh,将每个像素的灰度值与阈值进行比较,将灰度大于阈值的像素点置为最大灰度,小于阈值的像素点置为最小灰度,得到二值图像,可以突出图像轮廓,把目标从背景中分割出来。
图像的反色变换,即图像反转,将黑色像素点变白色,白色像素点变黑色。广义的反色变换也可以应用于彩色图像,即对所有像素点取补。
图像的反转处理可以增强暗色区域中的白色或灰色细节。
注意图像反转(Invert)与图像翻转(Flip)的区别:图像翻转是沿对称轴的几何变换,像素值不变;图像反转是像素颜色的逆转,像素位置不变。
#图像的反转:即图像像素取补
import cv2
import matplotlib.pyplot as plt
import numpy as np
img=cv2.imread(r'C:\Users\lenovo\Desktop\pythonn\dog_bike_car.jpg',flags=1)
img_gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
height,width,channel=img.shape
imginv=np.empty((height,width),np.uint8)#创建空白数组
for i in range(height):
for j in range(width):
imginv[i][j]=255-img_gray[i][j] #图像取补
# plt.figure(figsize=(9,6))
# titlelist=['1.BGR','2.GRAY','3,inv']
# imglist=[img,img_gray,imginv]
# for i in range(3):
# plt.subplot(1,3,i+1),plt.imshow(imglist[i]),plt.title(titlelist[i]),plt.axis('off')
# plt.show()
cv2.imshow('original',img)
cv2.imshow('gray',img_gray)
cv2.imshow('inv',imginv)
cv2.waitKey(0)
运行结果:
原图:
像素取补:
线性灰度变换将原始图像灰度值的动态范围按线性关系扩展到指定范围或整个动态范围。
线性灰度变化对图像的每一个像素作线性拉伸,可以凸显图像的细节,提高图像的对比度。
线性灰度变换可以由以下公式描述 :
式中,D 为原始图像的灰度值,Dt 为线性灰度变换后的图像灰度值。
#图像的线性灰度变换
import cv2
import matplotlib.pyplot as plt
import numpy as np
img=cv2.imread(r'C:\Users\lenovo\Desktop\pythonn\dog_bike_car.jpg',flags=1)
img_gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
h,w,ch=img.shape
img1=np.empty((h,w),np.uint8)
img2=np.empty((h,w),np.uint8)
img3=np.empty((h,w),np.uint8)
img4=np.empty((h,w),np.uint8)
img5=np.empty((h,w),np.uint8)
img6=np.empty((h,w),np.uint8)
img7=np.empty((h,w),np.uint8)
# Dt[i,j] = a*D[i,j] + b
a1,b1=1,0 # 保持原始图像不变
a2,b2=1,80 #图像的灰度值上移,灰度图像发白,彩色图像颜色发亮
a3,b3=1,-80#图像的灰度值下移,灰度图像发黑,彩色图像颜色变暗
a4=2 #图像对比度增强
a5=0.6 #图像对比度减弱
a6,b6=-0.5,255 #图像暗区域变亮,亮区域变暗,图像求补
a7,b7=-1,255 #图像反转
for i in range(h):
for j in range(w):
img1[i][j] = min(255,max(img_gray[i][j]+b1,0))
img2[i][j] = min(255, max(img_gray[i][j] + b2, 0))
img3[i][j] = min(255, max(img_gray[i][j] + b3, 0))
img4[i][j] = min(255, max(a4*img_gray[i][j], 0))
img5[i][j] = min(255, max(a5*img_gray[i][j], 0))
img6[i][j] = a6*img_gray[i][j] + b6
img7[i][j] = min(255, max(a7*img_gray[i][j] + b7, 0))
plt.figure(figsize=(9,6))
titlelist=['1.img_GRAY','2.a=1,b=0','3.a=1,b=80','4.a=1,b=-80','5.a=2','6.a=0.6','7.a=-0.5,b=255','8.a=-1,b=255']
imglist=[img_gray,img1,img2,img3,img4,img5,img6,img7]
for i in range(8):
plt.subplot(3,3,i+1),plt.imshow(imglist[i],cmap='gray'),plt.title(titlelist[i]),plt.axis('off')
plt.show()
运行结果:
分段线性变换函数可以增强图像各部分的反差,增强感兴趣的灰度区间、抑制不感兴趣的灰度级。
分段线性函数的优点是可以根据需要拉伸特征物的灰度细节,一些重要的变换只能用分段函数来描述和实现,缺点则是参数较多不容易确定。
分段线性函数通用公式如下:
式中,D 为原始图像的灰度值,Dt 为线性灰度变换后的图像灰度值。
#图像的分段线性变换:对比度拉伸
import cv2
import numpy as np
import matplotlib.pyplot as plt
img=cv2.imread(r'C:\Users\lenovo\Desktop\ExImage_beam_data_183925_00001.jpg',flags=0)#直接读取为灰度图像
h,w=img.shape
r1,s1=65,30
r2,s2=100,225
k1=s1/r1
k2=(s2-s1)/(r2-r1)
k3=(255-s2)/(255-r2)
imgStretch=np.empty((h,w),np.uint8)
for i in range(h):
for j in range(w):
if img[i][j]<r1:
imgStretch[i][j]=k1*img[i][j]
elif r1 <=img[i][j] <=r2:
imgStretch[i][j]=k2*(img[i][j]-r1)+s1
elif img[i][j]>r2:
imgStretch[i][j]=k3*(img[i][j]-r2)+s2
plt.subplots_adjust(left=0.2, bottom=0.2, right=0.9, top=0.8, wspace=0.1, hspace=0.1)
plt.subplot(131), plt.title("s=T(r)")
x = [0, 65, 100, 255]
y = [0, 30, 225, 255]
plt.plot(x, y)
plt.axis([0,256,0,256])
plt.text(65, 30, "(r1,s1)", fontsize=8)
plt.text(100, 215, "(r2,s2)", fontsize=8)
plt.xlabel("r, Input value")
plt.ylabel("s, Output value")
plt.subplot(132), plt.imshow(img, cmap='gray', vmin=0, vmax=255), plt.title("Original"), plt.axis('off')
plt.subplot(133), plt.imshow(imgStretch, cmap='gray', vmin=0, vmax=255), plt.title("Stretch"), plt.axis('off')
plt.show()
灰度级分层可以突出图像中特定的灰度级区间,可以对灰度级进行分层处理。
灰度级分层有两种常用方案:一种方案是二值处理,将感兴趣的灰度级区间设为较大的灰度值,其它区间设为较小的灰度值;另一种方案是窗口处理,将感兴趣的灰度级区间设为较大的灰度值,其它区间不变。
两种灰度级分层方案的分段变换公式分别为:
式中,D 为原始图像的灰度值,Dt1、Dt2 为灰度变换后的图像灰度值。
#灰度级分层
import cv2
import matplotlib.pyplot as plt
img_Gray=cv2.imread(r'C:\Users\lenovo\Desktop\pythonn\dog_bike_car.jpg',flags=0)
h,w=img_Gray.shape
a,b=15,150
img1=img_Gray.copy()
img1[(img1[:,:]<a ) | (img1[:,:]>b)]=0
img1[(img1[:, :] >= a ) & (img1[:, :] <=b)]=128
img2=img_Gray.copy()
img2[(img2[:,:]>=a) & (img2[:,:]<=b)]=225
plt.figure(figsize=(9,6))
plt.subplot(131),plt.imshow(img_Gray,cmap='gray'),plt.title('Gray'),plt.axis('off')
plt.subplot(132),plt.imshow(img1,cmap='gray'),plt.title('Binary layered'),plt.axis('off')
plt.subplot(133),plt.imshow(img2,cmap='gray'),plt.title('Grayscale layered'),plt.axis('off')
plt.show()
略过不学
对数变换可以由以下公式描述:
对数曲线在像素值较低的区域斜率大,在像素值较高的区域斜率小。对数变换将输入中范围较窄的低灰度值映射为范围较宽的灰度级,输入中的高灰度值则被映射为范围较窄的灰度级。对数变换后,较暗区域的对比度提升,可以增强图像的暗部细节。
对数变换实现了扩展低灰度值而压缩高灰度值的效果,广泛应用于频谱图像的显示中。对数变换的典型应用是傅立叶频谱的动态范围很宽,直接显示时受显示设备动态范围的限制而丢失大量的暗部细节;使用对数变换将图像的动态范围进行非线性压缩后,就可以清晰地显示。
#灰度变换,对数变换
import math
import cv2
import matplotlib.pyplot as plt
import numpy as np
img_gray=cv2.imread(r'C:\Users\lenovo\Desktop\ExImage_beam_data_183925_00001.jpg',flags=0)
h,w=img_gray.shape
c=5
img_new=np.empty((h,w),np.uint8)
for i in range(h):
for j in range(w):
img_new[i][j]=c*(math.log(1+img_gray[i][j]))
#经过log变换后0-255变成了0-5.5,需要在8比特的显示器中进行显示,则需要将其归一化规范化到(0,255)中,即使用normalize来实现
img_new = cv2.normalize(img_new,img_new,0,255,cv2.NORM_MINMAX)
plt.figure(figsize=(9,6))
plt.subplot(121),plt.imshow(img_gray,cmap='gray'),plt.title('GRAY'),plt.axis('off')
plt.subplot(122),plt.imshow(img_new,cmap='gray'),plt.title('LOG'),plt.axis('off')
plt.show()
幂律变换也称伽马变换,可以提升暗部细节,对发白(曝光过度)或过暗(曝光不足)的图片进行矫正。幂律变换也称伽马变换,可以提升暗部细节,对发白(曝光过度)或过暗(曝光不足)的图片进行矫正。
伽马变换本质上是对图像矩阵中的每个值进行幂运算。0< gamma <1 时,拉伸图像中灰度级较低的区域,压缩灰度级较高的部分,增加图像的对比度;gamma >1时,拉伸图像中灰度级较高的区域,压缩灰度级较低的部分,降低图像的对比度。
伽马变换通过非线性变换对人类视觉特性进行补偿,最大化地利用有效的灰度级带宽。很多拍摄、显示、打印设备的亮度曲线都符合幂律曲线,因此伽马变换广泛应用于各种设备显示效果的调校,称为伽马校正。
#图像的灰度变换-伽马变换
import cv2
import matplotlib.pyplot as plt
import numpy as np
img=cv2.imread(r'C:\Users\lenovo\Desktop\pythonn\dog_bike_car.jpg', flags=0)
gammalist=[0.125, 0.25, 0.5, 1.0, 2.0, 4.0]
plt.figure(figsize=(9,6))
for i in range(len(gammalist)):
img_gamma = np.power(img, gammalist[i]) #power(x, y) 函数,计算 x 的 y 次方
img_gamma=cv2.normalize(img_gamma,img_gamma,0,255,cv2.NORM_MINMAX)#cv2.normalize()按归一化到[0,255]
img_gamma = cv2.convertScaleAbs(img_gamma,img_gamma) #cv2.convertScaleAbs()函数 (通过线性变换将数据转换成8位[uint8])
plt.subplot(2,3,i+1),plt.imshow(img_gamma, cmap='gray'),plt.axis('off'),plt.title(f"$\gamma={gammalist[i]}$")
plt.show()
图像直方图是反映图像像素分布的统计表,横坐标代表像素值的取值区间,纵坐标代表每一像素值在图像中的像素总数或者所占的百分比。 灰度直方图是图像灰度级的函数,用来描述每个灰度级在图像矩阵中的像素个数。
灰度直方图反映了图像中的灰度分布规律,直观地表现了图像中各灰度级的占比,很好地体现出图像的亮度和对比度信息:灰度图分布居中说明亮度正常,偏左说明亮度较暗,偏右表明亮度较高;狭窄陡峭表明对比度降低,宽泛平缓表明对比度较高。
根据直方图的形态可以判断图像的质量,通过调控直方图的形态可以改善图像的质量。OpenCV 提供了函数 cv2.calcHist 可以计算直方图,Numpy 中的函数 np.bincount 也可以实现同样的功能。
cv2.calcHist(images, channels, mask, histSize, ranges[, hist[,> accumulate ]]) → hist
函数 cv2.calcHist 可以计算一维直方图或二维直方图,函数的参数 images, channels, histSize, ranges 在计算一维直方图时也要带 [] 号。
images:输入图像,用 [] 括号表示
channels: 直方图计算的通道,用 [] 括号表示
mask:掩模图像,一般置为 None
histSize:直方柱的数量,一般取 256
ranges:像素值的取值范围,一般为 [0,256]
返回值 hist:返回每一像素值在图像中的像素总数,形状为 (histSize,1)
注意:
#图像的灰度直方图 画出原图及经过伽马变换后的灰度直方图,gamma=0.6/对数变换 c=4
import cv2
import matplotlib.pyplot as plt
import numpy as np
img=cv2.imread(r'C:\Users\lenovo\Desktop\pythonn\dog_bike_car.jpg', flags=0)
hist=cv2.calcHist([img],[0],None,[256],[0,256])
plt.figure(figsize=(9,6))
plt.subplot(121),plt.axis('off'),plt.title('Origin'),plt.imshow(img,cmap='gray')
plt.subplot(122),plt.axis([0,255,0,np.max(hist)]),plt.bar(range(256),hist[:,0]),plt.title('Gray Hist')
plt.show()
原理部分不多阐述,OpenCV有一个函数可以这样做,cv.equalizeHist(),它封装好了计算cdf和cdf重映射以及根据cdf表生成直方图均衡图像的过程。它的输入只是灰度图像,输出是我们的直方图均衡图像。
语法:
cv2.qualizeHist(src[, dst]) → dst
#直方图均衡
import cv2
import matplotlib.pyplot as plt
import numpy as np
img=cv2.imread(r'C:\Users\lenovo\Desktop\pythonn\dog_bike_car.jpg', flags=0)
img_equal=cv2.equalizeHist(img)
calhist1=cv2.calcHist([img],[0],None,[256],[0,256])
calhist2=cv2.calcHist([img_equal],[0],None,[256],[0,256])
plt.figure(figsize=(30,20))
plt.subplot(221),plt.imshow(img,cmap='gray'),plt.title('Origin_img'),plt.axis('off')
plt.subplot(222,xticks=[], yticks=[]),plt.bar(range(256),calhist1[:,0]),plt.axis([0,255,0,np.max(calhist1)]),plt.title('The original Hist')
plt.subplot(223),plt.imshow(img_equal,cmap='gray'),plt.title('equalize_img'),plt.axis('off')
plt.subplot(224),plt.bar(range(256),calhist2[:,0]),plt.axis([0,255,0,np.max(calhist2)]),plt.title('The equalize Hist')
plt.show()
滤波通常是指对图像中特定频率的分量进行过滤或抑制。图像滤波是在尽可能保留图像细节特征的条件下对目标图像的噪声进行抑制,是常用的图像预处理操作。
数据采集都会带有一定的噪声,图像的噪声可以理解为灰度值的随机变化。对图像在空间域存在的随机噪声,可以通过平滑技术进行抑制或去除,称为空间域图像滤波。
频率域滤波是通过傅里叶变换方法实现的,而空间域滤波则是通过相关与卷积运算实现。常用的平滑处理算法有基于二维离散卷积的高斯平滑、均值平滑,基于统计方法的中值平滑,保留边缘信息的双边滤波、导向滤波等。
空间滤波器是由邻域和定义的操作构成的,滤波器规定了滤波时采用的邻域形状及该区域内像素值的处理方法。滤波器也被称为 “核”、“模板”、“窗口”、“掩模”、“算子”,一般在信号处理中称为 “滤波器”,在数学领域称为 “核”。线性滤波器就是指基于线性核的滤波,也就是卷积运算。
原理参考博客图像的相关与卷积运算
相关和卷积运算都要对图像的边界点要进行特殊处理,就需要将边界进行适当扩充。
OpenCV 中提供了函数 cv.copyMakeBorder 进行边界扩充方式,也可以为图像设置边框。
cv.copyMakeBorder(src, top, bottom, left, right, borderType[, dst[, value]]) → dst
#图像的相关与卷积运算
import cv2
import matplotlib.pyplot as plt
img=cv2.imread(r'C:\Users\lenovo\Desktop\pythonn\dog_bike_car.jpg', flags=0)
top = bottom = left = right = 50
img1=cv2.copyMakeBorder(img,top,bottom,left,right,borderType=cv2.BORDER_REPLICATE) #复制,复制最边缘像素进行填充,中值滤波采用复制法
img2=cv2.copyMakeBorder(img,top,bottom,left,right,borderType=cv2.BORDER_REFLECT)#对称法,以图像边缘为轴进行对称填充
img3=cv2.copyMakeBorder(img,top,bottom,left,right,borderType=cv2.BORDER_REFLECT_101)#倒映法,以图像最边缘像素为轴进行对称填充,filter2D, blur, GaussianBlur, bilateralFilter 中默认的边界处理方法
img4=cv2.copyMakeBorder(img,top,bottom,left,right,borderType=cv2.BORDER_WRAP)#用另一侧的元素来填充这一侧的元素
img5=cv2.copyMakeBorder(img,top,bottom,left,right,borderType=cv2.BORDER_CONSTANT,value=(128,128,128))
plt.figure(figsize=(9,6))
imglist=[img,img1,img2,img3,img4,img5]
titlelist=['1.Original','2.REPLICATE','3.REFLECT','4,REFLECT_101','5.WRAP','6.CONSTANT(value=128)']
for i in range(6):
plt.subplot(2,3,i+1),plt.imshow(imglist[i],cmap='gray'),plt.title(titlelist[i]),plt.axis('off')
plt.show()
运行结果:
Scipy 中提供了函数 sp.convolve2d 实现二维离散卷积的计算。
convolve2d(in1, in2, mode="full", boundary="fill", fillvalue=0) → dst
#scipy.signal 实现图像的二维卷积
import cv2
import matplotlib.pyplot as plt
import numpy as np
from scipy import signal
img=cv2.imread(r'C:\Users\lenovo\Desktop\pythonn\dog_bike_car.jpg', flags=0)
kernel=np.array([[-3-3j,0-10j,+3-3j],[-10+0j,0+0j,+10+0j], [-3+3j,0+10j,+3+3j]])
img1=signal.convolve2d(img,kernel,mode='full',boundary='symm') # full 卷积
img2=signal.convolve2d(img,kernel,mode='valid',boundary='symm')#valid卷积
img3=signal.convolve2d(img,kernel,mode='same',boundary='symm') #same卷积
print(img.shape,img1.shape,img2.shape,img3.shape)
plt.figure(figsize=(9,6))
imglist=[img,img1,img2,img3]
titlelist=['1.original','2.full_convolve2d','3.valid_convolve2d','4.same_convolve2d']
for i in range(4):
plt.subplot(2,2,i+1),plt.imshow(np.abs(imglist[i]),cmap='gray',vmin=0, vmax=255),plt.title(titlelist[i]),plt.axis('off')
plt.show()
运行结果:
注意:
使用 OpenCV 中的 cv.flip 和 cv.filter2D 函数也可以实现图像的卷积运算。
函数 cv.flip 实现围绕轴线翻转二维阵列,将图像沿轴线进行轴对称变换,可以将图像沿水平方向、垂直方向、或水平/垂直方向同时进行翻转
函数 cv.filter2D 对图像与核(模板)进行相关计算,与函数 cv.flip 共同实现卷积运算。
cv.filter2D(src, ddepth, kernel[, dst[, anchor[, delta[, borderType]]]]) → dst
#cv2 实现图像的二维卷积
import cv2
import numpy as np
import matplotlib.pyplot as plt
img=cv2.imread(r'C:\Users\lenovo\Desktop\pythonn\dog_bike_car.jpg', flags=0)
kernel= np.array([[-1, -1, -1], [-1, 9, -1], [-1, -1, -1]])
kernel_flip=cv2.flip(kernel,flipCode=-1) # 将卷积核旋转180 度
img1=cv2.filter2D(img,ddepth=-1,kernel=kernel_flip,anchor=[-1,-1],borderType=cv2.BORDER_REPLICATE)
img2=cv2.filter2D(img,ddepth=-1,kernel=kernel_flip,anchor=[-1,-1],borderType=cv2.BORDER_REFLECT_101)
plt.figure(figsize=(9,6))
imglist=[img,img1,img2]
titlelist=['1.original','2.REPLICATE_convolve2d','3.REFLECT_101_convolve2d']
for i in range(3):
plt.subplot(1,3,i+1),plt.imshow(np.abs(imglist[i]),cmap='gray',vmin=0, vmax=255),plt.title(titlelist[i]),plt.axis('off')
plt.show()
运行结果:
原理参考博客可分离卷积核
随着图像尺寸与卷积核尺寸的增大,用分离的卷积核依次对图像进行卷积操作,可以有效地提高运算速度。因此,在二维图像处理中,经常将一个可分离卷积核分解为一维水平核 kernalX 和一维垂直核 kernalY 的乘积。
函数 sepFilter2D 实现可分离核(模板)对图像进行线性滤波。
cv.sepFilter2D( src, ddepth, kernelX, kernelY[, dst[, anchor[, delta[, borderType]]]]) → dst # OpenCV4
中值滤波是一种非线性滤波方法,是基于统计排序方法的滤波器 。中值滤波法将像素点的邻域内的所有像素点灰度值的中值作为该像素点的灰度值。
注意中值不是平均值,而是按大小排序的中间值。由于需要排序操作,中值滤波消耗的运算时间很长。
中值滤波处理后像素点的灰度值,可能保持不变,也可能改变为邻域内其它像素点的灰度值。
中值滤波对于消除图像中的椒盐噪声非常有效。椒盐噪声也称为脉冲噪声,是随机出现的白点或者黑点,通常是由于影像讯号受到干扰而产生,如脉冲干扰、图像扫描。
OpenCV 提供了 cv.medianBlur 函数实现中值滤波算法。
cv.medianBlur(src, ksize[, dst]) → dst
#中值滤波
import cv2
import matplotlib.pyplot as plt
img=cv2.imread(r'C:\Users\lenovo\Desktop\pythonn\dog_bike_car.jpg', flags=1)
img1=cv2.medianBlur(img,3)
img2=cv2.medianBlur(img,5)
img3=cv2.medianBlur(img,9)
plt.figure(figsize=(9,6))
imglist=[img,img1,img2,img3]
titlelist=['1.Original','2.medianBlur(size=3)','3.medianBlur(size=5)','4.medianBlur(size=9)']
for i in range(4):
plt.subplot(2,2,i+1),plt.imshow(cv2.cvtColor(imglist[i],cv2.COLOR_BGR2RGB)),plt.title(titlelist[i]),plt.axis('off')
plt.show()
双边滤波是一种非线性滤波方法,是结合图像的空间邻近度和像素值相似度的一种折衷处理,同时考虑空域信息和灰度相似性,在去除噪声的同时有效地保持边缘清晰锐利,对于人像处理具有美颜功能。
边缘的灰度变化较大,高斯滤波会明显地模糊边缘,对于高频细节的保护较弱。双边滤波器在空间中也采用高斯滤波器,但增加了一个反映像素强度差异的高斯方差 σd在边缘附近离的较远的像素对边缘上的像素值影响很小,从而保证了边缘附近的像素值,实现边缘保存(edge preserving)。双边滤波器的权值是空间临近度权值和像素值相似度权值的乘积,因此输出像素依赖于当前被卷积像素的邻域,又取决于被卷积像素的灰度值和邻域像素的灰度值的差。
双边滤波器对于低频信息的滤波效果较好,但不能干净地过滤彩色图像里的高频噪声。
OpenCV 提供了 cv. bilateralFilter 函数可以实现图像的双边滤波。
cv.bilateralFilter(src, d, sigmaColor, sigmaSpace[, dst[, borderType]]) → dst