阿里MNN移动端部署框架,将FreeImage更换为opencv的实现

一、动机

上周阿里最新开源的MNN移动端部署框架,为了尽可能压缩依赖库的大小,读图及图像处理操作全部基于FreeImage实现。但是,

(1)由于我们项目中大量依赖opencv,再添加FreeImage库显得有些冗余。

(2)目前来看,opencv的应用范围远远大于FreeImage,文档也更为丰富。

(3)我们项目在安卓端需要大量使用交叉编译方式,JNI部分需要引入FreeImage链接库。而安卓端的FreeImage链接库编译起来并不是很容易。

因此,将FreeImage 部分的操作用opencv重写,可大大减轻我们的工作量。

 

二、代码更改

1. 先放上原版的预测部分的代码,并做适当的注释。以pictureRecognition.cpp为例:

//FreeImage读图


FREE_IMAGE_FORMAT f = FreeImage_GetFileType(inputPatch); // 文件头分析文件类型 f值为2
FIBITMAP* bitmap    = FreeImage_Load(f, inputPatch);  // 读图
MNN_ASSERT(NULL != bitmap);

// bitmap转为32bit 
auto newBitmap = FreeImage_ConvertTo32Bits(bitmap); 
FreeImage_Unload(bitmap); // 清理掉原变量 释放内存

// 图像原始输入大小
auto width  = FreeImage_GetWidth(newBitmap);
auto height = FreeImage_GetHeight(newBitmap);
MNN_PRINT("origin size: %d, %d\n", width, height);

//按行顺序读取元素值,转为uint8_t格式
uint8_t*FreeImage = (uint8_t*)FreeImage_GetScanLine(newBitmap, 0);
FreeImage_Unload(bitmap);
// 定义一个转换矩阵
Matrix trans;
// Dst -> [0, 1]
trans.postScale(1.0 / size_w, 1.0 / size_h);
// 沿Y轴翻转
trans.postScale(1.0, -1.0, 0.0, 0.5);
//[0, 1] -> Src
trans.postScale(width, height);

ImageProcess::Config config;  // 图像处理的配置变量
config.filterType = BILINEAR;
float mean[3]     = {103.94f, 116.78f, 123.68f};
float normals[3]  = {0.017f, 0.017f, 0.017f};
::memcpy(config.mean, mean, sizeof(mean));  // 设置均值
::memcpy(config.normal, normals, sizeof(normals));  // 设置方差
config.sourceFormat = RGBA;  // 源数据格式
config.destFormat   = GRAY;  // 目标数据格式 

std::shared_ptr pretreat(ImageProcess::create(config));
pretreat->setMatrix(trans); // 将转换矩阵设置到图像处理操作中
// 将源数据转换到给定的tensor变量中(input)
// convert参数为 source, iw, ih, stride, dest
// ImageProcess的convert步骤会把处理好的数据直接塞入inputTensor中

pretreat->convert(FreeImage, width, height, 0, input);
FreeImage_Save(FIF_PNG, newBitmap, argv[3], PNG_DEFAULT);  // 写图
FreeImage_Unload(newBitmap);  // 清理掉变量 释放内存

梳理一下流程:

(1)读图,数值格式为uchar。

(2)转为32位bitmap。

(3)按行读取32位bitmap。转化为uint8_t 格式。

(4)ImgProcess类的图像config: 将图像沿y轴翻转180度。

(5)ImgProcess类的图像config: 设置均值、方差、颜色空间、resize尺寸。

(6)向网络中喂数据,得到识别结果。

 

2. 知道了实现流程,我们只需用opencv自己实现一遍即可。主要有以下几点要注意:

(1)32位bitmap的原理 (此处32位bitmap的原理刚开始不懂,以为只是将数值变为float32,在这儿卡了好久。因此有必要阐述一下)

32位bitmap指的是每个像素值处将rgba四通道连接,即每个像素由四个U8值组成,共32bit.

采用32位bitmap主要是便于simd指令并行优化,一条CPU指令可并行处理四个数据。缺省的24位bitmap是BGR三个数据,CPU没有24位的数据类型,只能扩充一个8位数据,哪怕无意义,也能通过简单的simd指令提速3倍。

用opencv重写,参考如下:

https://stackoverflow.com/questions/10265125/opencv-2-3-convert-mat-to-rgba-pixel-array

// 按BGR三通道读取图像
Mat colorImg = imread(inputPatch,1);
//total函数返回矩阵的像素总个数,将其扩展为四通道
uchar* colorImgData = new uchar[colorImg.total()*4];
// 新建同等大小的Mat,通道为8UC4
Mat MatTemp(colorImg.size(), CV_8UC4, colorImgData);
//为新Mat赋值,颜色空间转换为RGBA,与32bitmap一致
cvtColor(colorImg, MatTemp, CV_BGR2RGBA, 4);
int height_opencv = MatTemp.rows;
int width_opencv = MatTemp.cols;
MNN_PRINT("opencv_origin size: %d, %d\n", width_opencv, height_opencv);
//.data操作级逐行读取元素。与FreeImage_ScanLine 作用一致
uint8_t *pImg = (uint8_t*)MatTemp.data;
    

(2)图像config稍作更改

此处要理解转换矩阵操作的意义。

最终效果是沿 Y 轴中心翻转,将输入 width * height 的图像 缩放到 模型指定的输入大小 size_w * size_h。Matrix trans 表示 dst 坐标点到 src 坐标点的映射。

之所以要 沿 Y 轴中心翻转 ,是因为 FreeImage 的坐标系Y 是反的。而opencv的坐标系是正的,所以我们要把该行语句屏蔽掉。

// 定义一个转换矩阵
Matrix trans;
// Dst -> [0, 1]
trans.postScale(1.0 / size_w, 1.0 / size_h);
// Flip Y
//  trans.postScale(1.0, -1.0, 0.0, 0.5);
//[0, 1] -> Src
trans.postScale(width, height);

ImageProcess::Config config;  // 图像处理的配置变量
config.filterType = BILINEAR;
float mean[3]     = {103.94f, 116.78f, 123.68f};
float normals[3]  = {0.017f, 0.017f, 0.017f};
::memcpy(config.mean, mean, sizeof(mean));  // 设置均值
::memcpy(config.normal, normals, sizeof(normals));  // 设置方差
config.sourceFormat = RGBA;  // 源数据格式
config.destFormat   = GRAY;  // 目标数据格式 

std::shared_ptr pretreat(ImageProcess::create(config));
pretreat->setMatrix(trans); // 将转换矩阵设置到图像处理操作中
// 将源数据转换到给定的tensor变量中(input)
// convert参数为 source, iw, ih, stride, dest
// ImageProcess的convert步骤会把处理好的数据直接塞入inputTensor中


pretreat->convert(pImg, width, height, 0, input);

三、结果

至此已经完全修改完毕。可以愉快地跟FreeImage说拜拜啦。show一下自己做分类的结果。虽然有一丢丢偏差,但基本没有影响。猜想可能是 freeimage 解码和 opencv 解码出来的数据稍有区别。

阿里MNN移动端部署框架,将FreeImage更换为opencv的实现_第1张图片          阿里MNN移动端部署框架,将FreeImage更换为opencv的实现_第2张图片

 

另外,附上FreeImage格式转openCV格式的代码:

https://stackoverflow.com/questions/31814451/convert-freeimage-fibitmap-format-to-opencv-mat

你可能感兴趣的:(模型前端化)