OpenCV图像处理基础

图像基本操作

图像读取

  • openCV按照BGR格式读取数据,需要使用pyplot展示时需要转换为RGB模式
  • cv2.cvtColor函数提供图像通道转换功能
1
2
3
4
5
6
7
image=cv2.imread('image.png')
img_RGB = cv2.cvtColor(image,cv2.COLOR_BGR2RGB)
img_gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
img_Lab = cv2.cvtColor(image, cv2.COLOR_BGR2Lab)
#Lab通道是亮度、红绿、黄蓝通道,某些业务中使用比RGB通道有更好的效果
img_Lab = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
#HSV通道色调、饱和度、强度

使用pyplot进行展示

1
2
3
4
plt.imshow(img_RGB)
plt.show()
plt.imshow(img_gray, cmap ='gray') #灰度图展示
plt.show()

截取图像

与多维数组裁剪方式一样

1
img_cut = img_RGB[25:100,50:200]

通道提取与合并

  • 使用openCV自带的split函数进行通道提取
  • 使用merge函数进行合并
1
2
3
4
5
6
R,G,B = cv2.split(img_RGB)
img_RGB_merge = cv2.merge((R,G,B))
#仅保留一个通道,例仅保留R
img_R = img_RGB.copy()
img_R[:,:,1] = 0
img_R[:,:,2] = 0

边界扩充

  • BORDER_REPLICATE:复制法,也就是复制最边缘像素。
  • BORDER_REFLECT:反射法,对感兴趣的图像中的像素在两边进行复制例如:fedcba|abcdefgh|hgfedcb
  • BORDER_REFLECT_101:反射法,也就是以最边缘像素为轴,对称,gfedcb|abcdefgh|gfedcba
  • BORDER_WRAP:外包装法cdefgh|abcdefgh|abcdefg
  • BORDER_CONSTANT:常量法,常数值填充。
1
2
3
4
5
6
7
top_size,bottom_size,left_size,right_size = (50,50,50,50)

replicate = cv2.copyMakeBorder(img_RGB, top_size, bottom_size, left_size, right_size, borderType=cv2.BORDER_REPLICATE)
reflect = cv2.copyMakeBorder(img_RGB, top_size, bottom_size, left_size, right_size,cv2.BORDER_REFLECT)
reflect101 = cv2.copyMakeBorder(img_RGB, top_size, bottom_size, left_size, right_size, cv2.BORDER_REFLECT_101)
wrap = cv2.copyMakeBorder(img_RGB, top_size, bottom_size, left_size, right_size, cv2.BORDER_WRAP)
constant = cv2.copyMakeBorder(img_RGB, top_size, bottom_size, left_size, right_size,cv2.BORDER_CONSTANT, value=0)

图片数值计算

两图片直接相加 相当于%256

1
img1 + img2

使用cv2.add函数相加大于255时直接赋值255

1
cv2.add(img1,img2)

更改图片尺寸

  • cv2.resize()函数进行图片缩放时,输入参数中的dsize,即输出图片的大小,顺序应为(w, h),和cv2.imread()读入图片的顺序相反,cv2.imread()读入图片的通道顺序为(h, w, c)。
1
2
img_resize = cv2.resize(img_RGB, (100, 100))
img_resize2 = cv2.resize(img_RGB, (0, 0), fx=2, fy=3)

图片融合

需要两张相同大小的图片按照权重进行融合,使用cv2.addWeighted函数。

在将尺寸不同的图片进行融合时要注意resize时的参数顺序

1
2
3
4
5
image2 = cv2.imread('source/testimg.png')
img2_RGB = cv2.cvtColor(image2,cv2.COLOR_BGR2RGB)
img2_resize = cv2.resize(img2_RGB, (int(img_RGB.shape[1]), int(img_RGB.shape[0])))
print(img2_resize.shape,img_RGB.shape)
img1&2 = cv2.addWeighted(img_RGB, 0.4, img2_resize, 0.6, 0)

图像处理基本操作

图像阀值处理(二值化)

  1. threshold 固定阀值二值化
1
ret, dst = cv2.threshold(src, thresh, maxval, type)
  • dst: 输出图
  • ret/thresh: 阈值
  • src: 输入图,只能输入单通道图像,通常来说为灰度图
  • maxval: 当像素值超过了阈值(或者小于阈值,根据type来决定),所赋予的值
  • type:二值化操作的类型,包含以下5种类型: cv2.THRESH_BINARY; cv2.THRESH_BINARY_INV; cv2.THRESH_TRUNC; cv2.THRESH_TOZERO;cv2.THRESH_TOZERO_INV
  • cv2.THRESH_BINARY 超过阈值部分取maxval(最大值),否则取0
  • cv2.THRESH_BINARY_INV THRESH_BINARY的反转
  • cv2.THRESH_TRUNC 大于阈值部分设为阈值,否则不变
  • cv2.THRESH_TOZERO 大于阈值部分不改变,否则设为0
  • cv2.THRESH_TOZERO_INV THRESH_TOZERO的反转
