指针式仪表识别读数 Python(已开源数据集)

目录

  • 一、前言
  • 二、使用方法
    • 1.安装相关的库
    • 2.运行
  • 三、方法说明
    • MeterDetection类说明
      • 类参数
      • 主函数
      • self.ImgCutCircle() 截取表盘区域,滤除背景
      • self.ContoursFilter() 对轮廓进行筛选
      • self.FitNumLine()轮廓拟合直线
      • self.getIntersectionPoints()获取刻度线交点
      • self.FitPointerLine()拟合指针直线段
      • 读数
  • 总结

一、前言

第一个版本的指针式仪表识别读数程序写得有点乱,有空重新整理下代码,写成了类MeterDetection进行封装。
原始指针仪表识别地址
基于深度学习方法的指针识别
程序地址:指针式仪表读数源码github
数据集为coco.zip,在github中
记得点个star

二、使用方法

1.安装相关的库

pip install -r requirments.txt

numpy==1.19.5
opencv_python==4.5.5.64

2.运行

python main.py

<main.py>

from MeterClass import *
if __name__ =="__main__":  

    #多张图片,修改输入文件夹

    # imglist=glob.glob('input/*.jpg')  
    # for imgpath in  imglist: 
    #     A=MeterDetection(imgpath)
    #     A.Readvalue()
    #一张图片
    imgpath='images/1.jpg'
    A=MeterDetection(imgpath) 	#创建类对象
    readValue=A.Readvalue()		#调用类方法

三、方法说明

MeterDetection类说明

<MeterClass.py>

类参数

定义了类中的相关参数

class MeterDetection:
    def __init__(self,path):
        self.imageName=path.split('/')[-1].split('.')[0]
        self.outputPath=os.getcwd()+'/outputs/'
        self.image=cv2.imread(path)
        self.circleimg=None
        self.panMask=None           #霍夫圆检测切割的表盘图片
        self.poniterMask =None      #指针图片
        self.numLineMask=None       #刻度线图片
        self.centerPoint=None       #中心点[x,y]
        self.farPoint=None          #指针端点[x,y]
        self.zeroPoint=None         #起始点[x,y]
        self.r=None                 #半径
        self.divisionValue=100/360  #分度值
        self.makeFiledir()
        self.markZeroPoint()

主函数

调用类中其他的方法进行仪表读数

 def Readvalue(self):
        try:
            self.ImgCutCircle()
            self.ContoursFilter()
            self.FitNumLine()
            self.getIntersectionPoints()
            self.FitPointerLine()
            v1=[self.zeroPoint[0]-self.centerPoint[0],self.centerPoint[1]-self.zeroPoint[1]]
            v2=[self.farPoint[0]-self.centerPoint[0],self.centerPoint[1]-self.farPoint[1]]
            theta=Functions.GetClockAngle(v1,v2)
            readValue=self.divisionValue*theta
            print(theta,readValue)
            return readValue
        except Exception as e:# 写一个except
            print("程序错误:",e)

self.ImgCutCircle() 截取表盘区域,滤除背景

    def ImgCutCircle(self):
        #截取表盘区域,滤除背景
        img=self.image
        dst = cv2.pyrMeanShiftFiltering(img, 10, 100)
        cimage = cv2.cvtColor(dst, cv2.COLOR_BGR2GRAY)
        circles = cv2.HoughCircles(cimage, cv2.HOUGH_GRADIENT, 1, 80, param1=100, param2=20, minRadius=80, maxRadius=0)
        circles = np.uint16(np.around(circles))  # 把类型换成整数
        r_1 = circles[0, 0, 2]
        c_x = circles[0, 0, 0]
        c_y = circles[0, 0, 1]
        circle = np.ones(img.shape, dtype="uint8")
        circle = circle * 255
        cv2.circle(circle, (c_x, c_y), int(r_1), 0, -1)
        bitwiseOr = cv2.bitwise_or(img, circle)
        cv2.imwrite(self.outputPath+self.imageName + '_1_imgCutCircle.jpg' , bitwiseOr)
        self.cirleData = [r_1, c_x, c_y]
        self.panMask=bitwiseOr
       
        return bitwiseOr

请添加图片描述

