普通表计读数开发思路

一、普通表计类型介绍🍉

常见的普通表计有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 = ipoints = 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 npdef 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 gooddef 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,desdef 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.2ptsB = ptsB / 0.2ransacReprojThreshold = 4H, 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 imgOutelse:return img2def cv_imread(file_path):"""能读取中文路径的cv2读图函数。"""cv_img = cv2.imdecode(np.fromfile(file_path,dtype=np.uint8),-1)return cv_imgdef 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_alignif __name__ == "__main__":t0_path = r".\_1723957234138288128.jpg"t1_path = r".\1723957234138288128_20231115_200243.jpg"align(t0_path, t1_path)

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

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

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

相关文章

linux 磁盘管理、分区管理常用命令

文章目录 基础命令挂载新硬盘/分区添加内存交换分区swaplvm分区管理模式 基础命令 查看目录文件大小 du -sh /backup du -sh /backup/* du -sh *查看磁盘挂载信息 df -lhT查看某个目录挂载在哪个分区&#xff0c;以及分区的磁盘使用情况 df [目录] #例如&#xff1a;df /ho…

(二) Windows 下 Sublime Text 3 安装离线插件 Anaconda

1 下载 Sublime Text 3 免安装版 Download - Sublime Text 2 下载 Package Control&#xff0c;放到 Sublime Text Build 3211\Data\Installed Packages 目录下。 Installation - Package Control 3 页面搜索 anaconda anaconda - Search - Package Control Anaconda - Pac…

vue3通过provide和inject实现多层级组件通信

父组件 <template><div><h1>我是父组件 {{num}}</h1><hr><child></child></div> </template><script setup> import child from ./child.vue; import { ref,provide } from vue; let num ref(520) provide(pare…

kafka的详细安装部署

简介&#xff1a; Kafka是一个分布式流处理平台&#xff0c;主要用于处理高吞吐量的实时数据流。Kafka最初由LinkedIn公司开发&#xff0c;现在由Apache Software Foundation维护和开发。 Kafka的核心是一个分布式发布-订阅消息系统&#xff0c;它可以处理大量的消息流&#…

PHP TCP服务端监听端口接收客户端RFID网络读卡器上传的读卡数据

本示例使用设备&#xff1a;WIFI/TCP/UDP/HTTP协议RFID液显网络读卡器可二次开发语音播报POE-淘宝网 (taobao.com) <?php header("content-type:text/html;charsetGBK");set_time_limit(0); $port39169; //监听端口if(($socket socket_create(AF_INET, SOCK…

共享模型之不可变

前言 该文章后续还需要进行修改&#xff01;&#xff01; 不可变的解释是对象属性不可以更改。 在多线程下&#xff0c;格式转化使用SimpleDateFormat可能会报错。这是因为线程之间互相影响导致。 public class test {public static void main(String[] args) {SimpleDateFo…

抽象类-Java

抽象类 一、父类方法的不确定性二、抽象类介绍三、抽象类细节四、练习题 一、父类方法的不确定性 引入&#xff1a;对于一个动物&#xff0c;不知道它吃什么&#xff0c;比如猫吃鱼&#xff0c;兔子吃萝卜。动物类中的 eat 方法往往由它的子类去具体实现。 class Animal {pub…

qgis添加arcgis的FeatureServer

左侧浏览器-ArcGIS要素服务器-新建连接 http://sampleserver6.arcgisonline.com/arcgis/rest/services/ 展开-双击即可

sql中group by和having的使用

group by&#xff1a;按照某个字段或者某些字段进行分组。 having&#xff1a;对分组之后的数据进行再次过滤&#xff0c;having必须和group by一起用&#xff0c;且在group by后面。 比如person表如下&#xff08;以下查询均基于此表&#xff09;&#xff1a; 1.group by 用法…

为何要隐藏IP地址?网络上哪些行为需要隐藏IP和更换IP?

网络已经成为现代人生活的重要组成部分&#xff0c;人们在网络上交流、学习、娱乐、购物等。但是&#xff0c;在享受网络带来的便利时&#xff0c;我们也需要时刻保护自己的隐私和安全。其中&#xff0c;IP地址作为网络通信中的重要标识&#xff0c;如何隐藏以及在哪些情况下需…

C语言题目强化-DAY12

题型指引 一、选择题二、编程题 ★★写在前面★★ 本题库源自互联网&#xff0c;仅作为个人学习使用&#xff0c;记录C语言题目练习的过程&#xff0c;如果对你也有帮助&#xff0c;那就点个赞吧。 一、选择题 1、请阅读以下程序&#xff0c;其运行结果是&#xff08; &#x…

CMake语法解读 | Qt6需要用到

CMake 入门CMakeLists.txtmain.cpp编译示例cmake常用参数入门 Hello CMake CMake 是一个用于配置跨平台源代码项目应该如何配置的工具建立在给定的平台上。 ├── CMakeLists.txt # 希望运行的 CMake命令 ├── main.cpp # 带有main 的源文件 ├── include # 头文件目录 …

GLM: 自回归空白填充的多任务预训练语言模型

当前&#xff0c;ChatGLM-6B 在自然语言处理领域日益流行。其卓越的技术特点和强大的语言建模能力使其成为对话语言模型中的佼佼者。让我们深入了解 ChatGLM-6B 的技术特点&#xff0c;探索它在对话模型中的创新之处。 GLM: 自回归空白填充的多任务预训练语言模型 ChatGLM-6B 技…

C++二分查找视频教程:两数之和

作者推荐 利用广度优先或模拟解决米诺骨牌 本文涉及的基础知识点 二分查找算法合集 题目 给你一个下标从 1 开始的整数数组 numbers &#xff0c;该数组已按 非递减顺序排列 &#xff0c;请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 n…

Webhook端口中的自签名身份验证

概述 有时&#xff0c;可能需要通过 Webhook 端口从交易伙伴处接收数据&#xff0c;但该交易伙伴可能需要更多的安全性&#xff0c;而不仅仅是用于验证入站 Webhook 请求的基本身份验证用户名/密码 – 或者您可能只想在入站 Webhook 消息上添加额外的安全层。 使用 Webhook 端…

使用STM32和蓝牙模块进行无线数据传输的实践

无线数据传输在现代通信领域中具有重要的地位&#xff0c;而蓝牙技术是一种常用的无线数据传输技术。本文介绍了如何使用STM32微控制器和蓝牙模块实现无线数据传输的方案&#xff0c;包括硬件设计、蓝牙模块配置、数据发送和接收等步骤&#xff0c;并给出相应的代码示例。 一、…

Codebeamer—软件全生命周期管理轻量级平台

产品概述 Codebeamer涵盖了软件研发的生命周期&#xff0c;在一个整合的平台内支持需求管理、测试管理、软件开发过程管理以及项目管理等&#xff0c;同时具有IToperations&DevOps相关的内容&#xff0c;并支持变体管理的功能。对于使用集成的应用程序生命周期管理&#xf…

13.端点、簇、属性

源码地址&#xff1a;13.端点、簇、属性 端点&#xff08;endPoint&#xff09; 一个端点就是一个应用 一个字节编号&#xff0c;数据收和发送的基本单元&#xff0c;在模块通信的时候&#xff0c;发送模块必须指定收发双方模块的网络地址和端点。端点要使用必须要和模块里的…

MFC添加窗体菜单栏和消息响应

在资源视图右键,添加资源,选择Menu,新建 添加的菜单在资源菜单的Menu目录下 双击直接编辑输入菜单 之后在要添加菜单的窗体的属性Menu里面填写菜单的ID就可以了 如何给菜单添加点击响应? OnCommand是MFC中的一个消息处理函数,用于处理在窗口或控件被激活时发出的WM_CO…

java.sql.SQLException: No suitable driver 问题解决

问题出现 自己在写一个连接C3P0数据库连接池库的测试类&#xff0c;运行该类后出现了下图这个问题 这是我写的测试类 package demo;import com.mchange.v2.c3p0.ComboPooledDataSource;import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLExcept…