普通表计读数开发思路

news2024/11/24 17:49:29

一、普通表计类型介绍🍉

常见的普通表计有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)

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

系列二十一、Spring中bean的创建顺序

一、概述 我们知道启动IOC容器时&#xff0c;Spring会为我们创建各种各样的bean&#xff0c;那么思考一个问题&#xff0c;bean的创建顺序是由什么决定的呢&#xff1f;答&#xff1a;bean的创建顺序是由BeanDefinition的注册信息决定的&#xff0c;这个其实很好理解&#xff0…

linux安装部署redis

1、下载redis包2、解压3、进入解压路径编译安装4、修改配置文件使redis后台运行5、启动 1、下载redis包 https://redis.io/download/ 2、解压 tar -zxvf redis-7.2.3.tar.gz3、进入解压路径编译安装 cd redis-7.2.3 make && make install默认安装路径&#xff1a; …

CocosCreator 之 Tween缓动系统的使用

版本&#xff1a; 3.4.0 语言&#xff1a; TypeScript 环境&#xff1a; Mac 简介 在CocosCreator 3.x版本后&#xff0c; Tween缓动系统代替了原有的Action动作。官方使用缓动系统的主要目的之一是用于解决离线动画无法满足需求时的动态动画问题。 简单的示例&#xff1a; …

Linux文件操作应用及open和fork

1.文件操作的应用: 1).打开一个文件并往里面写入hello: #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <fcntl.h> #include <assert.h> int main() { int fdopen("file.txt",O_WRONLY|O_CREAT,0600); …

oracle免费资源 终止实例 以及新建一台实例的折腾记录

事情的背景是这样的&#xff0c;我的一台oracle小鸡&#xff0c;不太好用的样子&#xff0c;有时候SSH连不上&#xff0c;有时候莫名其妙卡住。所以我就想把它重新安装一下系统&#xff0c;恢复成最初的样子。 然后在网上查资料&#xff0c;是有办法把系统重装一下的。但是略微…

pdf加密文件解密(pdf文件解密小工具)

工具放在文章末尾&#xff01; 1.pdf文件加密后会有很多使用权限的限制很不方便&#xff0c;只要是为了pdf的数据不被二次利用&#xff0c;未加密的pdf功能都是可以正常使用的 2.加密后的pdf使用权限会被限制部分 3.工具只能解决pdf编辑等加密情况&#xff0c;不能解决文件打…

深度学习及其基本原理

深度学习的 Ups and Downs概念区分神经网络的构成深度学习基本原理深度学习的普遍近似定理扩展&#xff1a;反卷积网络——可视化每一层提取的特征 深度学习的 Ups and Downs 1958&#xff1a;感知机&#xff08;线性模型&#xff09;1969&#xff1a;感知机有局限性1980s&…

stream流和方法引用

1.Stream流 1.1体验Stream流【理解】 案例需求 按照下面的要求完成集合的创建和遍历 创建一个集合&#xff0c;存储多个字符串元素把集合中所有以"张"开头的元素存储到一个新的集合把"张"开头的集合中的长度为3的元素存储到一个新的集合遍历上一步得到的集…

Go 基本语法

一、​​​​变量定义方法 var 定义变量 var 变量名 类型 表达式 var name string "Snail" var age int 21 var isOK bool bool 2.类型推导方式定义变量 a 在函数内部&#xff0c;可以使用更简略的: 方式声明并初始化变量**注意&#xff1a;**短变量只能用于声…

MySQL数据库如何实现跨服务器访问数据

点击上方蓝字关注我 在使用MySQL数据库时&#xff0c;很多同学经常会问&#xff0c;我能跨服务器访问另一库的数据么&#xff1f;得到的答案很多时候是让人失望的。那么如果真的需要访问&#xff0c;又不想使用拷贝表及数据的方式&#xff0c;可以实现么&#xff0c;又该如何实…

【jupyter notebook中插件 nbextensions 安装失败分析与解决方法】

文章目录 问题描述分析与解决总结 问题描述 一开始在安装 notebook 中的插件 nbextensions 时根本没有注意到版本的适配问题&#xff0c;都是进行默认的安装&#xff0c;结果安装是最新版本的 notebook7.x&#xff0c;恰好 notebook7.x 版本不再适应插件 nbextensions&#xf…

