机器视觉(仪表篇)

欢迎阅读机器视觉(仪表篇)笔记


1. 根据 0 刻度线计算仪表盘角度

基本工具类

```python
import numpy as np
import cv2
import os
import random

class Functions:
@staticmethod
def GetClockAngle(v1, v2):

    # 向量模长
    TheNorm = np.linalg.norm(v1) * np.linalg.norm(v2)

    # 叉乘判断方向
    rho = np.rad2deg(np.arcsin(np.cross(v1, v2) / TheNorm))

    # 点乘计算角度
    theta = np.rad2deg(np.arccos(np.dot(v1, v2) / TheNorm))

    if rho > 0:
        return 360 - theta
    else:
        return theta

@staticmethod
def Disttances(a, b):
    x1, y1 = a
    x2, y2 = b
    return int(((x1 - x2) ** 2 + (y1 - y2) ** 2) ** 0.5)

@staticmethod
def couputeMean(deg):
    mean = np.mean(deg)
    percentile = np.percentile(deg, (25, 50, 75))

    Q1 = percentile[0]
    Q3 = percentile[2]
    IQR = Q3 - Q1

    ulim = Q3 + 2.5 * IQR
    llim = Q1 - 1.5 * IQR

    new_deg = [d for d in deg if llim < d < ulim]

    return np.mean(new_deg)

class MeterDetection:
def init(self, path):
self.imageName = path.split(‘/‘)[-1].split(‘.’)[0]
self.outputPath = ‘outputs/‘
self.image = cv2.imread(path)

    self.circleData = None
    self.panMask = None
    self.pointerMask = None
    self.numLineMask = None

    self.centerPoint = None
    self.farPoint = None
    self.zeroPoint = None

    self.r = None
    self.divisionValue = 100 / 360

    self.makeFiledir()
    self.markZeroPoint()

# 创建文件夹
def makeFiledir(self):
    if not os.path.exists(self.outputPath):
        os.makedirs(self.outputPath)

# 手动标记0刻度
def markZeroPoint(self):
    img = self.image

    def on_EVENT_LBUTTONDOWN(event, x, y, flags, param):
        if event == cv2.EVENT_LBUTTONDOWN:
            self.zeroPoint = [x, y]
            cv2.circle(img, (x, y), 2, (120, 0, 255), -1)
            cv2.imshow("image", img)

    cv2.imshow("image", img)
    cv2.setMouseCallback("image", on_EVENT_LBUTTONDOWN)
    cv2.waitKey()

# 圆形检测
def ImgCutCircle(self):
    img = self.image
    dst = cv2.pyrMeanShiftFiltering(img, 10, 100)
    gray = cv2.cvtColor(dst, cv2.COLOR_BGR2GRAY)

    circles = cv2.HoughCircles(
        gray,
        cv2.HOUGH_GRADIENT,
        1,
        80,
        param1=100,
        param2=20,
        minRadius=80,
        maxRadius=0
    )

    if circles is None:
        raise ValueError("未检测到表盘圆")

    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") * 255
    cv2.circle(circle, (c_x, c_y), int(r_1), 0, -1)

    bitwiseOr = cv2.bitwise_or(img, circle)

    self.circleData = [r_1, c_x, c_y]
    self.panMask = bitwiseOr

    return bitwiseOr

# 轮廓筛选
def ContoursFilter(self):
    r_1, c_x, c_y = self.circleData

    img = self.panMask.copy()
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    binary = cv2.adaptiveThreshold(
        ~gray,
        255,
        cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
        cv2.THRESH_BINARY,
        15,
        -10
    )

    contours, _ = cv2.findContours(
        binary,
        cv2.RETR_LIST,
        cv2.CHAIN_APPROX_SIMPLE
    )

    cntset = []
    needlecnt = []
    radiusLength = [r_1 * 0.6, r_1 * 1]

    localtion = []

    for cnt in contours:
        rect = cv2.minAreaRect(cnt)
        (cx, cy), (w, h), angle = rect

        if w == 0 or h == 0:
            continue

        dis = Functions.Disttances((c_x, c_y), (cx, cy))

        if radiusLength[0] < dis < radiusLength[1]:
            if h / w > 4 or w / h > 4:
                cntset.append(cnt)
                localtion.append(dis)
        else:
            needlecnt.append(cnt)

    self.r = np.mean(localtion)

    mask = np.zeros(img.shape[:2], np.uint8)
    self.pointerMask = cv2.drawContours(mask.copy(), needlecnt, -1, 255, -1)
    self.numLineMask = cv2.drawContours(mask.copy(), cntset, -1, 255, -1)

    return cntset

# 计算最终值
def Readvalue(self):
    try:
        self.ImgCutCircle()
        self.ContoursFilter()

        # 示例:这里只保留核心流程
        v1 = [
            self.zeroPoint[0] - self.circleData[1],
            self.circleData[2] - self.zeroPoint[1]
        ]

        v2 = [0, -1]  # 示例指针向量(简化)

        theta = Functions.GetClockAngle(v1, v2)
        readValue = self.divisionValue * theta

        print("角度:", theta)
        print("读数:", readValue)

        return readValue

    except Exception as e:
        print("程序错误:", e)