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:腐蚀操作的次数,默认为1
  • borderType:边界种类,有默认值
  • borderValue:边界值,有默认值

四.膨胀

dst = cv2.dilate(src,kernel,anchor,iterations,borderType,borderValue)       
  • src: 输入图像对象矩阵,为二值化图像
  • kernel:进行腐蚀操作的核,可以通过函数getStructuringElement()获得
  • anchor:锚点,默认为(-1,-1)
  • iterations:腐蚀操作的次数,默认为1
  • borderType:边界种类
  • 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:腐蚀操作的次数,默认为1
  • borderType: 边界种类
  • 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()

参考资料

【1】OpenCV-Python教程(11、轮廓检测)

【2】OpenCV-Python学习—形态学处理


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!