OpenCV 表盘指针自动读数的示例代码


Posted in Python onApril 10, 2020

前段时间参加了一个表盘指针读数的比赛,今天来总结一下

数据集一共有一千张图片:

OpenCV 表盘指针自动读数的示例代码

方法一:径向灰度求和

基本原理:

将图像以表盘圆心转换成极坐标,然后通过矩阵按行求和找到二值图最大值即为指针尖端

导入需要用到的包

import cv2 as cv
import numpy as np
import math
from matplotlib import pyplot as plt
import os

图像预处理

去除背景:利用提取红色实现

def extract_red(image):
  """
  通过红色过滤提取出指针
  """
  red_lower1 = np.array([0, 43, 46])
  red_upper1 = np.array([10, 255, 255])
  red_lower2 = np.array([156, 43, 46])
  red_upper2 = np.array([180, 255, 255])
  dst = cv.cvtColor(image, cv.COLOR_BGR2HSV)
  mask1 = cv.inRange(dst, lowerb=red_lower1, upperb=red_upper1)
  mask2 = cv.inRange(dst, lowerb=red_lower2, upperb=red_upper2)
  mask = cv.add(mask1, mask2)
  return mask

OpenCV 表盘指针自动读数的示例代码

获得钟表中心:轮廓查找,取出轮廓的外接矩形,根据矩形面积找出圆心