1
2
3
4
5
ret, thresh1 = cv2.threshold(img3_gray, 200, 255, cv2.THRESH_BINARY)
ret, thresh2 = cv2.threshold(img3_gray, 200, 255, cv2.THRESH_BINARY_INV)
ret, thresh3 = cv2.threshold(img3_gray, 200, 255, cv2.THRESH_TRUNC)
ret, thresh4 = cv2.threshold(img3_gray, 200, 255, cv2.THRESH_TOZERO)
ret, thresh5 = cv2.threshold(img3_gray, 200, 255, cv2.THRESH_TOZERO_INV)

  1. threshold + OTSU 基于直方图的二值化阀值
1
ret, dst = cv2.threshold(src, 0, maxval, type + cv2.THRESH_OTSU)
  • 不需要手动设置阀值,根据直方图自动设置阀值,thresh填0即可
1
2
3
re3, th4 = cv2.threshold(img_gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
print('re3 =',re3)
# re3 = 198.0 自动设置阀值为198.0

  1. adaptiveThreshold 自适应阀值二值化
  • 自适应阀值法不计算全局图像阀值,是根据图像不同区域亮度分布,计算局部阀值,对于亮度不同的区域自适应计算不同阀值。
1
cv2.adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C)
  • thresholdType : 二值化操作的类型 只能使用cv2.THRESH_BINARY 或 cv2.THRESH_BINARY_INV
  • blockSize : 像素邻域的大小,用来计算像素的阈值,blockSize必须为奇数
  • C : 从平均数或加权平均数减去的常量。通常,它是正的,但也可能是零或负数。
  • adaptiveMethod : 自适应阈值的方法共两个
  • cv2.ADAPTIVE_THRESH_MEAN_C 阈值T(x,y)是(x,y)减去C的Blocksize×Blocksize邻域的平均值。
  • cv2.ADAPTIVE_THRESH_GAUSSIAN_C 阈值T(x,y)是(x,y)减去C的Blocksize×Blocksize邻域的加权和(与高斯相关),默认sigma(标准差)用于指定的Blocksize

图像平滑

  1. 均值滤波:平均卷积操作
1
blur = cv2.blur(img, (3, 3))
  1. 方框滤波(可以归一化的均值滤波)
1
2
box = cv2.boxFilter(img4_RGB,-1,(3,3), normalize=True)
box_F = cv2.boxFilter(img4_RGB,-1,(3,3), normalize=False)
  • 第二个参数为目标图像深度,-1表示与原始图像一致
  • normalize为true 时与均值滤波一样,为false时表示任意一个点的像素为周围像素点的和,容易发生溢出超过255
  1. 高斯滤波:高斯模糊的卷积核里的数值是满足高斯分布的,相当于更重视中间的
1
gaussian = cv2.GaussianBlur(img, (5, 5), 1)
  1. 中值滤波:用中值代替
1
median = cv2.medianBlur(img, 5)

形态学操作

  1. 腐蚀操作
  • 它沿着物体边界移除像素并缩小物体的大小,会增强图像的暗部。
  • iterations 为 迭代次数
1
2
kernel = np.ones((3,3),np.uint8)
erosion = cv2.erode(img5_GRAY,kernel,iterations = 3)

  1. 膨胀操作
  • 通过将像素添加到该图像中的对象的感知边界,扩张放大图像中的明亮白色区域。
1
dige_dilate = cv2.dilate(erosion,kernel,iterations = 3)

  1. 开运算与闭运算
  • 开运算:先腐蚀,再膨胀。 去除白色小信息
  • 闭运算:先膨胀,再腐蚀。 加强白色小信息
  • 使用形态学操作函数cv2.morphologyEx()
1
closing = cv2.morphologyEx(img, op, kernel, dst=None, anchor=None, iterations=None, borderType=None, borderValue=None)
  • op 开运算或闭运算操作 cv2.MORPH_OPEN 或 cv2.MORPH_CLOSE
  • iterations 迭代次数 默认为1 即膨胀和腐蚀次数
