MLCore 端上推理全流程
1. 问题描述
目前,我们在做一项端上的增强项目,需要用到相关平台的推理框架。现在有一个这样的问题:存在已有的onnx模型,可否转化成mlmodel模型,以供iOS端上使用。听上去挺简单的,安排☑️。
对于端上推理框架的套路如下几点:
- 如何转化模型
- 如何转化模型输入
- 如何推理
- 如何转化模型输出
在实际生产中,我们最难做的就是如何转化模型输入,所以,只要能做到模型输入的转化,我们即可完成任务。当然,后面还有需要需要注意的。
2. 如何转化模型
我们参考coremltools官网相关api。安装
sudo pip3 install coremltools==5.0b2
相关转化代码
import coremltools
import coremltools.proto.FeatureTypes_pb2 as ft
## onnx模型转换为mlmodel模型
mlmodel = coremltools.converters.onnx.convert(model=‘xxx’)
## 设置描述,作者,版本号
mlmodel.description = ‘xxx’
mlmodel.author = ‘xxx’
mlmodel.version = ‘xxx’
## 保存模型
mlmodel.save(‘xxx')
## 可处理输入和输出,将输出和输出转化为Image输入
spec = mlmodel.get_spec()
input_ = spec.description.input[0]
## ft.ImageFeatureType.BGR or ft.ImageFeatureType.RGB
input.type.imageType.colorSpace = ft.ImageFeatureType.GRAYSCALE
input.type.imageType.width = xxx
input.type.imageType.height = xxx
## 将新生成的配置保存到模型中
coremltools.utils.save_spec(spec, mlmodel)
以上代码的注释也解释清楚了。这里需要明确的问题是:
- 转换的模型路径。
- 转换的模型输入,类似指定为iOS的CVPixelBufferRef,否则,默认为MLMultiArray。(这里特指输出为图像的情况下。)
- 转换的模型输出,类似指定为iOS的CVPixelBufferRef,否则, 默认为MLMultiArray。(这里特指输出为图像的情况下。)
- 是否需要额外的scale或者bias运算。
- 保存模型路径。
整理完毕,你即可拿到你想要的mlmodel模型啦。
3. 如何验证模型转化是否成功
我们有以下的思路:
- 先转化成可推理的model
- 将一张图片作为输入,输入到这个model里面
- 获取这个推理的输出,查看结果是否存在问题
验证代码如下:
from PIL import Image
import coremltools
## xxx equals to the path of image
image_ = Image.open(‘xxx’)
## look mlmodel input size to width/height
image_ = image_.resize((width, height))
## if the format of image is GRAYSCALE, you must trans the format of image to ‘L’
image_ = image_("L")
## load model
mlmodel = coremltools.models.model.MLModel(path)
out_ = mlmodel.predict({‘xxx’: image_})
## if out_ is a image, you can look image by showing it
out_[‘xxx'].show()
需要依赖PIL,安装命令如下:
sudo pip3 install pillow
4. 在iOS端上使用该模型
不得不说一句,mlmodel是我遇到现在最简单的一套推理框架。所有的类和转化都围绕'简单'和‘易用’来设计的一样,不得不敬佩。(只能说iOS的系统设计的真的是太好了。)
4.1 如何转化输入
在mlmodel模型里面,只支持三种格式输入,即Gray,BGR和RGB。我们先使用Gray来说明。
在iOS中,所有的图像数据都可以使用CVPixelBufferRef的形式存在,特比的,这个Buffer甚至可以绑定纹理,跟随者Shader语句的执行,也一起改变过来,那么,如何将Gray的数据存放到Buffer里面然后进行推理呢?
第一步,我们需要创建CVPixelBufferRef,并指定Format为kCVPixelFormatType_OneComponent8。
const void *keys[] = {
kCVPixelBufferOpenGLESCompatibilityKey,
kCVPixelBufferIOSurfacePropertiesKey,
};
const void *values[] = {
(__bridge void *)[NSNumber numberWithBool:YES],
(__bridge void*)[NSDictionary dictionary]
};
CFDictionaryRef optionsDictionary = CFDictionaryCreate(kCFAllocatorDefault, keys, values, 2, nil, nil);
// create CVPixelBufferRef, must set kCVPixelBufferOpenGLESCompatibilityKey and kCVPixelBufferIOSurfacePropertiesKey so the _pixelBuffer can create texture from image
OSType ret = CVPixelBufferCreate(kCFAllocatorDefault,
width,
height,
kCVPixelFormatType_OneComponent8,
optionsDictionary,
&_pixelBuffer);
第二步,复制数据源头到Buffer里面。
// must lock so the address can be vetted by users
CVPixelBufferLockBaseAddress(_pixelBuffer, 0);
size_t pixelBufferWidth = CVPixelBufferGetWidthOfPlane(_pixelBuffer, 0);
size_t pixelBufferHeight = CVPixelBufferGetHeightOfPlane(_pixelBuffer, 0);
size_t pixelBufferBytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(_pixelBuffer, 0);
Uint8_t* address = CVPixelBufferGetBaseAddressOfPlane(_pixelBuffer, 0);
for (int i = 0; i < pixelBufferBytesPerRow; ++i) {
for (int j = 0; j < pixelBufferHeight; ++j) {
address[i * pixelBufferHeight + j] = xxx;
}
}
CVPixelBufferUnlockBaseAddress(_pixelBuffer, 0);
到此,我们接收到转化数据了。直接送给推理框架即可。
4.2 输出的数据
如果我们没有指定输出为Image,那么输出的数据即为MLMultiArray,这个是一个类似数组的东西,我们可以通过MLMultiArray[i]的形式来访问第(i/height, i%height)行。
但是,我们这里主要说下CVPixelBufferRef如何转化的。
第一种方式: 就是把不具备iOSSurface的CVPixelBufferRef转化为具备iOSSurface的数据,道理和4.1讲的一样,先生成,再copy,最后输出纹理。以下代码讲下如何生成Texture
CVMetalTextureRef texture;
size_t width = CVPixelBufferGetWidthOfPlane(_pixelBuffer);
size_t height = CVPixelBufferGetHeightOfPlane(_pixelBuffer);
CVReturn result = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, _videoTextureCache, _pixelBuffer, nil, MTLPixelFormatR8Unorm, width, height, 0, &texture);
id<MTLTexture> renderTexture = CVMetalTextureGetTexture(texture);
CVBufferRelease(texture);
第二种方式,是否可以让输出的CVPixelBufferRef自身就携带iOSSurface的属性呢?目前还在查询解决方案,后续存在会及时更新。
5. 总结
到此,完整的一个问题"存在已有的onnx模型,可否转化成mlmodel模型,以供iOS端上使用。"就得到了处理。
=========
附增:
关于如何设置scale和bias,以下为具体代码:
scale = 1.0 / (1.0 / 255.0)
preprocessing_args = dict(is_bgr=False, red_bias=0.0, green_bias=0.0, blue_bia=0.0, image_scale=scale)
depreprocessing_args = dict(is_bgr=False, red_bias=0.0, green_bias=0.0, blue_bia=0.0, image_scale=255.0)
model = ct.converters.onnx.convert(model=xxx,
minimum_ios_deployment_target='11.2',
preprocessing_args=preprocessing_args,
deprocessing_args=depreprocessing_args)