获取输入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 floatfloat 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,即变为72x128Matrix 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);// 放大回 72x128matrix.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] -> Srctrans.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);
