上周阿里最新开源的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 解码出来的数据稍有区别。
另外,附上FreeImage格式转openCV格式的代码:
https://stackoverflow.com/questions/31814451/convert-freeimage-fibitmap-format-to-opencv-mat