文章目录
- 前言
- 1 训练一个YOLOV8的一维码检测模型
- 2 创建滑动窗口
- 2.1 模块导入与测试图片展示
- 2.2 创建滑动窗口检测,窗口大小为(640,640),滑动距离为640。对不足(640,640)的窗口进行填充
- 3 创建onnxruntime推理引擎
- 3.1推理测试
- 3.2获得ONNX模型输入层(输出层)和数据维度
- 3.3 预处理-构造输入张量 torch_list
- 4 执行推理预测
- 5 后处理-置信度过滤、NMS过滤
- 5.1解析目标检测预测结果
- 6 opencv 可视化
- 7 检测完的切片重新放回原图
- 8 用Zbar识别二维码
- 8.1条码剪切
- 8.1 筛选出选合格的条码
- 总结
- 相关文章
原创声明:如有转载请注明文章来源。码字不易,如对卿有所帮助,欢迎评论、点赞、收藏。
本文为深度学习的条码检测方案,如需传统算法的条码检测方案请阅读(基于Opencv+Kmeans+Zbar的条码检测与基于锐化+双边高斯滤波+Zbar的条码检测在工业光伏产线上的检测效果研究)
前言
最近项目中用到了条码检测,查阅很多资料,说用Zbar等工具检测的比较多。但是我们会发现,检测是不稳定的,Zbar是解析条码的工具包,运用好它的前提是:能够准确将条码区域提取出来,以及图像质量(分辨率、打光效果等)要把握很好。本文基于YOLOV8+OnnxRuntime部署+滑动窗口+Zbar对于条码检测进行升级,可以有效解决条码检测问题,并且速度也很高。
1 训练一个YOLOV8的一维码检测模型
-
关于如何训练模型不是本文的重点,可以根据这篇文章简单训练一个模型(YOLOV8目标检测——模型训练)
-
如果需要本人训练好的模型可以在点击这里下载( 文章顶部文件包:best.pt、best.onnx、 数据集、实验图片)
-
当然若有训练和环境的相关问题,可以在评论里写明。看到一定回复。
2 创建滑动窗口
2.1 模块导入与测试图片展示
import cv2
import numpy as np
from PIL import Image
import onnxruntime
import torch
# 有 GPU 就用 GPU,没有就用 CPU
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
import matplotlib.pyplot as plt
%matplotlib inline
```在这里插入图片描述
```python
# 显示图片
image = cv2.imread("D:/yolov8/data/images/Pic_2023_04_18_104022_3.bmp")
plt.imshow(image[:,:,::-1])
image_1 = image.copy()
2.2 创建滑动窗口检测,窗口大小为(640,640),滑动距离为640。对不足(640,640)的窗口进行填充
win = 0
image_list = [] # 用来存储滑动窗口
while(win<4000):
img_test = image[340:980,win:win+640]
if img_test.shape[1]<640:
top = 0
bottom = 0
left = 0
right = 640-img_test.shape[1]
img_test = cv2.copyMakeBorder(img_test,top,bottom,left,right,cv2.BORDER_CONSTANT,value=[255,255,255])
image_list.append(img_test)
win = win + 640
展示窗口切片
for i in range(len(image_list)):
plt.subplot(2,4,i+1)
plt.imshow(image_list[i][:,:,::-1])
plt.show()
3 创建onnxruntime推理引擎
ort_session = onnxruntime.InferenceSession('E:/ultralytics/runs/detect/train4/weights/best.onnx', providers=['CUDAExecutionProvider', 'CPUExecutionProvider'])
3.1推理测试
x = torch.randn(1, 3, 640, 640).numpy()
ort_inputs = {'images': x}
ort_output = ort_session.run(['output0'], ort_inputs)[0]
ort_output
输出了结果,说明onnxruntime引擎没有问题。
3.2获得ONNX模型输入层(输出层)和数据维度
3.3 预处理-构造输入张量 torch_list
这里是标准的yolov8输入图像的预处理模式
# 有 GPU 就用 GPU,没有就用 CPU
torch_list = []
for i in range(len(image_list)):
# 预处理-归一化
image = image_list[i][:,:,::-1]
image = image / 255
# 预处理-构造输入 Tensor
image = np.expand_dims(image, axis=0) # 加 batch 维度
image = image.transpose((0, 3, 1, 2)) # N, C, H, W
image = np.ascontiguousarray(image) # 将内存不连续存储的数组,转换为内存连续存储的数组,使得内存访问速度更快
image = torch.from_numpy(image).to(device).float() # 转 Pytorch Tensor
# input_tensor = input_tensor.half() # 是否开启半精度,即 uint8 转 fp16,默认转 fp32
torch_list.append(image)
可以查看一下相关数据
4 执行推理预测
当你在对切片处理的时候,还需要用字典去标记切片的,即:{切片:目标检测结果}。这个字典是贯穿始终的,能够保证最后把检测之后的切片,贴到原图的时候,知道那个预测结果是哪个窗口预测得来的。
preds_dict = {}
for i in range(len(torch_list)):
# ONNX Runtime 推理预测
ort_output = ort_session.run(output_name, {input_name[0]: torch_list[i].cpu().numpy()})[0]
# 转 Tensor
preds = torch.Tensor(ort_output)
preds_dict[str(i)] = preds
5 后处理-置信度过滤、NMS过滤
from ultralytics.utils import ops
for i in range(len(preds_dict)):
pred = ops.non_max_suppression(preds_dict[str(i)], conf_thres=0.7, iou_thres=0.7, nc=1)
preds_dict[str(i)] = pred[0]
经过非极大值抑制,条码基本都被标记出来了。
5.1解析目标检测预测结果
pred_det = []
num_bboxs = {} # 画框框的记录,需要标记好每个切片的目标检测数量
for i in range(len(preds_dict)):
det = preds_dict[str(i)][:,0:6].cpu().numpy()
preds_dict[str(i)] = det
num_bboxs[str(i)] = len(det)
这里的类别会变成0,本文中0代表条码
6 opencv 可视化
# 框(rectangle)可视化配置
bbox_color = (150, 0, 0) # 框的 BGR 颜色
bbox_thickness = 6 # 框的线宽
# 框类别文字
bbox_labelstr = {
'font_size':2, # 字体大小
'font_thickness':3, # 字体粗细
'offset_x':0, # X 方向,文字偏移距离,向右为正
'offset_y':-10, # Y 方向,文字偏移距离,向下为正
}
for i in range(len(preds_dict)):
# 遍历每个框
for j in range(len(preds_dict[str(i)])):
# 获取该框坐标
bboxes_xyxy = preds_dict[str(i)][:, :4].astype('uint32')
bbox_xyxy = bboxes_xyxy[j]
# 获取框的预测类别(对于关键点检测,只有一个类别)
bbox_label = 'code'
# 画框
image_list[i] = cv2.rectangle(image_list[i], (bbox_xyxy[0], bbox_xyxy[1]), (bbox_xyxy[2], bbox_xyxy[3]), bbox_color, bbox_thickness)
# 写框类别文字:图片,文字字符串,文字左上角坐标,字体,字体大小,颜色,字体粗细
image_list[i] = cv2.putText(image_list[i], bbox_label, (bbox_xyxy[0]+bbox_labelstr['offset_x'], bbox_xyxy[1]+bbox_labelstr['offset_y']), cv2.FONT_HERSHEY_SIMPLEX, bbox_labelstr['font_size'], bbox_color, bbox_labelstr['font_thickness'])
for i in range(len(image_list)):
plt.subplot(2,4,i+1)
plt.imshow(image_list[i][:,:,::-1])
plt.show()
运行结果如下:
7 检测完的切片重新放回原图
wide = 0
for i in range(len(image_list)):
wide += 640
if wide < 4000:
image_1[340:980,i*640:640*(i+1)] = image_list[i]
else:
image_1[340:980,3840:4000] = image_list[i][:,0:160]
plt.imshow(image_1[:,:,::-1])
8 用Zbar识别二维码
8.1条码剪切
codes = []
for i in range(len(preds_dict)):
# 遍历每个框
for j in range(len(preds_dict[str(i)])):
# 获取该框坐标
bboxes_xyxy = preds_dict[str(i)][:, :4].astype('uint32') # 目标检测预测结果:左上角X、左上角Y、右下角X、右下角Y、置信度、类别ID
bbox_xyxy = bboxes_xyxy[j]
code = image_list[i][bbox_xyxy[1]:bbox_xyxy[3],bbox_xyxy[0]:bbox_xyxy[2]]
codes.append(code)
# 展示一下提取到的条码图片
for i in range(len(codes)):
plt.subplot(4,5,i+1)
plt.imshow(codes[i],cmap="gray")
plt.show()
此时我们会发现条码中是有一个条码被标注上两个框的。15个目标,但涵盖了16个检测框。
8.1 筛选出选合格的条码
条码的宽度如果小于条码的一半,则剔除该条码
for code in codes:
if code.shape[1]<20: # 去除宽度小于20的条码
codes.remove(code)
```python
# 展示一下提取到的条码图片
for i in range(len(codes)):
plt.subplot(4,5,i+1)
plt.imshow(codes[i],cmap="gray")
plt.show()
from pyzbar import pyzbar
res_1=[]
for i in range(len(codes)):
res = pyzbar.decode(codes[i])
res_1.append(res)
res_1
但是我们会发现,最后检测出的15个目标,只有14个条码。那是因为在滑动窗口中是需要去调节滑动窗口的大小以及滑动的距离。总得来说,将滑动距离从640变为320会是比较好的选择,最后对于检测出的条码再做一下处理即可。
总结
市面上有很多条码、二维码检测的算法,最让人印象深刻的就是腾讯微信扫码——基于SSD和超分算法的二维码检测方式。本文深受启发,将SSD的提取网络换成YOLOV8,将二维码的提取换成一维码。总的来说,试一次不错的体验。
本人是一名计算机视觉应用工程师,喜欢将算法应用于实际当中,这是自己的乐趣,如有什么需要讨论,欢迎评论区留言。
如您百忙之中还看到了这里,那是缘分。想来您和我一样对深度学习的应用深有兴趣,还请您帮忙点个赞,以便于更多的你我这样的人发现本文章,谢谢。
相关文章
基于Opencv+Kmeans+Zbar的条码检测与基于锐化+双边高斯滤波+Zbar的条码检测在工业光伏产线上的检测效果研究