欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 新闻 > 国际 > 【工业机器视觉】基于深度学习的水表盘读数识别(2-数据采集与增强)

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

2024/12/22 1:12:50 来源:https://blog.csdn.net/u014608435/article/details/144295575  浏览:    关键词:【工业机器视觉】基于深度学习的水表盘读数识别(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博客

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com