self.ContoursFilter() 对轮廓进行筛选

    def ContoursFilter(self):
        #对轮廓进行筛选
        """
        :funtion : 提取刻度线,指针
        :param a: 高斯滤波 GaussianBlur,自适应二值化adaptiveThreshold,闭运算
        :param b: 轮廓寻找 findContours,
        :return:lineSet,new_needleset
        """
        r_1, c_x, c_y = self.cirleData

        img = self.image.copy()
        # cv2.circle(img, (c_x, c_y), 20, (23, 28, 28), -1)
        img = cv2.GaussianBlur(img, (3, 3), 0)
        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        binary = cv2.adaptiveThreshold(~gray, 255,
                                    cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 15, -10)
        # cv2.circle(binary, (c_x, c_y), int(r_1*0.5), (0, 0, 0),5)
        # 闭运算
        # kernel = np.ones((3, 3), np.uint8)
        #膨胀
        # dilation = cv2.dilate(binary, kernel, iterations=1)
        # kernel2 = np.ones((3, 3), np.uint8)
        #腐蚀
        # erosion = cv2.erode(dilation, kernel2, iterations=1)
        
        #轮廓查找,根据版本不同,返回参数不同
        if cv2.__version__ >'4.0.0':
            contours, hier = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
        else:
            aa,contours, hier = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
        cntset = []  # 刻度线轮廓集合
        cntareas = []  # 刻度线面积集合

        needlecnt = []  # 指针轮廓集合
        needleareas = []  # 指针面积集合
        radiusLength = [r_1 * 0.6, r_1 * 1] # 半径范围

      
        # cv2.drawContours(img, contours, -1, (255, 90, 60), 2)
        # cv2.imwrite(self.outputPath+self.imageName + '_2_----numLineMask.jpg' , img)
        localtion = []
        for cnt in contours:
            rect = cv2.minAreaRect(cnt)
            # print(rect)
            #(中心点坐标,(宽度,高度),旋转的角度)=   = rect
            a, (w, h), c = rect  
            w = int(w)
            h = int(h)
            ''' 满足条件:“长宽比例”,“面积”'''
            if h == 0 or w == 0:
                pass
            else:
                dis = Functions.Disttances((c_x, c_y), a)
                # if (radiusLength[0] < dis and radiusLength[1] > dis):
                if (radiusLength[0] < dis and radiusLength[1] > dis):
                    #矩形筛选
                    if h / w > 4 or w / h > 4:
                        localtion.append(dis)
                        cntset.append(cnt)
                        cntareas.append(w * h)
                else:
                    if w > r_1 / 2 or h > r_1 / 2:
                        needlecnt.append(cnt)
                        needleareas.append(w * h)
        cntareas = np.array(cntareas)
        areasMean = Functions.couputeMean(cntareas)  # 中位数,上限区
        new_cntset = []
        # 面积
        for i, cnt in enumerate(cntset):
            if (cntareas[i] <= areasMean * 1.5 and cntareas[i] >= areasMean * 0.8):
                new_cntset.append(cnt)

        self.r = np.mean(localtion)
        mask = np.zeros(img.shape[0:2], np.uint8)
        self.poniterMask = cv2.drawContours(mask, needlecnt, -1, (255, 255, 255), -1)  # 生成掩膜
        mask = np.zeros(img.shape[0:2], np.uint8)
        self.numLineMask = cv2.drawContours(mask, new_cntset, -1, (255, 255, 255), -1)  # 生成掩膜

        cv2.imwrite(self.outputPath+self.imageName + '_2_numLineMask.jpg' , self.numLineMask)
        cv2.imwrite(self.outputPath+self.imageName + '_3_poniterMask.jpg' , self.poniterMask)
        # for cnt in needlecnt:
        #     cv2.fillConvexPoly(mask,cnt , 255)
        self.new_cntset=new_cntset
        
        return new_cntset

请添加图片描述请添加图片描述

self.FitNumLine()轮廓拟合直线

    def FitNumLine(self):
        """ 轮廓拟合直线"""
        lineSet = []  # 拟合线集合
        img=self.image.copy()
        for cnt in self.new_cntset:
            rect = cv2.minAreaRect(cnt)
            # 获取矩形四个顶点,浮点型
            box = cv2.boxPoints(rect)
            box = np.int0(box)
            cv2.polylines(img, [box], True, (0, 255, 0), 1)  # pic
            output = cv2.fitLine(cnt, 2, 0, 0.001, 0.001)
            k = output[1] / output[0]
            k = round(k[0], 2)
            b = output[3] - k * output[2]
            b = round(b[0], 2)
            x1 = 1
            x2 = img.shape[0]
            y1 = int(k * x1 + b)
            y2 = int(k * x2 + b)
            # cv2.line(img, (x1, y1), (x2, y2), (0, 255, 0), 1)
            #lineSet:刻度线拟合直线数组,k斜率 b
            lineSet.append([k, b])  # 求中心点的点集[k,b]
        cv2.imwrite(self.outputPath+self.imageName + '_4_fitNumLine.jpg' , img)
        self.lineSet=lineSet
        return lineSet

请添加图片描述

