【工业机器视觉】基于深度学习的水表盘读数识别(2-数据采集与增强)

【工业机器视觉】基于深度学习的仪表盘识读(1)-CSDN博客

数据采集与增强

        为了训练出适应多种表型和环境条件的模型,确保数据集的质量与多样性对于模型的成功至关重要。高质量的数据不仅需要准确无误、具有代表性,还需要涵盖尽可能广泛的情况以确保模型的泛化能力。

        面对现场数据采集可能遇到的困难,通过精心设计的数据采集策略,如自动采集、人工采集或两者结合的方式,可以有效获取初始数据集。此外,采用数据增强技术,例如图像变换及合成数据生成等方法,能够扩充数据集规模,增加其多样性,从而帮助克服数据不足的问题。

        最终,这些措施共同作用,有助于训练出更加稳定、鲁棒的机器学习模型,使其能够在不同条件下保持良好的性能表现。

数据采集

1. 准备USB工业相机

  • 选择相机:确保你选择的USB工业相机支持640x480分辨率。确认相机与你的计算机兼容(例如,是否需要额外驱动程序)。
  • 连接电源:如果相机需要外部电源,请确保按照制造商的说明正确连接。

2. 架设简易支架

  • 固定位置:使用简易支架将相机固定在水表上方,确保相机镜头垂直对准水表表盘中心。根据相机说明书调整焦距,使表盘清晰显示在画面中,且占据尽可能大的画面比例而不超出边界。
  • 稳定性:确保支架足够稳固,避免因震动或风力导致相机位移。

3. 使用简易吹风机

  • 轻柔操作:选择小型、功率较低的吹风机,仅用于轻轻吹动水表指针,以便于捕捉不同位置的读数。注意不要让吹风机过于接近水表以免造成损坏或干扰正常工作。
  • 安全第一:确保吹风机不会接触到水或其他可能导致电气危险的地方。

4. 确保合适的光照环境

  • 均匀照明:为避免阴影和反光,使用柔和而均匀的光源照亮水表表盘。可以考虑使用LED灯环等设备环绕在相机周围提供稳定的光线。
  • 避免直射光:防止阳光或强光源直接照射到表盘上产生眩光。

5. 创建自动保存图片的脚本

下面提供一个Python脚本:

import datetimeimport cv2
import random
import numpy as npcamera_path = 'svr-detect-pointer/ALL/1'
# camera_path = 'svr-detect-digit/ALL/1'cv2.namedWindow('camera', cv2.WINDOW_NORMAL)
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)times = 0
collect_freq = random.randint(30, 100)
file_idx = 0
start_collect = Falsewhile cap.isOpened():ret, frame = cap.read()if not ret:breakcv2.resizeWindow("camera", 640, 480)cv2.imshow('camera', frame)key = cv2.waitKey(1)if key == 27:breaktimes += 1if times >= collect_freq and start_collect:times = 0collect_freq = random.randint(30, 100)file_idx += 1filename = camera_path + '/' + datetime.datetime.now().strftime("%Y%m%d%H%M%S") + '_' + str(file_idx) + '.jpg'cv2.imwrite(filename, frame)print('save camera image success: ', filename)if key == ord('s'):times = 0start_collect = not start_collectif key == ord('g'):file_idx += 1filename = camera_path + '/' + datetime.datetime.now().strftime("%Y%m%d%H%M%S") + '_' + str(file_idx) + '.jpg'cv2.imwrite(filename, frame)print('save camera image success: ', filename)cap.release()
cv2.destroyAllWindows()

        确保电脑连接好相机,执行脚本,通过显示的画面调整好镜头焦距,保证表盘图像清晰可见。然后打开吹风机,看见表盘指针转动后,用鼠标左键点击到视频窗体上,然后按下s键,开始自动采集表盘图像。采集频率可以通过修改collect_freq = random.randint()中的参数调整。

        指针和字轮数字的数据采集最好分开。

表盘指针        

针对指针的图片不需要很多,每种表型300张左右即可,短时间可采集完成。图片示例:

04f4fdf29aa344f18393bc619a6600b9.pngd3fec9a6335c40b1a34eb1cb85e200d5.png

表盘字轮

针对字轮数字的图片采集时间相对较长,需要观察每种表型字轮数字差异,不需要每种表型都采集一遍,选择一种常用表型即可,需要从最低两位00采集到99。图片示例:

85ae354199da4b31928d0c000ba27e8f.pngc0add96952834a47b71c938c3d71fa21.png

数据增强

        基于Ultralytics YOLO框架,在训练时,会对数据进行增强:翻转、剪裁、组合等,已能满足数据多样性的扩充,但是针对表盘字轮数字,由于我们只采集了最后两位数字的变化,所以需要自己实现对其他位置的字轮数字进行数据补充。(如果只识别到最后两位,可以不进行额外的数据增强操作)

字轮数字

  1. 将采集的图片,最后两位字轮数字位置全部剪裁,保存到本地
  2. 将剪裁出来的纯字轮数字图片,随机取出,覆盖到其他字轮数字位置上即可
  3. 由于字轮数字走字时的特殊性,不需要对剪裁出来的数字图片在其他位置上进行全数字覆盖和生成,只需要关注数字的过渡状态即可

提供一个参考代码:

if __name__ == '__main__':for filename in os.listdir('ALL/6/'):cut_digit('ALL/6/' + filename, filename.split('.')[0])gen_img(f'ALL/cut/{meter_name}/base.jpg')

cut_digit:对采集的最后两位字轮数字的图片进行剪裁

meter_name = 'wx_meter6'
top = 117
digit_pos = [(182, top), (210, top), (239, top), (267, top), (295, top)]
w_size, h_size = 26, 34def cut_digit(image_path, filename):image = cv2.imread(image_path)cut_img = image[digit_pos[4][1]:digit_pos[4][1] + h_size, digit_pos[4][0]:digit_pos[4][0] + w_size]cv2.imwrite(f'ALL/cut/{meter_name}/digits/{filename}.jpg', cut_img)

gen_img:随机组合生成新的图片,对高3位字轮数字位置

def gen_img(base_img_path):nine_digit_path = f'ALL/cut/{meter_name}/digits/9/'i_count = 0base_image = cv2.imread(base_img_path)for i in range(10):cut_d_path = f'ALL/cut/{meter_name}/digits/{i}/'cut_files = os.listdir(cut_d_path)for cut_file in cut_files:cut_img = cv2.imread(cut_d_path + cut_file)pos = digit_pos[0]base_image[pos[1]:pos[1] + h_size, pos[0]:pos[0] + w_size] = cut_img[::]nine_digit_files = os.listdir(nine_digit_path)random.shuffle(nine_digit_files)cut_img = cv2.imread(nine_digit_path + nine_digit_files[0])pos = digit_pos[1]base_image[pos[1]:pos[1] + h_size, pos[0]:pos[0] + w_size] = cut_img[::]random.shuffle(nine_digit_files)cut_img = cv2.imread(nine_digit_path + nine_digit_files[0])pos = digit_pos[2]base_image[pos[1]:pos[1] + h_size, pos[0]:pos[0] + w_size] = cut_img[::]random.shuffle(nine_digit_files)cut_img = cv2.imread(nine_digit_path + nine_digit_files[0])pos = digit_pos[3]base_image[pos[1]:pos[1] + h_size, pos[0]:pos[0] + w_size] = cut_img[::]i_count += 1save_path = f'{meter_name}_{i}_gen_{i_count}'cv2.imwrite(f'ALL/gen/{meter_name}/{save_path}.jpg', base_image)print(f'gen image success {save_path}.jpg')base_image = cv2.imread(base_img_path)for i in range(10):cut_d_path = f'ALL/cut/{meter_name}/digits/{i}/'cut_files = os.listdir(cut_d_path)for cut_file in cut_files:cut_img = cv2.imread(cut_d_path + cut_file)pos = digit_pos[1]base_image[pos[1]:pos[1] + h_size, pos[0]:pos[0] + w_size] = cut_img[::]nine_digit_files = os.listdir(nine_digit_path)random.shuffle(nine_digit_files)cut_img = cv2.imread(nine_digit_path + nine_digit_files[0])pos = digit_pos[2]base_image[pos[1]:pos[1] + h_size, pos[0]:pos[0] + w_size] = cut_img[::]random.shuffle(nine_digit_files)cut_img = cv2.imread(nine_digit_path + nine_digit_files[0])pos = digit_pos[3]base_image[pos[1]:pos[1] + h_size, pos[0]:pos[0] + w_size] = cut_img[::]i_count += 1save_path = f'{meter_name}_{i}_gen_{i_count}'cv2.imwrite(f'ALL/gen/{meter_name}/{save_path}.jpg', base_image)print(f'gen image success {save_path}.jpg')base_image = cv2.imread(base_img_path)for i in range(10):cut_d_path = f'ALL/cut/{meter_name}/digits/{i}/'cut_files = os.listdir(cut_d_path)for cut_file in cut_files:cut_img = cv2.imread(cut_d_path + cut_file)pos = digit_pos[2]base_image[pos[1]:pos[1] + h_size, pos[0]:pos[0] + w_size] = cut_img[::]nine_digit_files = os.listdir(nine_digit_path)random.shuffle(nine_digit_files)cut_img = cv2.imread(nine_digit_path + nine_digit_files[0])pos = digit_pos[3]base_image[pos[1]:pos[1] + h_size, pos[0]:pos[0] + w_size] = cut_img[::]i_count += 1save_path = f'{meter_name}_{i}_gen_{i_count}'cv2.imwrite(f'ALL/gen/{meter_name}/{save_path}.jpg', base_image)print(f'gen image success {save_path}.jpg')base_image = cv2.imread(base_img_path)for i in range(10):cut_d_path = f'ALL/cut/{meter_name}/digits/{i}/'cut_files = os.listdir(cut_d_path)for cut_file in cut_files:cut_img = cv2.imread(cut_d_path + cut_file)pos = digit_pos[3]base_image[pos[1]:pos[1] + h_size, pos[0]:pos[0] + w_size] = cut_img[::]i_count += 1save_path = f'{meter_name}_{i}_gen_{i_count}'cv2.imwrite(f'ALL/gen/{meter_name}/{save_path}.jpg', base_image)print(f'gen image success {save_path}.jpg')

表盘指针

  1. 图片采集:对于表盘指针的多样性图片采集,由于其相对简单的背景和固定的模式,确实较容易进行。这意味着在这一阶段,不需要额外的数据增强技术来增加数据集的多样性。

  2. 人工标注:在人工标注过程中,只对最低位指针进行了直接标注,而高位指针的读数需要通过计算梅花针(用来指示更精细或更高位数值的特殊指针)的方向和角度来间接获取。这表明,为了准确地得到所有指针的读数,不仅需要识别指针的位置,还需要理解它们之间的相对位置以及与表盘刻度的关系。

  3. 分割模型引入:为了更精确地处理梅花针区域,引入一个新的分割模型来专门针对这部分进行处理。分割模型可以帮助精确定位梅花针所在的区域,从而简化后续的角度计算过程。

  4. 单独区域标注:既然分割出的梅花针区域对于计算顶点方向非常重要,那么为这些区域创建独立的图像,并对其进行人工标注,将有助于训练更加精准的分割模型。这样做的好处是可以专注于特定区域内的特征,提高模型对该部分的理解和预测能力。

  5. 数据准备与标注

    • 对于已经采集到的表盘图像,可以使用现有的目标检测模型初步定位指针。
    • 然后利用分割模型专注于梅花针区域,确保能够准确提取该区域。
    • 最后,人工标注员只需关注分割出来的梅花针区域,标记出必要的信息(如顶点位置等),以便后续用于计算角度和读数。

提供一个指针区域剪裁的代码:

import osimport cv2AREAS = {'A': [260, 360, 383, 471]
}if __name__ == '__main__':for name in ['A']:for filename in os.listdir('ALL/' + name):src_path = os.path.curdir + '/ALL/' + name + '/' + filenamedst_path = os.path.curdir + '/CUTS/' + filenameimg = cv2.imread(src_path)cut_img = img[AREAS[name][0]:AREAS[name][1], AREAS[name][2]:AREAS[name][3]]cv2.imwrite(dst_path, cut_img)

AREAS是已经采集的表盘图片目录,一般来说梅花针切割任务不需要太关注表盘多样性,因为只针对指针区域部分,所有表盘之间的差异很小。

上面已完成数据准备,下面准备开始进行数据标注(使用lableme和labelimg两个库,然后进行数据转换)!

【工业机器视觉】基于深度学习的仪表盘识读(3)-CSDN博客

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

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

相关文章

vscode通过ssh连接远程服务器(实习心得)

一、连接ssh服务器 1.打开Visual Studio Code,进入拓展市场(CtrlShiftX),下载拓展Remote - SSH 2. 点击远程资源管理器选项卡,并选择远程(隧道/SSH)类别 3. 点击ssh配置:输入你的账号主机ip地址 4.在弹出的选择配置文件中&#xf…

Maven(生命周期、POM、模块化、聚合、依赖管理)详解

Maven构建项目的生命周期 在Maven出现之前,项目构建的生命周期就已经存在,软件开发人员每天都在对项目进行清理,编译,测试,部署等工作,这个过程就是项目构建的生命周期。虽然大家都在不停的做构建工作&…

webstorm开发uniapp(从安装到项目运行)

1、下载uniapp插件 下载连接:Uniapp Tool - IntelliJ IDEs Plugin | Marketplace (结合自己的webstorm版本下载,不然解析不了) 将下载到的zip文件防在webstorm安装路径下,本文的地址为: 2、安装uniapp插…

unique_ptr自定义删除器,_Compressed_pair利用偏特化减少存储的一些设计思路

主要是利用偏特化, 如果自定义删除器是空类(没有成员变量,可以有成员函数): _Compressed_pair会继承删除器(删除器作为基类),但_Compressed_pair里不保存删除器对象,只…

【数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】

目录😋 任务描述 相关知识 测试说明 我的通关代码: 测试结果: 任务描述 本关任务:编写一个程序实现环形队列的基本运算。 相关知识 为了完成本关任务,你需要掌握: 初始化队列、销毁队列、判断队列是否为空、进队列…

路由器、二层交换机与三层交换机的区别与应用

路由器、二层交换机和三层交换机是常见的网络设备,常常协同工作。它们都可以转发数据,但在功能、工作层级以及应用场景上存在差异。 1. 工作层级 三者在OSI模型中的工作层级不同: 路由器: 工作在 网络层(第三层&#…

SQL计算字段:拼接字段

为了说明如何使用计算字段,本文将通过一个简单的示例来展示如何将两列组合成一个标题。假设Vendors表包含供应商的名称和国家信息,我们希望生成一个报表,其中列出每个供应商的名称和所在国家,并且需要格式化名称显示,国…

高级数据结构-树状数组

介绍 树状数组的推导 两个基础操作 模板-acwing795. 前缀和 #include<bits/stdc.h> using namespace std;const int N 1e610; int c[N]; int lowbit(int x){return x & -x; }int query(int x){int ans 0;for(; x; x - lowbit(x)) ans c[x];return ans; }void add…

香港科技大学广州|智能交通学域博士招生宣讲会—湖南大学专场

香港科技大学广州&#xff5c;智能交通学域博士招生宣讲会—湖南大学专场 &#x1f559;时间&#xff1a;2024年12月17日&#xff08;星期二&#xff09;15:00 &#x1f3e0;地点&#xff1a;湖南大学二办公楼三楼学生就业指导中心329 &#x1f517;报名链接&#xff1a;http…

node利用路由搭建web实例

npm init npm i express body-parser cookie-parser 封装web实例 搭建路由 导出web 应用实例注册

MFC案例:基于对话框的简易阅读器

一、功能目标&#xff1a; 1.阅读txt文件 2.阅读时可以调整字体及字的大小 3.打开曾经阅读过的文件时&#xff0c;能够自动从上次阅读结束的位置开始显示&#xff0c;也就是能够保存和再次使用阅读信息。 4.对于利用剪贴板粘贴来的文字能够存储成txt文件保存。 5.显示…

端点鉴别、安全电子邮件、TLS

文章目录 端点鉴别鉴别协议ap 1.0——发送者直接发送一个报文表明身份鉴别协议ap 2.0——ap1.0 的基础上&#xff0c;接收者对报文的来源IP地址进行鉴别鉴别协议ap 3.0——使用秘密口令&#xff0c;口令为鉴别者和被鉴别者之间共享的秘密鉴别协议ap 3.1——对秘密口令进行加密&…

电脑技巧:Everything 1.5 版本重大更新​支持拼音搜索+全文搜索

目录 一、软件介绍 二、主要更新亮点 更快的搜索速度和拼音搜索 全文搜索功能 智能推荐功能 增强的过滤选项 改进的用户界面 更好的多语言支持 增强的安全性和隐私保护 三、总结 Everything 作为一款备受推崇的文件搜索工具&#xff0c;以其卓越的性能和简洁的用户界…

element左侧导航栏

由element组件搭建的左侧导航栏 预览: html代码: <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>首页</title><style> /*<!-- 调整页面背景颜色-->*/body{background-colo…

Datax可视化工具Datax-web安装部署

文章目录 一、Datax-web官网二、Datax-web介绍 1、Datax-web概述2、架构图3、系统环境要求4、特性支持 三、安装部署 1、环境准备2、Datax-web安装包准备 一、Datax-web官网 github&#xff1a;Datax-web gitee: Datax-web 二、Datax-web介绍 1、Datax-web概述 DataX Web…

node-js Express中间件

中间件介绍 什么是中间件 中间件其本质就是一个回调函数&#xff0c;可以像路由一样访问请求对象&#xff08;request&#xff09;和响应对象&#xff08;response&#xff09;。中间件的作用是什么 通过函数封装公共操作&#xff0c;简化代码中间件类型 - 全局中间件 - 路由中…

【数学】矩阵的逆与伪逆 EEGLAB

文章目录 前言matlab代码作用EEGLAB 中的代码总结参考文献 前言 在 EEGLAB 的使用中&#xff0c;运行程序时出现了矩阵接近奇异值&#xff0c;或者缩放错误。结果可能不准确。RCOND 1.873732e-20 的 bug&#xff0c;调查 EEGLAB 后发现是 raw 数据的问题。 matlab代码 A_1 …

TCP 的三次握手与四次挥手

TCP 的三次握手与四次挥手 TCP 协议&#xff0c;使用 TCP 的三次握手建立连接&#xff0c;使用四次挥手断开连接。 我们先看看基本的计算机网络中的TCP连接建立和断开的过程。 1.HTTP 三次握手 HTTP 的三次握手过程如下&#xff1a; 第一次握手&#xff08;SYN&#xff09;…

解锁前端开发速度的秘密武器【Vite】

在前端开发的江湖中&#xff0c;有人偏爱 Webpack 的强大与稳定&#xff0c;有人钟情于 Rollup 的轻量与高效。而 Vite&#xff0c;这个后来居上的工具&#xff0c;却以“极致的快”和“极简的易”赢得了开发者的芳心。众所周知万事都有缘由&#xff0c;接下来我们就来深度剖析…

【动态库.so | 头文件.hpp】基于CMake与CMakeList编写C++自定义库

前言 最近比较忙&#xff0c;其他系列教程得等到年后一起更&#xff01;请大家多多包涵&#xff01;&#xff01;相信各位在配置C环境和各类库的时候一定经常看到如下小连招 git clone https://github.com/opencv/opencv.git cd opencv mkdir build && cd build cma…