1
opening = cv2.morphologyEx(img5_GRAY, cv2.MORPH_OPEN, kernel, iterations=3)

  1. 顶帽与黑帽
  • 礼帽 = 原始输入-开运算结果
  • 黑帽 = 闭运算-原始输入
  • 顶帽与黑帽同样使用形态学操作函数cv2.morphologyEx()

  • op 顶帽与黑帽操作 cv2.MORPH_TOPHAT 或 cv2.MORPH_BLACKHAT

1
tophat = cv2.morphologyEx(img5_GRAY,  cv2.MORPH_TOPHAT, kernel ,iterations=3)

Canny边缘检测

  • 使用高斯滤波器,以平滑图像,滤除噪声。
  • 计算图像中每个像素点的梯度强度和方向。
  • 应用非极大值(Non-Maximum Suppression)抑制,以消除边缘检测带来的杂散响应。
  • 应用双阈值(Double-Threshold)检测来确定真实的和潜在的边缘。
    • 梯度值>maxVal:处理为强边缘边界
    • minVal<梯度值<maxVal:与强边缘相连则保留,否则舍弃
    • 提督值<minVal:弱边缘舍弃
  • 通过抑制孤立的弱边缘最终完成边缘检测。
  • 明显轮廓断裂
1
edges = cv.Canny(image, minVal, maxVal ,apertureSize=None, L2gradient=None)
  • edges: 计算得到的边缘图像
  • minval: 弱边缘阀值
  • maxval: 强边缘阀值
  • apertureSize: Sobel算子孔径大小
  • L2gradient:计算图像梯度幅度(gradient magnitude)的标识。如果为 True,则使用更精确的 L2 范数进行计算(即两个方向的导数的平方和再开方)
1
2
edge1=cv2.Canny(image5,100,150)
edge2=cv2.Canny(image5,200,400)

图像轮廓

  1. 轮廓检测
1
contours,hierarchy = cv2.findContours(img,mode,method)
  • contours:轮廓坐标数组
  • mode:轮廓检索模式

    • RETR_EXTERNAL :只检索最外面的轮廓;
    • RETR_LIST:检索所有的轮廓,并将其保存到一条链表当中;
    • RETR_CCOMP:检索所有的轮廓,并将他们组织为两层:顶层是各部分的外部边界,第二层是空洞的边界;
    • RETR_TREE:检索所有的轮廓,并重构嵌套轮廓的整个层次;
  • method:轮廓逼近方法

    • CHAIN_APPROX_NONE:以Freeman链码的方式输出轮廓,所有其他方法输出多边形(顶点的序列)。
    • CHAIN_APPROX_SIMPLE:压缩水平的、垂直的和斜的部分,也就是,函数只保留他们的终点部分。
  • 一般使用二值图像提高准确率
  1. 轮廓绘制
1
res = cv2.drawContours(image, contours, contourIdx, color, thickness=None, lineType=None, hierarchy=None, maxLevel=None, offset=None)
  • contours:轮廓list
  • contourIdx:需要绘制的轮廓在contours的list中的索引,-1为默认绘制所有轮廓
  • color:为轮廓线颜色
  • thickness:为轮廓线宽度
  • hierarchy:绘制分层轮廓时使用