def get_center(image):
  """
  获取钟表中心
  """ 
  edg_output = cv.Canny(image, 100, 150, 2) # canny算子提取边缘
  cv.imshow('dsd', edg_output)
  # 获取图片轮廓
  contours, hireachy = cv.findContours(edg_output, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
  center = []
  cut=[0, 0]
  for i, contour in enumerate(contours):
    x, y, w, h = cv.boundingRect(contour) # 外接矩形
    area = w * h # 面积
    if area < 100 or area > 4000:
      continue
    cv.rectangle(image, (x, y), (x + w, y + h), (255, 0, 0), 1)
    cx = w / 2
    cy = h / 2
    cv.circle(image, (np.int(x + cx), np.int(y + cy)), 1, (255, 0, 0)) ## 在图上标出圆心
    center = [np.int(x + cx), np.int(y + cy)]
    break
  return center[::-1]

OpenCV 表盘指针自动读数的示例代码

由上面的图像可以看出,圆心定位还是非常准确的

图片裁剪

def ChangeImage(image):
  """
  图像裁剪
  """
  # 指针提取
  mask = extract_red(image)
  mask = cv.medianBlur(mask,ksize=5)#去噪
  # 获取中心
  center = get_center(mask)
  # 去除多余黑色边框
  [y, x] = center
  cut = mask[y-300:y+300, x-300:x+300]
  # 因为mask处理后已经是二值图像,故不用转化为灰度图像
  return cut

剪裁后的图像如下图所示:

OpenCV 表盘指针自动读数的示例代码

极坐标转换

注意:需要将图片裁剪成正方形

def polar(image):
  """
  转换成极坐标
  """
  x, y = 300, 300
  maxRadius = 300*math.sqrt(2)
  linear_polar = cv.linearPolar(image, (y, x), maxRadius, cv.WARP_FILL_OUTLIERS + cv.INTER_LINEAR)
  mypolar = linear_polar.copy()
  #将图片调整为从0度开始
  mypolar[:150, :] = linear_polar[450:, :]
  mypolar[150:, :] = linear_polar[:450, :]
  cv.imshow("linear_polar", linear_polar)
  cv.imshow("mypolar", mypolar)
  return mypolar

OpenCV 表盘指针自动读数的示例代码

由图像就可以很容易发现指针的顶点

计算角度

def Get_Reading(sumdata):
  """
  读数并输出
  """
  peak = []
  # s记录遍历时波是否在上升
  s = sumdata[0] < sumdata[1]
  for i in range(599):
    # 上升阶段
    if s==True and sumdata[i] > sumdata[i+1] and sumdata[i] > 70000:
      peak.append(sumdata[i])
      s=False
    # 下降阶段
    if s==False and sumdata[i] < sumdata[i+1]:
      s=True
  peak.sort()
  a = sumdata[0]
  b = sumdata[-1]
  if not peak or max(a,b) > peak[-1]:
    peak.append(max(a,b))
  longindex = (sumdata.index(peak[-1]))%599
  longnum = (longindex + 1)//25*50
  # 先初始化和长的同一刻度
  #shortindex = longindex
  shortnum = round(longindex / 6)
  try:
    shortindex = sumdata.index(peak[-2])
    shortnum = round(shortindex / 6)
  except IndexError:
    i=0
    while i<300:
      i += 1
      l = sumdata[(longindex-i)%600]
      r = sumdata[(longindex+i)%600]
      possibleshort = max(l,r)
      # 在短指针可能范围内寻找插值符合条件的值
      if possibleshort > 80000:
        continue
      elif possibleshort < 60000:
        break
      else:
        if abs(l-r) > 17800:
          shortindex = sumdata.index(possibleshort) - 1
          shortnum = round(shortindex / 6)
          break
  return [longnum,shortnum%100]
def test():
  """
  RGS法测试
  """
  image = cv.imread("./BONC/1_{0:0>4d}".format(400) + ".jpg")
  newimg = ChangeImage(image)
  polarimg = polar(newimg)
  psum = polarimg.sum(axis=1, dtype = 'int32')
  result = Get_Reading(list(psum))
  print(result)
if __name__ == "__main__":
  test()
  k = cv.waitKey(0)
  if k == 27:
    cv.destroyAllWindows()
  elif k == ord('s'):
    cv.imwrite('new.jpg', src)
    cv.destroyAllWindows()

[1050, 44]

方法二:Hough直线检测

原理:利用Hough变换检测出指针的两条边,从而两条边的中线角度即为指针刻度

数据预处理与上面的方法类似

OpenCV 表盘指针自动读数的示例代码

可以看到分别检测出了两个指针的左右两条边,然后可以由这四个角度算出两个指针中线的角度,具体计算过程写的有点复杂

class Apparatus:
  def __init__(self, name):
    self.name = name
    self.angle = []
    self.src = cv.imread(name)


  def line_detect_possible_demo(self, image, center, tg):
    '''
    :param image: 二值图
    :param center: 圆心
    :param tg: 直线检测maxLineGap
    '''
    res = {} # 存放线段的斜率和信息
    edges = cv.Canny(image, 50, 150, apertureSize=7)
    cv.imshow("abcdefg", edges)
    lines = cv.HoughLinesP(edges, 1, np.pi/360, 13, minLineLength=20, maxLineGap=tg)
    for line in lines:
      x_1, y_1, x_2, y_2 = line[0]
      # 将坐标原点移动到圆心
      x1 = x_1 - center[0]
      y1 = center[1] - y_1
      x2 = x_2 - center[0]
      y2 = center[1] - y_2

      # 计算斜率
      if x2 - x1 == 0:
        k = float('inf')
      else:
        k = (y2-y1)/(x2-x1)
      d1 = np.sqrt(max(abs(x2), abs(x1)) ** 2 + (max(abs(y2), abs(y1))) ** 2) # 线段长度
      d2 = np.sqrt(min(abs(x2), abs(x1)) ** 2 + (min(abs(y2), abs(y1))) ** 2)
      # 将长指针与短指针做标记
      if d1 < 155 and d1 > 148 and d2 > 115:
        res[k] = [1]
      elif d1 < 110 and d1 > 100 and d2 > 75:
        res[k] = [2]
      else:
        continue
      res[k].append(1) if (x2 + x1) /2 > 0 else res[k].append(0) # 将14象限与23象限分离
      cv.line(self.src, (x1 + center[0], center[1] - y1), (x2 + center[0], center[1] - y2), (255, 0, 0), 1)
      cv.imshow("line_detect-posssible_demo", self.src)


      # 计算线段中点的梯度来判断是指针的左侧线段还是右侧线段
      middle_x = int((x_1 + x_2) / 2)
      middle_y = int((y_1 + y_2) / 2)
      grad_mat = image[middle_y-5:middle_y+6, middle_x-5:middle_x+6]
      cv.imshow("grad_mat", grad_mat)
      grad_x = cv.Sobel(grad_mat, cv.CV_32F, 1, 0)
      grad_y = cv.Sobel(grad_mat, cv.CV_32F, 0, 1)
      gradx = np.max(grad_x) if np.max(grad_x) != 0 else np.min(grad_x)
      grady = np.max(grad_y) if np.max(grad_y) != 0 else np.min(grad_y)
      if ((gradx >=0 and grady >= 0) or (gradx <= 0 and grady >= 0)) and res[k][1] == 1:
        res[k].append(1) # 右测
      elif ((gradx <= 0 and grady <= 0) or (gradx >= 0 and grady <= 0)) and res[k][1] == 0:
        res[k].append(1)
      else:
        res[k].append(0) # 左侧
    # 计算角度
    angle1 = [i for i in res if res[i][0] == 1]
    angle2 = [i for i in res if res[i][0] == 2]
    # 长指针
    a = np.arctan(angle1[0])
    b = np.arctan(angle1[1])
    if a * b < 0 and max(abs(a), abs(b)) > np.pi / 4:
      if a + b < 0:
        self.angle.append(math.degrees(-(a + b) / 2)) if res[angle1[1]][1] == 1 else self.angle.append(
          math.degrees(-(a + b) / 2) + 180)
      else:
        self.angle.append(math.degrees(np.pi - (a + b) / 2)) if res[angle1[1]][1] == 1 else self.angle.append(
          math.degrees(np.pi - (a + b) / 2) + 180)
    else:
      self.angle.append(math.degrees(np.pi / 2 - (a + b) / 2)) if res[angle1[1]][1] == 1 else self.angle.append(math.degrees(np.pi / 2 - (a + b) / 2) + 180)
    print('长指针读数:%f' % self.angle[0])


    # 短指针
    a = np.arctan(angle2[0])
    b = np.arctan(angle2[1])
    if a * b < 0 and max(abs(a), abs(b)) > np.pi / 4:
      if a + b < 0:
        self.angle.append(math.degrees(-(a + b) / 2)) if res[angle2[1]][1] == 1 else self.angle.append(
          math.degrees(-(a + b) / 2) + 180)
      else:
        self.angle.append(math.degrees(np.pi - (a + b) / 2)) if res[angle2[1]][1] == 1 else self.angle.append(
          math.degrees(np.pi - (a + b) / 2) + 180)
    else:
      self.angle.append(math.degrees(np.pi / 2 - (a + b) / 2)) if res[angle2[1]][1] == 1 else self.angle.append(math.degrees(np.pi / 2 - (a + b) / 2) + 180)
    print('短指针读数:%f' % self.angle[1])



  def get_center(self, mask):
    edg_output = cv.Canny(mask, 66, 150, 2)
    cv.imshow('edg', edg_output)
    # 外接矩形
    contours, hireachy = cv.findContours(edg_output, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
    center = []
    for i, contour in enumerate(contours):
      x, y, w, h = cv.boundingRect(contour) # 外接矩形
      area = w * h # 面积
      if area > 1000 or area < 40:
        continue
      #print(area)
      # cv.circle(src, (np.int(cx), np.int(cy)), 3, (255), -1)
      cv.rectangle(self.src, (x, y), (x + w, y + h), (255, 0, 0), 1)
      cx = w / 2
      cy = h / 2
      cv.circle(self.src, (np.int(x + cx), np.int(y + cy)), 1, (255, 0, 0))
      center.extend([np.int(x + cx), np.int(y + cy)])
      break

    cv.imshow('center', self.src)
    return center


  def extract(self, image):
    red_lower1 = np.array([0, 43, 46])
    red_lower2 = np.array([156, 43, 46])
    red_upper1 = np.array([10, 255, 255])
    red_upper2 = np.array([180, 255, 255])
    frame = cv.cvtColor(image, cv.COLOR_BGR2HSV)
    mask1 = cv.inRange(frame, lowerb=red_lower1, upperb=red_upper1)
    mask2 = cv.inRange(frame, lowerb=red_lower2, upperb=red_upper2)
    mask = cv.add(mask1, mask2)
    mask = cv.bitwise_not(mask)
    cv.imshow('mask', mask)
    return mask


  def test(self):
    self.src = cv.resize(self.src, dsize=None, fx=0.5, fy=0.5) # 此处可以修改插值方式interpolation
    mask = self.extract(self.src)
    mask = cv.medianBlur(mask, ksize=5) # 去噪
    # 获取中心
    center = self.get_center(mask)
    # 去除多余黑色边框
    [y, x] = center
    mask = mask[x - 155:x + 155, y - 155:y + 155]
    cv.imshow('mask', mask)
    #self.find_short(center, mask)
    try:
      self.line_detect_possible_demo(mask, center, 20)
    except IndexError:
      try:
        self.src = cv.imread(self.name)
        self.src = cv.resize(self.src, dsize=None, fx=0.5, fy=0.5) # 此处可以修改插值方式interpolation
        self.src = cv.convertScaleAbs(self.src, alpha=1.4, beta=0)
        blur = cv.pyrMeanShiftFiltering(self.src, 10, 17)
        mask = self.extract(blur)
        self.line_detect_possible_demo(mask, center, 20)
      except IndexError:
        self.src = cv.imread(self.name)
        self.src = cv.resize(self.src, dsize=None, fx=0.5, fy=0.5) # 此处可以修改插值方式interpolation
        self.src = cv.normalize(self.src, dst=None, alpha=200, beta=10, norm_type=cv.NORM_MINMAX)
    
        blur = cv.pyrMeanShiftFiltering(self.src, 10, 17)
        mask = self.extract(blur)
        self.line_detect_possible_demo(mask, center, 20)


if __name__ == '__main__':
  apparatus = Apparatus('./BONC/1_0555.jpg')
  # 读取图片
  apparatus.test()
  k = cv.waitKey(0)
  if k == 27:
    cv.destroyAllWindows()
  elif k == ord('s'):
    cv.imwrite('new.jpg', apparatus.src)
    cv.destroyAllWindows()

长指针读数:77.070291
短指针读数:218.896747

由结果可以看出精确度还是挺高的,但是这种方法有三个缺点:

  • 当两个指针重合时候不太好处理
  • 有时候hough直线检测只能检测出箭头的一条边,这时候就会报错,可以利用图像增强、角点检测和图像梯度来辅助解决,但是效果都不太好
  • 计算角度很复杂!!(也可能是我想复杂了,不过这段代码确实花了大量时间)

代码里可能还有很多问题,希望大家多多指出

到此这篇关于OpenCV 表盘指针自动读数的示例代码的文章就介绍到这了,更多相关OpenCV 表盘自动读数内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Python 相关文章推荐
tornado捕获和处理404错误的方法
Feb 26 Python
从零学python系列之从文件读取和保存数据
May 23 Python
MySQL适配器PyMySQL详解
Sep 20 Python
利用Python代码实现数据可视化的5种方法详解
Mar 25 Python
Tensorflow 查看变量的值方法
Jun 14 Python
Python3中编码与解码之Unicode与bytes的讲解
Feb 28 Python
Python FFT合成波形的实例
Dec 04 Python
在Tensorflow中查看权重的实现
Jan 24 Python
详解Ubuntu环境下部署Django+uwsgi+nginx总结
Apr 02 Python
在jupyter notebook 添加 conda 环境的操作详解
Apr 10 Python
浅谈keras 的抽象后端(from keras import backend as K)
Jun 16 Python
Python 绘制可视化折线图
Jul 22 Python
Python装饰器的应用场景代码总结
Apr 10 #Python
在Python中使用K-Means聚类和PCA主成分分析进行图像压缩
Apr 10 #Python
jupyter notebook 增加kernel教程
Apr 10 #Python
Python3操作YAML文件格式方法解析
Apr 10 #Python
Jupyter Notebook远程登录及密码设置操作
Apr 10 #Python
Python 炫技操作之合并字典的七种方法
Apr 10 #Python
python+selenium+chromedriver实现爬虫示例代码
Apr 10 #Python
You might like
ThinkPHP之import方法实例详解
2014/06/20 PHP
PHP实现随机数字、字母的验证码功能
2018/08/01 PHP
php输出反斜杠的实例方法
2019/09/19 PHP
Laravel Eloquent分表方法并使用模型关联的实现
2019/11/25 PHP
Js event事件在IE、FF兼容性问题
2011/01/01 Javascript
用jquery修复在iframe下的页面锚点失效问题
2014/08/22 Javascript
javascript动态控制服务器控件实例
2014/09/05 Javascript
动态加载js的方法汇总
2015/02/13 Javascript
css如何让浮动元素水平居中
2015/08/07 Javascript
AngualrJS中的Directive制作一个菜单
2016/01/26 Javascript
JavaScript判断变量是否为数组的方法(Array)
2016/02/24 Javascript
JavaScript实现简单的四则运算计算器完整实例
2017/04/28 Javascript
zTree树形插件异步加载方法详解
2017/06/14 Javascript
vue-cli项目中怎么使用mock数据
2017/09/27 Javascript
Vue侧滑菜单组件——DrawerLayout
2017/12/18 Javascript
基于wordpress的ajax写法详解
2018/01/02 Javascript
vue addRoutes实现动态权限路由菜单的示例
2018/05/15 Javascript
在Vuex使用dispatch和commit来调用mutations的区别详解
2018/09/18 Javascript
Element-UI踩坑之Pagination组件的使用
2018/10/29 Javascript
仿ElementUI实现一个Form表单的实现代码
2019/04/23 Javascript
JS实现图片懒加载(lazyload)过程详解
2020/04/02 Javascript
Vue实现背景更换颜色操作
2020/07/17 Javascript
一分钟学会JavaScript中的try-catch
2020/12/14 Javascript
浅析Python编写函数装饰器
2016/03/18 Python
python框架django基础指南
2016/09/08 Python
详谈Python基础之内置函数和递归
2017/06/21 Python
Python实现二叉树前序、中序、后序及层次遍历示例代码
2019/05/18 Python
python异步Web框架sanic的实现
2020/04/27 Python
英国当代时尚和街头服饰店:18montrose
2018/12/15 全球购物
大学生职业生涯规划书模板
2014/01/03 职场文书
房屋买卖协议书范本
2014/04/10 职场文书
房地产开发项目建议书
2014/05/16 职场文书
小学优秀班干部事迹材料
2014/05/25 职场文书
咖啡店创业计划书范文
2014/09/15 职场文书
2014年小学国庆节活动方案
2014/09/16 职场文书
MySQL实例精讲单行函数以及字符数学日期流程控制
2021/10/15 MySQL