您当前的位置: 首页 >  opencv

RuiH.AI

暂无认证

  • 0浏览

    0关注

    274博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

OpenCV 学习总结.番外篇:图像预处理接口 cv::dnn::blobFromImage()

RuiH.AI 发布时间:2021-08-09 19:01:24 ,浏览量:0

目录
  • 前言
  • 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)
强大的Numpy

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             
关注
打赏
1658651101
查看更多评论
0.0420s