【计算方法与科学建模】矩阵特征值与特征向量的计算(五):乘幂法的加速(带有原点移位的乘幂法)

文章目录 一、Jacobi 旋转法二、Jacobi 过关法三、Householder 方法四、乘幂法四、乘幂法的加速 矩阵的特征值&#xff08;eigenvalue&#xff09;和特征向量&#xff08;eigenvector&#xff09;在很多应用中都具有重要的数学和物理意义。 本文将详细介绍乘幂法的基本原理和步…

尚硅谷大数据项目《在线教育之实时数仓》笔记008

视频地址&#xff1a;尚硅谷大数据项目《在线教育之实时数仓》_哔哩哔哩_bilibili 目录 第10章 数仓开发之DWS层 P066 P067 P068 P069 P070 P071 P072 P073 P074 P075 P076 P077 P078 P079 P080 P081 P082 第10章 数仓开发之DWS层 P066 第10章 数仓开发之DW…

互联网金融智能风险防控技术要求

《互联网金融智能风险防控技术要求》 8月6日&#xff0c;国家市场监督管理总局和国家标准化管理委员会发布《互联网金融智能风险防控技术要求》&#xff08;GB/T 42929-2023&#xff09;&#xff08;以下简称“《要求》”&#xff09;&#xff0c;将于2023年12月1日实施。 《要…

MySQL数据库:外键、唯一键、唯一索引

目录 说明 一、如果要使用外键&#xff0c;表的存储引擎选择哪个&#xff1f; 1.1 答 1.2 示范 1.2.1 主表 &#xff08;1&#xff09;MyISAM的表&#xff1a;masterTable2 &#xff08;2&#xff09;InnoDB的表&#xff1a;masterTable1 1.2.2 从表 &#xff08;1&am…

图书管理系统源码,图书管理系统开发,图书借阅系统源码四TuShuManager应用程序MVC视图View

Asp.net web应用程序MVC之View视图 .ASP.NET MVC页面也就是要说的视图基本被放在Views文件夹下&#xff1b; 2.利用APS.NET MVC模板生成框架&#xff0c;Views文件夹下的默认页面为.cshtml页面&#xff1b; 3.ASP.NET MVC默认页面为Razor格式的页面&#xff0c;因此默认页面为.…

无人机光伏巡检代替人工,贵州电站运维升级

无人机光伏巡检如何做到降本增效&#xff1f;贵州省光伏电站有新招&#xff01;某70MWp的光伏电站通过引入复亚智能无人机光伏巡检系统&#xff0c;专注于使用无人机对区域内的光伏面板进行自动巡航巡查&#xff0c;利用自动化巡检和故障识别技术&#xff0c;显著提升了光伏电站…

UniPro集成华为云WeLink 为企业客户构建互为联接的协作平台

华为云WeLink是华为开启数字化办公体验、帮助企业实现数字化转型的实践&#xff0c;类似钉钉。UniPro的客户企业中&#xff0c;有使用WeLink作为协作工具的&#xff0c;基于客户的实际业务需求&#xff0c;UniPro实现了与WeLink集成的能力&#xff0c;以帮助客户企业丰富和扩展…

【触想智能】无风扇工控电脑一体机使用优势分析

无风扇工控电脑一体机是属于工控一体机分类中的其中一种&#xff0c;看名字&#xff0c;很明显就是没有散热风扇的工控电脑一体机&#xff0c;而平常我们使用的电脑主机是带有电源风扇、CPU散热风扇的。 无风扇工控电脑一体机的配置组成和商用电脑主机的配置基本一样&#xff0…

【读懂AUTOSAR】DoIP模块(1)-- 使用场景和链接的建立规范

引子 --什么是?为什么使用DoIP? DoIP就是通过IP进行诊断的意思(Diagnostic Over IP)。我们熟悉的诊断都是通过CAN总线的啊,为什么要通过IP?IP是什么? IP就是Internet Protocol,就是”互联网协议“啦! 那DoIP就是通过互联网进行的诊断喽,也可以叫做“基于以太网的诊…