Opencv Q&A_9
2022/03/17-2022/03/18
基于opencv和比例算法的物体边长测量
算法详细:在实际长度已知的大背景板上放置被测物体,通过摄像头拍摄得到大背景和物体的分辨率比值,再由大背景的已知实际长度计算出物体实际边长
代码_length_measure.py(main)
import cv2 as cv import numpy as np from utlis import utlis scale = 3 #放大比例 height = 260 * scale #大背景分辨率 weight = 180 * scale #大背景分辨率 cap = cv.VideoCapture(1) cap.set(3,1920) cap.set(4,1080) cap.set(10,200) while True: flag,frame = cap.read() # allcon = utlis.find_all_contour(frame, 'test',contrast='low') #调试大背景检测阈值 allcon = utlis.find_all_contour(frame,(0,50),mode='certain',contrast='high') #固定大背景检测阈值,找出所有边缘 biggest = utlis.find_biggest_contour(allcon,100000) #找出最大边缘 if len(biggest) != 0: #如果找到了 pers = utlis.reorder(biggest, mod = 'perspective',count=1) #重新排序提供作透视变换 polys = utlis.reorder(biggest,mod = 'polylines',count=1) #重新排序提供多边形画框 polys = np.asarray(polys,np.int32) warp = cv.getPerspectiveTransform(np.float32(pers), np.float32([[0, 0], [weight, 0], [0, height], [weight, height]])) pers = cv.warpPerspective(frame, warp, (weight, height)) pers = pers[10:height-10,10:weight-10] #截取大背景部分,同时去除边缘干扰 # if cv.waitKey(0)&0xFF == ord('c'): # 调试物体检测阈值 # allcon = utlis.find_all_contour(pers, 'test', contrast='low') allcon = utlis.find_all_contour(pers, (0, 50), thres_=0,mode='certain', contrast='high') #在大背景中寻找物体边缘 biggest = utlis.find_biggest_contour(allcon, 5000) #找出物体最大边缘 if len(biggest) != 0: #如果找到了 polys = utlis.reorder(biggest, mod='polylines', count=1) polys = np.asarray(polys, np.int32) cv.arrowedLine(pers,(polys[0][0],polys[0][1]),(polys[1][0],polys[1][1]),(0,255,0),3) #带有箭头的直线 cv.arrowedLine(pers,(polys[0][0],polys[0][1]),(polys[3][0],polys[3][1]),(0,255,0),3) cv.circle(pers,(polys[0][0],polys[0][1]),2,(0,255,0),6) utlis.calcul_length(pers,round(1/30,3),polys) #计算实际长度 cv.imshow('per', pers) #显示大背景部分 cv.imshow('img', frame) #显示整个画面 cv.waitKey(1)
代码_utlis.py(function)
import cv2 as cv import numpy as np import math def nothing(x): pass def color_picker(img): ### 通过滑条进行实时选择阈值 cv.namedWindow('color_picker') cv.createTrackbar('lower_h', 'color_picker', 0, 180,nothing) cv.createTrackbar('lower_s', 'color_picker', 0, 255,nothing) cv.createTrackbar('lower_v', 'color_picker', 0, 255,nothing) cv.createTrackbar('upper_h', 'color_picker', 0, 180,nothing) cv.createTrackbar('upper_s', 'color_picker', 0, 255,nothing) cv.createTrackbar('upper_v', 'color_picker', 0, 255,nothing) while True: img_copy = img.copy() #保证原图img不会被覆盖,每一次操作都用img的副本img_copy lh = cv.getTrackbarPos('lower_h', 'color_picker') ls = cv.getTrackbarPos('lower_s', 'color_picker') lv = cv.getTrackbarPos('lower_v', 'color_picker') uh = cv.getTrackbarPos('upper_h', 'color_picker') us = cv.getTrackbarPos('upper_s', 'color_picker') uv = cv.getTrackbarPos('upper_v', 'color_picker') if lh < uh and ls < us and lv < uv: lower = np.array([lh,ls,lv]) upper = np.array([uh,us,uv]) img_copy = find_highlight(img_copy,lower,upper) print(lower) print(upper) cv.imshow('color_picker',img_copy) cv.waitKey(1) def find_highlight(img,lower,upper): ### 找出图片高亮区域 img = cv.cvtColor(img,cv.COLOR_BGR2HSV) mask = cv.inRange(img,lower,upper) #双阈值掩膜,在阈值范围外全为黑色(0,0,0),阈值范围内为白色(255,255,255) img_highlight = cv.bitwise_and(img,img,mask=mask) #位与操作截出阈值内(高亮)区域 return img_highlight def find_all_contour(img,threslist,thres_=0,mode='test',contrast='high'): ### 找出所有边缘 ### 当被检测物体与背景对比度不高时contrast选择'low',对比度高时contrast选择'high' ker = np.ones((3,3)) if mode == 'test': #canny阈值不确定时进行实时选择 cv.namedWindow('color_picker') cv.createTrackbar('lower_h', 'color_picker', 0, 255, nothing) cv.createTrackbar('lower_s', 'color_picker', 50, 255, nothing) while True: lh = cv.getTrackbarPos('lower_h', 'color_picker') ls = cv.getTrackbarPos('lower_s', 'color_picker') img_gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY) img_blur = cv.GaussianBlur(img_gray, (3,3), 0) if contrast == 'low': ret, img_thre = cv.threshold(img_blur, lh,255, cv.THRESH_BINARY) #对比度不高时使用二值化增强边缘 cv.imshow('color_picker', img_thre) cv.waitKey(1) if contrast == 'high': img_canny = cv.Canny(img_blur, lh, ls) cv.imshow('color_picker1', img_canny) img_ex = cv.morphologyEx(img_canny,cv.MORPH_CLOSE,ker,iterations=2) cv.imshow('color_picker', img_ex) cv.waitKey(1) if mode == 'certain': #canny阈值确定时使用 img_gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY) img_blur = cv.GaussianBlur(img_gray, (3,3), 0) if contrast == 'low': ret, img_thre = cv.threshold(img_blur, thres_, 255, cv.THRESH_BINARY) img_contour, hierarchy = cv.findContours(img_thre, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE) if contrast == 'high': img_canny = cv.Canny(img_blur, threslist[0],threslist[1]) img_ex = cv.morphologyEx(img_canny,cv.MORPH_CLOSE,ker,iterations=2) img_contour, hierarchy = cv.findContours(img_ex, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE) return img_contour def find_biggest_contour(contours,minArea,accuracy=0.02): ### 得到最大边框 Max_area = 0 biggest_contour = [] for contour in contours: area = cv.contourArea(contour) length = cv.arcLength(contour,True) lines = cv.approxPolyDP(contour,length*accuracy,True) if area > minArea and area > Max_area and len(lines) == 4: Max_area = area biggest_contour = lines return biggest_contour def find_right_contour(contours,minArea,filter=4,accuracy=0.01): ### 得到满足条件的曲线近似多边形框和最小矩形框合集 biggest_contour = [] for contour in contours: area = cv.contourArea(contour) length = cv.arcLength(contour,True) lines = cv.approxPolyDP(contour,length*accuracy,True) #accuracy太小影响结果 rect = cv.boundingRect(lines) if area > minArea and len(lines) == filter: biggest_contour.append([lines,rect]) #合成列表输出 return biggest_contour def reorder(locations,mod,count=1): ## 输入最大边框(↖↙↘↗)重新编排坐标信息顺序 lists = [] if count != 1: #当有一组元素个数大于1个的数组使用 for location in locations: list = [] if mod == 'perspective': # 透视使用:↖↙↗↘ list.append(location[0][0]) list.append(location[1][0]) list.append(location[3][0]) list.append(location[2][0]) if mod == 'polylines': # 多边形使用:↖↗↘↙ list.append(location[0][0]) list.append(location[3][0]) list.append(location[2][0]) list.append(location[1][0]) lists.append(list) if count == 1: #当只有一个元素使用 if mod == 'perspective': # 透视使用:↖↙↗↘ lists.append(locations[0][0]) lists.append(locations[1][0]) lists.append(locations[3][0]) lists.append(locations[2][0]) if mod == 'polylines': # 多边形使用:↖↗↘↙ lists.append(locations[0][0]) lists.append(locations[3][0]) lists.append(locations[2][0]) lists.append(locations[1][0]) return lists def draw_bounding(img,rects,mode): ### 画最小边框,或单独抽出框内图像 if mode == 'bounding': #画最小边框 for rect in rects: x, y, w, h = rect[1] #0为曲线近似边框,1位最小矩形边框 cv.rectangle(img, (x, y), (x + w, y + h), (255, 0, 0), 2) if mode == 'extract': #抽出框内图像返回图像合集 img_list = [] for rect in rects: x, y, w, h = rect[1] # 0为曲线近似边框,1位最小矩形边框 img_extract = img[y:y+h,x:x+w] img_list.append(img_extract) return img_list def calcul_length(img,backg_prop,polys): ### 计算四边形物体的边长,并显示长宽。算法为比例算法 for n in range(len(polys)): if n == 3: m = 0 #当n为最后一条边时,下一条返回第一条边 else: m = n + 1 length = backg_prop * math.sqrt(pow(polys[n][0] - polys[m][0], 2) + pow(polys[n][1] - polys[m][1], 2)) if n == 0 or n == 3: cv.putText(img,str(round(length,1)) + ' cm',(polys[n][0]-35,polys[n][1]+50),cv.FONT_HERSHEY_SIMPLEX,2,(255,100,255),3)
运行效果
遇到的问题
Q1:一开始识别边缘时总是只能识别到最外层(最大)边缘
A1:使用cv.findContours()函数进行边缘识别时如需找到所有边缘则需选择mode = cv.RETR_TREE,如果选择了 mode = cv.RETR_EXTERNAL则只会识别最外层(最大)边缘
Q2:很难识别出边缘
A2:当大背景板和周围环境、大背景板和被测物体对比度过低时,canny使用较麻烦,很难正确识别出边缘,此时可以折中使用二值化函数cv.threshold(),type=cv.THRESH_BINARY增强对比度,但缺点是环境复杂时可能对阈值的选择产生波动。两者需权衡使用