Toriyung

导航

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增强对比度,但缺点是环境复杂时可能对阈值的选择产生波动。两者需权衡使用

 

posted on 2022-03-18 17:19  Toriyung  阅读(37)  评论(0编辑  收藏  举报