1
2
3
4
contours,hierarchy = cv2.findContours(img5_GRAY, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
print(hierarchy,contours)
draw_img = img5_RGB.copy()
res = cv2.drawContours(draw_img, contours, -1, (255, 0, 0), 2)

  1. 根据轮廓操作
  • 轮廓周长
1
length = cv2.arcLength(cnt,closed = True)

closed为是否闭合,True为闭合

  • 计算最大轮廓
1
2
3
4
5
6
7
8
9
def findMaxContour(contours):
largeCnt = []
maxLength = 0
for c in contours:
# 计算轮廓近似
if maxLength<cv2.arcLength(c, True):
maxLength = cv2.arcLength(c, True)
largeCnt = c
return maxLength,largeCnt

maxLength:最大轮廓长度

largeCnt:最大轮廓array

  • 轮廓面积
1
area = cv2.contourArea(cnt)
  • 外接圆
1
2
3
(x,y),radius = cv2.minEnclosingCircle(cnt) 
center = (int(x),int(y)) #圆心
radius
= int(radius) #半径
  • 外接矩形
1
x,y,w,h = cv2.boundingRect(cnt)

x:右上x坐标 y:右上y坐标 w:宽度 h:高度

傅立叶变换

  • 高频:变化剧烈的灰度分量,比如边界
  • 低频:变化缓慢的灰度分量,比如背景
  • 高通滤波器:只保留高频,使图像细节增强
  • 低通滤波器:只保留高频,使图像模糊
  • 进行傅立叶变换得到频域结果使用cv2.dft(),cv2.idft(),输入图像需要先转换成np.float32 格式。
  • 得到的结果中频率为0的部分会在左上角,通常要转换到中心位置,可以通过shift变换来实现。
  • cv2.dft()返回的结果是双通道的(实部,虚部),通常还需要转换成图像格式才能展示(0,255)。
  1. 傅立叶变换
1
2
3
4
5
6
7
8
img7_float32 = np.float32(image7)
dft = cv2.dft(img7_float32, flags = cv2.DFT_COMPLEX_OUTPUT)
# 将结果中边角的转换到中心位置,即将低频值转换到中间
dft_shift = np.fft.fftshift(dft)
# 得到灰度图能表示的形式
# 先通过cv2.magnitude对两个通道进行转换,因为转换后的结果数值较小所以通过公式转换至0-255
magnitude_spectrum = 20*np.log(cv2.magnitude(dft_shift[:,:,0],dft_shift[:,:,1]))
dft_spectrum = 20*np.log(cv2.magnitude(dft[:,:,0],dft[:,:,1]))

  1. 低通滤波器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
mg7_float32 = np.float32(image7)
dft = cv2.dft(img7_float32, flags = cv2.DFT_COMPLEX_OUTPUT)
dft_shift = np.fft.fftshift(dft)

rows, cols = image7.shape
crow, ccol = int(rows/2) , int(cols/2) # 中心位置

# 低通滤波 取中心点周围30的矩形以内的点
mask = np.zeros((rows, cols, 2), np.uint8)
mask[crow-30:crow+30, ccol-30:ccol+30] = 1

# IDFT
fshift = dft_shift*mask
f_ishift = np.fft.ifftshift(fshift)
img_back = cv2.idft(f_ishift)
img_back = cv2.magnitude(img_back[:,:,0],img_back[:,:,1])

  1. 高通滤波器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
img7_float32 = np.float32(image7)
dft = cv2.dft(img7_float32, flags = cv2.DFT_COMPLEX_OUTPUT)
dft_shift = np.fft.fftshift(dft)


rows, cols = image7.shape
crow, ccol = int(rows/2) , int(cols/2) # 中心位置

# 高通滤波 取中心点周围30的矩形以外的点
mask = np.ones((rows, cols, 2), np.uint8)
mask[crow-30:crow+30, ccol-30:ccol+30] = 0

# IDFT
fshift = dft_shift*mask
f_ishift = np.fft.ifftshift(fshift)
img_back = cv2.idft(f_ishift)
img_back = cv2.magnitude(img_back[:,:,0],img_back[:,:,1])

Canny、形态学运算、轮廓检测应用

Canny轮廓检测会有间断,所以使用形态学闭运算先膨胀再腐蚀后再进行轮廓检测会有较好效果

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
import cv2
import numpy as np
import matplotlib.pyplot as plt

img = cv2.imread("source/test17.png") #0表示灰度图

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
image = cv2.cvtColor(img,cv2.COLOR_BGR2RGB)

gray = cv2.GaussianBlur(gray, (5, 5), 0)
edged = cv2.Canny(gray, 15,30)

def contourLength(cnt):
return cv2.arcLength(cnt,closed = True)

#Canny后直接边缘检测
contours,hierarchy = cv2.findContours(edged, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = sorted(contours, key = contourLength, reverse = True)[:3]
draw_img = image.copy()
cv2.drawContours(draw_img, cnts, -1, (0, 255, 0), 1)

#Canny后进行闭运算后再进行轮廓检测
kernel = np.ones((3,3),np.uint8)
closing = cv2.morphologyEx(edged, cv2.MORPH_CLOSE, kernel)

contours_close,hierarchy_close = cv2.findContours(closing.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = sorted(contours_close, key = contourLength, reverse = True)[:3]
draw_img_close = image.copy()
cv2.drawContours(draw_img_close, cnts, -1, (0, 255, 0), 1)

plt.subplot(231), plt.imshow(image), plt.title('ORIGINAL')
plt.subplot(232), plt.imshow(edged,'gray'), plt.title('CANNY')
plt.subplot(233), plt.imshow(draw_img), plt.title('CONTOUR')
plt.subplot(234), plt.imshow(gray,'gray'), plt.title('ORIGINAL_GRAY')
plt.subplot(235), plt.imshow(closing,'gray'), plt.title('CANNY_CLOSE')
plt.subplot(236), plt.imshow(draw_img_close), plt.title('CONTOUR_CLOSE')
plt.show()

打赏
  • 版权声明: 本博客所有文章除特别声明外,均采用 Apache License 2.0 许可协议。转载请注明出处!

请我喝杯咖啡吧~

支付宝
微信