OpenCV学习笔记
本文最后更新于:2021年1月8日 晚上
由于图像分析大作业用到了Opencv,临时学了一点点,特此记录
一.获取轮廓
image , contours , hierarchy = cv2.findContours ( binary , cv2.RETR_EXTERNAL , cv.CHAIN_APPROX_SIMPLE )
binary
:寻找轮廓的图像(二值图像)mode
:轮廓的检索模式,如cv2.RETR_EXTERNAL
表示只检测外轮廓method
:轮廓的近似方法,如cv2.CHAIN_APPROX_SIMPLE
压缩水平方向,垂直方向,对角线方向的元素,只保留该方向的终点坐标,例如一个矩形轮廓只需4个点来保存轮廓信息
返回值contour是list类型;返回值hierarchy表示每条轮廓对应的属性
二.生成最小外接矩形
cnt = np.array([[x1,y1],[x2,y2],[x3,y3],[x4,y4]]) # 必须是array数组的形式
rect = cv2.minAreaRect(cnt)
box = cv2.BoxPoints(rect) # 获取最小外接矩形的4个顶点坐标
返回值为最小外接矩形的 (中心点坐标(x,y),(矩形宽度、矩形高度),矩形旋转角度)
三.腐蚀
dst=cv2.erode(src,kernel,anchor,iterations,borderType,borderValue):
kernel=cv2.getStructuringElement(shape,ksize,anchor)
# shape:核的形状
#cv2.MORPH_RECT: 矩形
#cv2.MORPH_CROSS: 十字形(以矩形的锚点为中心的十字架)
#cv2.MORPH_ELLIPSE:椭圆(矩形的内切椭圆)
# ksize: 核的大小,矩形的宽,高格式为(width,height)
# anchor: 核的锚点,默认值为(-1,-1),即核的中心点
src
: 输入图像对象矩阵,为二值化图像kernel
:进行腐蚀操作的核,可以通过函数getStructuringElement()获得anchor
:锚点,默认为(-1,-1)iterations
:腐蚀操作的次数,默认为1borderType
:边界种类,有默认值borderValue
:边界值,有默认值
四.膨胀
dst = cv2.dilate(src,kernel,anchor,iterations,borderType,borderValue)
src
: 输入图像对象矩阵,为二值化图像kernel
:进行腐蚀操作的核,可以通过函数getStructuringElement()获得anchor
:锚点,默认为(-1,-1)iterations
:腐蚀操作的次数,默认为1borderType
:边界种类borderValue
:边界值
五.开运算、闭运算等
dst = cv2.morphologyEx(src,op,kernel,anchor,iterations,borderType,borderValue)
src
: 输入图像对象矩阵,为二值化图像op
: 形态学操作类型- cv2.MORPH_OPEN 开运算,先腐蚀后膨胀,去除一些较亮的部分
- cv2.MORPH_CLOSE 闭运算,先膨胀后腐蚀,去除一些较暗的部分
- cv2.MORPH_GRADIENT 形态梯度,膨胀运算结果减去腐蚀运算结果,获得轮廓信息
- cv2.MORPH_TOPHAT 顶帽运算,原图像减去开运算
- cv2.MORPH_BLACKHAT 底帽运算,原图像减去闭运算
kernel
:进行腐蚀操作的核,可以通过函数getStructuringElement()
获得anchor
:锚点,默认为(-1,-1)iterations
:腐蚀操作的次数,默认为1borderType
: 边界种类borderValue
:边界值
其他
import cv2 as cv
import numpy as np
src = cv.imread("C:/Users/GWL/Desktop/Final project/Final project_CE&M/Switch.png")
cv.imshow("input", src)
src_height, src_width = src.shape[:2] # 高度*宽度*维度
# 图像二值化
gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
ret, binary = cv.threshold(gray, 0, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)
cv.imshow("binary", binary)
# 轮廓提取, 发现最大轮廓
_,contours, _ = cv.findContours(binary, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE) #返回 图像,轮廓,轮廓间的关系
index = 0
area_max = 0
for c in range(len(contours)):
area = cv.contourArea(contours[c])
if area > area_max:
area_max = area #得到最大轮廓所包含的面积
index = c #得到最大轮廓的索引
# 寻找最小外接矩形
rect = cv.minAreaRect(contours[index]) # 获取最小外接矩形的 (中心点坐标(x,y),(矩形宽度、矩形高度),矩形旋转角度)
width, height = rect[1] #获取最小外接矩形的宽度和高度
src_pts = cv.boxPoints(rect) # 获取最小外接矩形的4个顶点坐标
# print('Minimum circumscribed rectangle vertex coordinates:\n',np.array(src_pts),'\n')
#进行投影变换,矫正畸变图像
dst_pts = np.array([[0, height-1], [0, 0], [width-1, 0], [width-1, height-1]], np.float32)
M = cv.getPerspectiveTransform(np.array(src_pts), dst_pts) # 获取 原来四个点(src_pts)到现在四个点(dst_pts) 的变换矩阵M
warpPerspective_result = cv.warpPerspective(src, M, (np.int32(width), np.int32(height))) # 投影变换,将图片中的ROI区域单独透视变换成输出result
if height > width:
warpPerspective_result = cv.rotate(warpPerspective_result, cv.ROTATE_90_CLOCKWISE)
cv.imshow("warpPerspective_result", warpPerspective_result)
# 将矫正后的图像进行二值化
_, binary2 = cv.threshold(warpPerspective_result, 210, 255, cv.THRESH_BINARY)
cv.imshow("warpPerspective_result_binary", binary2)
# 对二值化的图像进行开运算,去除小团块目标(一般比背景亮)
kernel = cv.getStructuringElement(cv.MORPH_RECT, (5, 5), (-1, -1)) #shape,核的形状;ksize,核的大小,(width,height);anchor,核的锚点,默认值为(-1,-1),即核的中心点
open_result = cv.morphologyEx(binary2, cv.MORPH_OPEN, kernel) #宽度*高度*维度,已经不再是单纯的二值图像了
cv.imshow("open_result", open_result)
# 转为二值图像,便于后续输入到cv.findContours中
open_gray = cv.cvtColor(open_result, cv.COLOR_BGR2GRAY)
_, open_binary = cv.threshold(open_gray, 0, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)
# 轮廓发现
_, contours2, _ = cv.findContours(open_binary, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE) #只检测外轮廓,输出终点坐标
for i in range(0,len(contours2)):
x, y, w, h = cv.boundingRect(contours2[i])
cv.rectangle(open_binary, (x,y), (x+w,y+h), (153,153,0), 3) #绘制轮廓
# 获取各个轮廓的质心坐标
dict1 = {}
dict2 = {}
for c in range(len(contours2)):
mm = cv.moments(contours2[c]) # 获取轮廓的特征矩,mm类型为dict
m00 = mm['m00']
m10 = mm['m10']
m01 = mm['m01']
cx = np.int(m10 / m00)
cy = np.int(m01 / m00)
cv.circle(open_binary, (cx, cy), 1, (0, 0, 255), 2) #绘制质心
cx1 = {c: cx} # key为轮廓索引,value为质心横/纵坐标
cy1 = {c: cy}
dict1.update(cx1) # 横坐标
dict2.update(cy1) # 纵坐标
# cv.circle(open_binary, (0, 0), 1, (255,255,255), 2) # 水平为x,竖直为y,因此可以按照质心的纵坐标确定开关码为0还是1
# cv.circle(open_binary, (0, 20), 1, (255,255,255), 2)
cv.imshow("open_binary_contours", open_binary)
# 按照 横坐标大小 对质心坐标进行排序,确定轮廓的左右顺序,从而便于输出开关码
cor = sorted(dict1.items(), key = lambda d:d[1])
print('按照横坐标大小排序后的质心横坐标:',cor)
print('质心纵坐标及其索引:',dict2)
cv.putText(src, "Switch code:", (10, np.int32(src_height - 20)),cv.FONT_HERSHEY_SIMPLEX, .5, (55,255,155), 1);
# 通过质心的纵坐标确定开关码为0还是1
for i in range(len(contours2)):
code = dict2[cor[i][0]] #通过key来寻址,找到对应的纵坐标
if 50 < code < 70:
cv.putText(src, "0", (np.int32(120 + i*11), np.int32(src_height - 20)),cv.FONT_HERSHEY_SIMPLEX, .5, (55,255,155), 2);
elif 40 < code < 50:
cv.putText(src, "1", (np.int32(120 + i*11), np.int32(src_height - 20)),cv.FONT_HERSHEY_SIMPLEX, .5, (55,255,155), 1);
# 绘制转换码表的最大外接矩形
x1, y1, w1, h1 = cv.boundingRect(contours[index])
cv.rectangle(src, (x1, y1), (x1 + w1, y1 + h1), (55,255,155), 2)
cv.imshow("final_result", src)
cv.waitKey(0)
cv.destroyAllWindows()
参考资料
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!