普通表计读数开发思路

一、普通表计类型介绍🍉

常见的普通表计有SF6,压力表,油位表(指针类)等。

图1:( 压力表)

图2:(油位表-指针类) 

图3:(SF6表) 

 

图4:(单指针油温表)

图5:(泄漏电流表-表盘2)

好了,普通表计的类型大概就是有这些了。那么看到这里我们不经有一个疑问——为什么把他们归为普通表计?

答案其实很简单,因为“单指针”。 

单指针的表计是很好识别的,如果有表计读数开发经验的伙计应该都知道。多指针的表计读数开发,分割指针的后处理是有多麻烦,为了应对各种各样的情况,后处理的代码可能多达几千行。

二、思路🍉

我们不得不以一个图片为例子,那么就选取最经典,也是最容易的压力表吧!

2.1 对点位进行打点(略)🎈。

2.2 对表盘进行检测🎈。

在此之前我们需要训练一个yolo目标检测模型,用于检测表盘以及表计的类型。假设我们已经拥有了他detection_meter,使用它对输入的图片进行检测,检测结果大致如下:

我们将得到两个重要的信息:

  • 标签<str>——表计类型:meter_type。
  • 矩形框<xmin, ymin, xmax, ymax>——表盘位置 : rectangle_meter。

根据此可以裁剪得到表盘和根据表盘的类型进行分类别预处理。(略)

。。。

2.3 对指针进行分割🎈。

此时我们需要一个必不可缺的指针分割模型对上一步裁剪出来的表盘进行分割,这里可以推荐一下:百度飞桨paddle的工业表计指针分割模型,开源可商用。(太久了,链接一下子找不着了。)

效果大致如下:

2.4 矩形展开指针和点位🎈。

NOTE:当然也可以不展开,直接根据点位的[<x1,y1>,<x2,y2>,...,]坐标和指针顶端的位置<x,y>,进行一个角度的位置判断。但是这里我们只探究展开矩形的方式。

将呈圆形的点位连带指针一起展开成矩形 :

根据此展开图,获取指针分割图普通坐标轴中x轴方向的位置:

  • point_location<float>: 483.4

同样的得到点位x所处的位置:

  • scale_location<list>: [43.0, 136.5, 231.5, 325.5, 466.0, 574.0, 678.5, 811.0, 936.0, 1083.5]

<展开原理如下:>

def circle_to_rectangle(self, seg_result):
        """将圆形表盘的预测结果label_map转换成矩形

        圆形到矩形的计算方法:
            因本案例中两种表盘的刻度起始值都在左下方,故以圆形的中心点为坐标原点,
            从-y轴开始逆时针计算极坐标到x-y坐标的对应关系:
              x = r + r * cos(theta)
              y = r - r * sin(theta)
            注意:
                1. 因为是从-y轴开始逆时针计算,所以r * sin(theta)前有负号。
                2. 还是因为从-y轴开始逆时针计算,所以矩形从上往下对应圆形从外到内,
                   可以想象把圆形从-y轴切开再往左右拉平时,圆形的外围是上面,內围在下面。

        参数:
            seg_results (list[dict]):分割模型的预测结果。

        返回值:
            rectangle_meters (list[np.array]):矩形表盘的预测结果label_map。

        """
        ...(不可知)

到这里恐怕很多人已经想到使用cv2.connectedComponetsWithStats来做了。但是我们不能只考虑理想的情况下,其中对于获取指针顶点的位置,状况百出,分割出来的红指针可能是不规则类型的,可能是歪着的,甚至可能是只有一半。 

如果单纯通过cv2的连通域得到的左上角坐标和右下角坐标,很多时候其实会出现错误情况:比如这样的:

我们需要从上向下找到位置最低的那些像素块选取最中间那个,代码块如下: 

    def get_connected_components(self, image):
        # 二值化处理
        ret, binary = cv2.threshold(image, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
        # 获取连通域信息
        output = cv2.connectedComponentsWithStats(binary, connectivity=8, ltype=cv2.CV_32S)
        num_labels = output[0]
        labels = output[1]
        stats = output[2]
        centroids = output[3]
        # 获取每个连通域的最高点和最低点坐标
        result = []
        highest_points = []
        for i in range(1, num_labels):
            x, y, w, h, area = stats[i]
            top_left = (x, y)
            bottom_right = (x + w - 1, y + h - 1)
            result.append((top_left, bottom_right))

            label = i
            points = np.argwhere(labels == label)
            # print(points)
            # 找到x轴最小的点
            max_x = np.min(points[:, 0])
            max_x_points = points[points[:, 0] == max_x]
            # 找到y轴最中的点
            max_y = np.median(max_x_points[:, 1])
            # 添加最高点坐标到列表中
            highest_points.append((max_y, max_x))

        sorted_result = sorted(result, key=lambda x: (x[0][0]+x[1][0])/2)
        highest_points = [ele[0] for ele in sorted(highest_points, key=lambda x:x[0])]
        print("highest_points:", highest_points)
        return sorted_result, highest_points

 2.5 根据点位x_list和指针顶端x便可计算出读数🎈。

       这是显而易见的,因为只需要计算指针顶端x在点位x_list中的位置,再加上每一个点位代表的读数,便可以轻松得到读数结果。

三、点位纠偏🥒

当然实际的情况远远不会如此理想,比如对于摄像头的点位偏移问题,比如多指针问题,更比如模糊问题等等。这时候就需要其他多种技术,例如这里以仿射变换进行点位纠偏来作为一个示例:

点位偏差一直是一个很头疼的问题,但是由于摄像头和实际环境的局限性,我们不得不面对这个问题。对此,使用判别的方式进行一个仿射变换,是一种非常有效的方式,下图中图1是基准图,图2是目标图,图3是目标图仿射变换后得到的结果图。

可以看出效果非常的nice。

import cv2
import numpy as np


def get_good_match(des1,des2):
    bf = cv2.BFMatcher()
    matches = bf.knnMatch(des1, des2, k=2)
    good = []
    for m, n in matches:
        if m.distance < 0.75 * n.distance:
            good.append(m)
    return good

def sift_kp(image):
    '''SIFT特征点检测'''
    height, width = image.shape[:2]
    size = (int(width * 0.2), int(height * 0.2))
    shrink = cv2.resize(image, size, interpolation=cv2.INTER_AREA)
    gray_image = cv2.cvtColor(shrink,cv2.COLOR_BGR2GRAY)
    sift = cv2.SIFT.create()
    kp, des = sift.detectAndCompute(gray_image, None)
    return kp,des

def siftImageAlignment(img1,img2):
    """
    img1: cv2.imread后读取的图片数组,标准图;
    img2: cv2.imread后读取的图片数组,测试图。
    函数作用:把img2配准到img1上,返回变换后的img2。注意:img1和img2的size一定要相同。
    """
    kp1,des1 = sift_kp(img1)
    kp2,des2 = sift_kp(img2)
    goodMatch = get_good_match(des1,des2)
    if len(goodMatch) > 4:
        ptsA= np.float32([kp1[m.queryIdx].pt for m in goodMatch]).reshape(-1, 1, 2)
        ptsB = np.float32([kp2[m.trainIdx].pt for m in goodMatch]).reshape(-1, 1, 2)
        ptsA = ptsA / 0.2
        ptsB = ptsB / 0.2
        ransacReprojThreshold = 4
        H, status =cv2.findHomography(ptsA,ptsB,cv2.RANSAC,ransacReprojThreshold)
        imgOut = cv2.warpPerspective(img2, H, (img1.shape[1],img1.shape[0]),flags=cv2.INTER_LINEAR + cv2.WARP_INVERSE_MAP)
        return imgOut
    else:
        return img2

def cv_imread(file_path):
    """
    能读取中文路径的cv2读图函数。
    """
    cv_img = cv2.imdecode(np.fromfile(file_path,dtype=np.uint8),-1)
    return cv_img

def align(t0_path, t1_path):
    """
    测试函数,分别输入标准图和测试图的路径,输出变换后的图和对比图。
    """
    t0 = cv_imread(t0_path)
    t1 = cv_imread(t1_path)
    t1_img_align, _, _, ptsA, ptsB = siftImageAlignment(t0, t1)
    
    # # 把配准图写到本地
    # t1_new_bn = 'align_' + os.path.basename(t1_path)
    # cv2.imwrite('./pics/' + t1_new_bn, t1_img_align)
    # new_img = np.vstack((t0, t1, t1_img_align))
    # com_bn = 'compare_' + os.path.basename(t1_path)
    # cv2.imwrite('./pics/' + com_bn, new_img)
    
    return t1_img_align

if __name__ == "__main__":
    t0_path = r".\_1723957234138288128.jpg"
    t1_path = r".\1723957234138288128_20231115_200243.jpg"
    align(t0_path, t1_path)

 注意着仍然会出现一些不好的状况,但相似点寻找错误或者过小的时候。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/194548.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

巧妙之中见真章:深入解析常用的创建型设计模式

设计模式之创建型设计模式详解 一、设计模式是什么&#xff1f;二、模板方法2.1、代码结构2.2、符合的设计原则2.3、如何扩展代码2.4、小结 三、观察者模式3.1、代码结构3.2、符合的设计原则3.3、如何扩展代码3.4、小结 四、策略模式4.1、代码结构4.2、符合的设计原则4.3、如何…

K 最近邻算法

K 最近邻算法 简单 KNN海伦约会手写数字识别KNN 算法的优缺点 K 最近邻&#xff08;K-NearestNeighbor&#xff0c;KNN&#xff09;算法&#xff0c;是 1967 年由 Cover T 和 Hart P 提出的一种用于分类与回归的方法。 基本原理&#xff1a;存在一个带标签的数据集&#xff08;…

C语言第三十五弹---打印九九乘法表

C语言打印九九乘法表 思路&#xff1a;观察每一行可以看出乘号右边的一行值都是相同的&#xff0c;而乘号左边不断变化&#xff0c;所以使用嵌套循环&#xff0c;控制好 乘号左右值变化的条件即可。 #include <stdio.h>int main() {for (int i 1; i < 9; i){for (in…

【微服务】java 规则引擎使用详解

目录 一、什么是规则引擎 1.1 规则引擎概述 1.2 规则引擎执行过程 二、为什么要使用规则引擎 2.1 使用规则引擎的好处 2.1.1 易于维护和更新 2.1.2 增强应用程序的准确性和效率 2.1.3 加快应用程序的开发和部署 2.1.4 支持可视化和可管理性 2.2 规则引擎使用场景 三、…

开源四轴协作机械臂ultraArm激光雕刻技术案例!

注意安全事项 开始之前&#xff0c;请确保您已采取适当的安全措施&#xff0c;例如用于激光操作的防护眼镜、灭火器和通风良好的区域。 引言 随着科技的不断进步&#xff0c;激光雕刻技术已经成为当今制造行业中不可或缺的一部分。它以其高精度、高效率和广泛的材料适应性&…

JAVA的一些便捷性方法(Object)

在IDEA中&#xff0c;如何查看JDK的源码&#xff1f; CTRL B; 常用方法&#xff1a; 1.equals&#xff08;&#xff09; booleanequals(Object obj) 指示其他某个对象是否与此对象“相等”。 与 的比较&#xff1a; &#xff0c;即可判断基本类型&#xff0c;也…

从0开始学习JavaScript--JavaScript对象封装

JavaScript中的对象封装是一种重要的编程概念&#xff0c;它允许将数据和方法组织成一个独立的单元&#xff0c;实现了数据的保护和抽象。本文将深入探讨JavaScript对象封装的原理、实践和最佳实践。 封装的基础概念 封装是面向对象编程的基础概念之一&#xff0c;它强调将数…

笔记十七、认识React的路由插件react-router-dom和基本使用

react-router 分类 web使用 react-router-dom native使用 react-router-native anywhere&#xff08;使用麻烦&#xff09; react-router 安装 yarn add react-router-dom main.jsx import React from "react"; import ReactDOM from "react-dom/client"…

蓝桥杯第100 题 九宫幻方 DFS 全排列 C++ 解题思维

题目 九宫幻方https://www.lanqiao.cn/problems/100/learning/?page1&first_category_id1&name%E4%B9%9D 思路和解题方法 一 &#xff08;DFS) 首先&#xff0c;定义了一些全局变量和数组。vis数组用于标记已经出现过的数字&#xff0c;a数组用于存储数独的初始状态…

轻松配置PPPoE连接:路由器设置和步骤详解

在家庭网络环境中&#xff0c;我们经常使用PPPoE&#xff08;点对点协议过夜&#xff09;连接来接入宽带互联网。然而&#xff0c;对于一些没有网络专业知识的人来说&#xff0c;配置PPPoE连接可能会有些困难。在本文中&#xff0c;我将详细介绍如何轻松配置PPPoE连接&#xff…

动静分离+多实例实验(nginx+tomcat)

Nginx服务器&#xff1a;192.168.188.14:80 Tomcat服务器1&#xff1a;192.168.188.11:80 Tomcat服务器2&#xff1a;192.168.188.12:8080 192.168.188.12:8081 部署Nginx负载均衡器 关闭防火墙 systemctl stop firewalld setenforce 0 安装依赖 yum -y install pcre-dev…

经典神经网络——AlexNet模型论文详解及代码复现

一、背景 AlexNet是在2012年由Alex Krizhevsky等人提出的&#xff0c;该网络在2012年的ImageNet大赛上夺得了冠军&#xff0c;并且错误率比第二名高了很多。Alexnet共有8层结构&#xff0c;前5层为卷积层&#xff0c;后三层为全连接层。 论文地址&#xff1a;ImageNet Classif…

稳定视频扩散数据管理解密【stable video diffusion】

Stability AI 最近于 2023 年 11 月 21 日推出了其最新模型—稳定视频扩散&#xff08;SVD&#xff09;。视频生成模型的这一突破取决于数据管理的关键作用。 除了模型检查点之外&#xff0c;他们还发布了一份技术报告。 让我们在 Stability AI 的技术报告和一些引人注目的示例…

LeetCode Hot100 394.字符串解码

题目&#xff1a; 给定一个经过编码的字符串&#xff0c;返回它解码后的字符串。 编码规则为: k[encoded_string]&#xff0c;表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。 你可以认为输入字符串总是有效的&#xff1b;输入字符串中没有额外的…

虚幻学习笔记—点击场景3D物体的两种处理方式

一、前言 本文使用的虚幻引擎为5.3.2&#xff0c;两种方式分别为&#xff1a;点击根物体和精准点击目标物体。 二、实现 2.1、玩家控制器中勾选鼠标点击事件&#xff1a;这一步很重要&#xff0c;如图2.1.1所示&#xff1a;在自定义玩家控制器中勾 图2.1.1 选该项&#xff0c…

解密人工智能:线性回归

导言 人工智能&#xff08;AI&#xff09;已经成为当今科技领域的热门话题&#xff0c;其应用领域涵盖了各个行业。线性回归作为人工智能中的一种关键统计学方法&#xff0c;被广泛应用于预测和决策支持系统中。本文将为您详细介绍线性回归在人工智能中的应用原理与方法&#x…

相同JS代码,多次混淆加密能得到不同的结果吗?

一份相同的JavaScript代码&#xff0c;进行多次混淆加密&#xff0c;能得到不同的结果吗&#xff1f; 答案是肯定的&#xff0c;能。 JShaman可以实现这个效果。即&#xff1a;加密结果具有多态性、变化性。 下面实测展示。 来到JShaman网站&#xff0c;用它默认的示例代码…

案例分析-FATfs文件系统移植单片机内存不够问题分析和解决

在通过cubeMX自带的FATfs 文件系统在STM32F103C8T6上进行移植&#xff0c;正式调用后&#xff0c;发现系统报错&#xff0c;出现内存空间不足问题。如下&#xff1a; 更改更大容量的单片机进行编译&#xff0c;通过了 说明刚开始分析空间不够是对的&#xff0c;是flash不够还是…

【vue】浏览器安装vue插件不生效

上一篇&#xff1a;浏览器安装vue插件 https://blog.csdn.net/m0_67930426/article/details/134598104 目录 问题情景 解决办法 问题情景 输入框无内容 解决办法 添加 Vue.config.devtools true; 并且控制台不显示的vue又出现

红米手机如何远程控制荣耀手机?

很多人都知道&#xff0c;华为体系有【畅联】&#xff0c;与华为手机或平板“畅连”通话时&#xff0c;可共享屏幕给对方&#xff0c;一边聊天一边演示&#xff0c;还可在屏幕上涂鸦帮助理解。同样&#xff0c;小米体系有【小米通话】&#xff0c;它的远程协助功能可以帮助朋友…