- 前言
- OpenCV 学习总结.番外篇 cv::dnn::blobFromImage()
- python中的图像预处理
- 常用的图像预处理方法
- 强大的Numpy
- YOLOX通过Numpy进行预处理
- 被埋没的cv2.dnn.blobFromImage()
- C++中的图像预处理
- Mat数据重排
- 懒人必备之cv::dnn::blobFromImage()接口
部署基于深度学习的CV算法时,几乎都会涉及到输入图像预处理。
在python中,图像预处理的任务基本上被numpy承包了。
But,当部署需要通过C++实现时,问题就出现了:numpy不能用,怎么样把图像转换为模型接受的格式呢?
OpenCV 学习总结.番外篇 cv::dnn::blobFromImage() python中的图像预处理首先需要知道为什么有图像预处理这个步骤,包括但不限于以下原因:
- 数据集内的图片尺寸不一定相同,而训练时的输入尺寸必须固定
- 模型输入尺寸不能太大,否则训练占用大量内存显存
- 多通道图像的各个通道对训练目标的贡献不一定相同
- 将训练数据放到同一个分布维度上,有利于提升泛化能力
- 部分CV任务对光照敏感,需要通过图像处理进行调整
- 数据增强相关
而神经网络根据数据集学习出来了一种拟合方法,在预测时,用于预测的数据自然也要符合数据集的分布。
常用的图像预处理方法- 尺寸修改 (resize)
- 颜色通道互换 (swap)
- 图像标准化 (normalize)
- 图像维度互换 (transpose)
- 增加维度 (expand)
- 图像截取(crop)
- 图像补充 (padding)
- 数组重排 (reshape)
python中常用的图像处理库opencv和PIL都可以与numpy数组无缝衔接,pytorch、tf、paddle等深度学习框架也能够直接转换numpy数据,并且numpy数组切片和处理都十分强大,因此几乎垄断了CV里的预处理和后处理实现方法。
YOLOX通过Numpy进行预处理YOLOX是经典目标检测系列YOLO的最新算法,它在训练和推理时的图像预处理步骤为:resize=>padding=>swap=>normalize=>transpose=>expand,具体实现如下:
# input_size是模型需要的输入尺寸,image是cv2.imread的结果
image_padded = np.ones([self.input_size[0], self.input_size[1], 3], dtype=np.float32) * 114.0
# resize
r = min(self.input_size[0]/image.shape[0], self.input_size[1]/image.shape[1])
image_resized = cv2.resize(image, (int(image.shape[1] * r), int(image.shape[0] * r)), cv2.INTER_LINEAR)
# padding
image_padded[:int(image.shape[0] * r), :int(image.shape[1] * r), :] = image_resized
# swap RB channels
img = image_padded[:, :, ::-1]
# normalize
img = (img - self.mean)/self.std
# expand
img = np.expand_dims(img.transpose(2, 0, 1), axis=0)
model_input = np.ascontiguousarray(img, dtype=np.float32)
熟悉了numpy切片操作就能轻易完成YOLOX的图像预处理步骤。
被埋没的cv2.dnn.blobFromImage()实际上opencv本身也提供了一个预处理接口blobFromImage,python中的原型如下:
cv2.dnn.blobFromImage(image[, scalefactor[, size[, mean[, swapRB[, crop[, ddepth]]]]]])
# scalefactor:缩放因子
# size:resize的尺寸
# mean: 通道均值
# swapRB: 交换RB通道
# crop: 截取区域
# ddepth: 输出图像数据格式,cv2.CV_8U or cv2.CV_32F
图像处理的顺序,首先resize,然后swapRB,接着减mean,再乘以scalefactor。
最后将返回一个blob,即四维数组,shape为[1, channels, size[0], size[1]]。
其实这个blob也是一个numpy.ndarray,因此可以被numpy完美替代(numpy能做的,它不一定能搞定)
C++中的图像预处理C++中没有numpy一样方便的数组切片和访问的方法,访问opencv Mat格式的方法依赖指针,因此涉及到维度的预处理操作就显得格外复杂。
例如YOLOX和YOLOv5模型中的Foucs模块,由于许多AI芯片并不支持这个focus的切片操作(硬件op算子稍落后于学术),因此必须在图像预处理时提前实现这个操作。
在python中,这个focus模块非常简单:
# focus模块
img_focus = np.concatnate([image[::2, ::2, :], image[1::2, ::2, :], image[::2, 1::2, :], image[1::2, 1::2, :]], axis=-1)
但是C++ opencv Mat的实现就非常麻烦,需要借助到指针进行Mat数据重排
Mat数据重排focus操作实际上是把一张图片拆成四张等大小图(等同于downsample),然后把这四张小图从上到下拼接起来,因此同时涉及到尺寸(downsample)、通道(拼接)两个方面的预处理。
那么数据重排的思路就是,将原Mat宽高中的downsample出的数据,贴到新Mat的通道上,C++实现如下
Mat focusImage(vector srcChannels) {
Mat focusMat(YOLOArgs::modelHeight/2, YOLOArgs::modelWidth/2, CV_32FC(12), 0.0);
int startPt[4][2] = {{0, 0}, {0, 1}, {1, 0}, {1, 1}}, startX, startY;
for (size_t i = 0; i
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?