获取输入tensor
/**
* @brief get input tensor for given name.
* @param session given session.
* @param name given name. if NULL, return first input.
* @return tensor if found, NULL otherwise.
*/
Tensor* getSessionInput(const Session* session, const char* name);
/**
* @brief get all input tensors.
* @param session given session.
* @return all output tensors mapped with name.
*/
const std::map<std::string, Tensor*>& getSessionInputAll(const Session* session) const;
Interpreter
上提供了两个用于获取输入Tensor
的方法:getSessionInput
用于获取单个输入tensor,getSessionInputAll
用于获取输入tensor映射。
在只有一个输入tensor时,可以在调用
getSessionInput
时传入NULL以获取tensor。
拷贝数据
NCHW示例,适用 ONNX / Caffe / Torchscripts 转换而来的模型:
auto inputTensor = interpreter->getSessionInput(session, NULL);
auto nchwTensor = new Tensor(inputTensor, Tensor::CAFFE);
// nchwTensor-host<float>()[x] = ...
inputTensor->copyFromHostTensor(nchwTensor);
delete nchwTensor;
NHWC示例,适用于由 Tensorflow / Tflite 转换而来的模型:
auto inputTensor = interpreter->getSessionInput(session, NULL);
auto nhwcTensor = new Tensor(inputTensor, Tensor::TENSORFLOW);
// nhwcTensor-host<float>()[x] = ...
inputTensor->copyFromHostTensor(nhwcTensor);
delete nhwcTensor;
通过这类拷贝数据的方式,用户只需要关注自己创建的tensor的数据布局,
copyFromHostTensor
会负责处理数据布局上的转换(如需)和后端间的数据拷贝(如需)。
直接填充数据
auto inputTensor = interpreter->getSessionInput(session, NULL);
inputTensor->host<float>()[0] = 1.f;
Tensor
上最简洁的输入方式是直接利用host
填充数据,但这种使用方式仅限于CPU后端,其他后端需要通过deviceid
来输入。另一方面,用户需要自行处理NC4HW4
、NHWC
数据格式上的差异。
对于非CPU后端,或不熟悉数据布局的用户,宜使用拷贝数据接口。
图像处理
MNN中提供了CV模块,可以帮助用户简化图像的处理,还可以免于引入opencv、libyuv等图片处理库。
1、支持目标Tensor为float或 uint8_t 的数据格式 2、支持目标Tensor为NC4HW4或NHWC的维度格式 3、CV模块支持直接输入Device Tensor,也即由Session中获取的Tensor。
图像处理配置
struct Config
{
Filter filterType = NEAREST;
ImageFormat sourceFormat = RGBA;
ImageFormat destFormat = RGBA;
//Only valid if the dest type is float
float mean[4] = {0.0f,0.0f,0.0f, 0.0f};
float normal[4] = {1.0f, 1.0f, 1.0f, 1.0f};
};
CV::ImageProcess::Config
中
- 通过
sourceFormat
和destFormat
指定输入和输出的格式,当前支持RGBA
、RGB
、BGR
、GRAY
、BGRA
、YUV_NV21、YUV_NV12
- 通过
filterType
指定插值的类型,当前支持NEAREST
、BILINEAR
和BICUBIC
三种插值方式 - 通过
mean
和normal
指定均值归一化,但数据类型不是浮点类型时,设置会被忽略
图像变换矩阵
CV::Matrix
移植自Android 系统使用的Skia引擎,用法可参考Skia的Matrix:https://skia.org/user/api/SkMatrix_Reference。
需要注意的是,ImageProcess中设置的Matrix是从目标图像到源图像的变换矩阵。使用时,可以按源图像到目标图像的变换设定,最后取逆。例如:
// 源图像:1280x720
// 目标图像:逆时针旋转90度再缩小到原来的1/10,即变为72x128
Matrix matrix;
// 重设为单位矩阵
matrix.setIdentity();
// 缩小,变换到 [0,1] 区间:
matrix.postScale(1.0f / 1280, 1.0f / 720);
// 以中心点[0.5, 0.5]旋转90度
matrix.postRotate(90, 0.5f, 0.5f);
// 放大回 72x128
matrix.postScale(72.0f, 128.0f);
// 转变为 目标图像 -> 源图的变换矩阵
matrix.invert(&matrix);
图像处理实例
MNN中使用CV::ImageProcess
进行图像处理。ImageProcess
内部包含一系列缓存,为了避免内存的重复申请释放,建议将其作缓存,仅创建一次。我们使用ImageProcess
的convert
填充tensor数据。
/*
* source: 源图像地址
* iw: 源图像宽
* ih:源图像高,
* stride:源图像对齐后的一行byte数(若不需要对齐,设成 0(相当于 iw*bpp))
* dest: 目标 tensor,可以为 uint8 或 float 类型
*/
ErrorCode convert(const uint8_t* source, int iw, int ih, int stride, Tensor* dest);
完整示例
auto input = net->getSessionInput(session, NULL);
auto output = net->getSessionOutput(session, NULL);
auto dims = input->shape();
int bpp = dims[1];
int size_h = dims[2];
int size_w = dims[3];
auto inputPatch = argv[2];
FREE_IMAGE_FORMAT f = FreeImage_GetFileType(inputPatch);
FIBITMAP* bitmap = FreeImage_Load(f, inputPatch);
auto newBitmap = FreeImage_ConvertTo32Bits(bitmap);
auto width = FreeImage_GetWidth(newBitmap);
auto height = FreeImage_GetHeight(newBitmap);
FreeImage_Unload(bitmap);
Matrix trans;
//Dst -> [0, 1]
trans.postScale(1.0/size_w, 1.0/size_h);
//Flip Y (因为 FreeImage 解出来的图像排列是Y方向相反的)
trans.postScale(1.0,-1.0, 0.0, 0.5);
//[0, 1] -> Src
trans.postScale(width, height);
ImageProcess::Config config;
config.filterType = NEAREST;
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 = BGR;
std::shared_ptr<ImageProcess> pretreat(ImageProcess::create(config));
pretreat->setMatrix(trans);
pretreat->convert((uint8_t*)FreeImage_GetScanLine(newBitmap, 0), width, height, 0, input);
net->runSession(session);
可变维度
/**
* @brief resize given tensor.
* @param tensor given tensor.
* @param dims new dims. at most 6 dims.
*/
void resizeTensor(Tensor* tensor, const std::vector<int>& dims);
/**
* @brief resize given tensor by nchw.
* @param batch / N.
* @param channel / C.
* @param height / H.
* @param width / W
*/
void resizeTensor(Tensor* tensor, int batch, int channel, int height, int width);
/**
* @brief call this function to get tensors ready. output tensor buffer (host or deviceId) should be retrieved
* after resize of any input tensor.
* @param session given session.
*/
void resizeSession(Session* session);
在输入Tensor维度不确定或需要修改时,需要调用resizeTensor
来更新维度信息。这种情况一般发生在未设置输入维度和输入维度信息可变的情况。更新完所有Tensor的维度信息之后,需要再调用resizeSession
来进行预推理,进行内存分配及复用。示例如下:
auto inputTensor = interpreter->getSessionInput(session, NULL);
interpreter->resizeTensor(inputTensor, {newBatch, newChannel, newHeight, newWidth});
interpreter->resizeSession(session);
inputTensor->copyFromHostTensor(imageTensor);
interpreter->runSession(session);