self.getIntersectionPoints()获取刻度线交点

    def getIntersectionPoints(self):
        #获取刻度线交点
        img = self.image
        lineSet=self.lineSet
        w, h, c = img.shape
        point_list = []
        xlist=[]
        ylist=[]
        if len(lineSet) > 2:
            # print(len(lineSet))
            np.random.shuffle(lineSet)
            lkb = int(len(lineSet) / 2)
            kb1 = lineSet[0:lkb]
            kb2 = lineSet[lkb:(2 * lkb)]
            # print('len', len(kb1), len(kb2))
            kb1sample = random.sample(kb1, int(len(kb1) / 2))
            kb2sample = random.sample(kb2, int(len(kb2) / 2))
        else:
            kb1sample = lineSet[0]
            kb2sample = lineSet[1]
        for i, wx in enumerate(kb1sample):
            # for wy in kb2:
            for wy in kb2sample:
                k1, b1 = wx
                k2, b2 = wy
                # print('kkkbbbb',k1[0],b1[0],k2[0],b2[0])
                # k1-->[123]
                try:
                    if (b2 - b1) == 0:
                        b2 = b2 - 0.1
                    if (k1 - k2) == 0:
                        k1 = k1 - 0.1
                    x = (b2 - b1) / (k1 - k2)
                    y = k1 * x + b1
                    x = int(round(x))
                    y = int(round(y))
                except:
                    x = (b2 - b1 - 0.01) / (k1 - k2 + 0.01)
                    y = k1 * x + b1
                    x = int(round(x))
                    y = int(round(y))
                # x,y=solve_point(k1, b1, k2, b2)
                if x < 0 or y < 0 or x > w or y > h:
                    break
                # point_list.append([x, y])
                xlist.append(x)
                ylist.append(y)
                # cv2.circle(img, (x, y), 2, (122, 22, 0), 2)
        # print('point_list',point_list)
        cx=int(np.mean(xlist))
        cy=int(np.mean(ylist))
        self.centerPoint=[cx,cy]
        cv2.circle(img, (cx, cy), 2, (0, 0, 255), 2)
        cv2.imwrite(self.outputPath+self.imageName + '_5_IntersectionPoints.jpg' , img)
        return img

请添加图片描述

self.FitPointerLine()拟合指针直线段

    def FitPointerLine(self):
        #拟合指针直线段
        img =self.poniterMask
        orgin_img=self.image.copy()
        # kernel = np.ones((3, 3), np.uint8)
        # mask = cv2.dilate(img, kernel, iterations=1)
        # img = cv2.erode(mask, kernel, iterations=1)
        lines = cv2.HoughLinesP(img, 1, np.pi / 180, 100, minLineLength=int(self.r / 2), maxLineGap=2)
        # nmask = np.zeros(img.shape, np.uint8)
        # lines = mential.findline(self=0, cp=[x, y], lines=lines)
        # print('lens', len(lines))
        dmax=0
        pointerLine=[]
        #最长的线段为指针
        for line in lines:
            x1, y1, x2, y2 = line[0]
            d1=Functions.Disttances((x1, y1),(x2, y2))
            if(d1>dmax):
                dmax=d1
                pointerLine=line[0]      
        x1, y1, x2, y2 = pointerLine
        d1=Functions.Disttances((x1, y1),(self.centerPoint[0],self.centerPoint[1]))
        d2=Functions.Disttances((x2, y2),(self.centerPoint[0],self.centerPoint[1]))
        if d1 > d2:
            self.farPoint = [x1, y1]
        else:
            self.farPoint = [x2, y2]

        cv2.line(orgin_img, (x1, y1), (x2, y2), 20, 1, cv2.LINE_AA)
        cv2.circle(orgin_img,(self.farPoint[0],self.farPoint[1]), 2, (0, 0, 255),2)
        cv2.imwrite(self.outputPath+self.imageName + '_6_PointerLine.jpg' , img)
        cv2.imwrite(self.outputPath+self.imageName + '_7_PointerPoint.jpg' , orgin_img)

请添加图片描述
请添加图片描述

读数

计算夹角

            v1=[self.zeroPoint[0]-self.centerPoint[0],self.centerPoint[1]-self.zeroPoint[1]]
            v2=[self.farPoint[0]-self.centerPoint[0],self.centerPoint[1]-self.farPoint[1]]
            theta=Functions.GetClockAngle(v1,v2)
            readValue=self.divisionValue*theta

总结

对程序重新进行封装,提高了可读性
程序地址:指针式仪表读数源码github
记得点个star
创作不易,有需要开发的可以联系我,在校研究生

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
xiaoxingxing的头像xiaoxingxing管理团队
上一篇 2023年4月22日
下一篇 2023年4月22日

相关推荐