MLCore 端上推理全流程
1. 問題描述
目前,我們?cè)谧鲆豁?xiàng)端上的增強(qiáng)項(xiàng)目,需要用到相關(guān)平臺(tái)的推理框架?,F(xiàn)在有一個(gè)這樣的問題:存在已有的onnx模型,可否轉(zhuǎn)化成mlmodel模型,以供iOS端上使用。聽上去挺簡(jiǎn)單的,安排??。
對(duì)于端上推理框架的套路如下幾點(diǎn):
- 如何轉(zhuǎn)化模型
- 如何轉(zhuǎn)化模型輸入
- 如何推理
- 如何轉(zhuǎn)化模型輸出
在實(shí)際生產(chǎn)中,我們最難做的就是如何轉(zhuǎn)化模型輸入,所以,只要能做到模型輸入的轉(zhuǎn)化,我們即可完成任務(wù)。當(dāng)然,后面還有需要需要注意的。
2. 如何轉(zhuǎn)化模型
我們參考coremltools官網(wǎng)相關(guān)api。安裝
sudo pip3 install coremltools==5.0b2
相關(guān)轉(zhuǎn)化代碼
import coremltools
import coremltools.proto.FeatureTypes_pb2 as ft
## onnx模型轉(zhuǎn)換為mlmodel模型
mlmodel = coremltools.converters.onnx.convert(model=‘xxx’)
## 設(shè)置描述,作者,版本號(hào)
mlmodel.description = ‘xxx’
mlmodel.author = ‘xxx’
mlmodel.version = ‘xxx’
## 保存模型
mlmodel.save(‘xxx')
## 可處理輸入和輸出,將輸出和輸出轉(zhuǎn)化為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)
以上代碼的注釋也解釋清楚了。這里需要明確的問題是:
- 轉(zhuǎn)換的模型路徑。
- 轉(zhuǎn)換的模型輸入,類似指定為iOS的CVPixelBufferRef,否則,默認(rèn)為MLMultiArray。(這里特指輸出為圖像的情況下。)
- 轉(zhuǎn)換的模型輸出,類似指定為iOS的CVPixelBufferRef,否則, 默認(rèn)為MLMultiArray。(這里特指輸出為圖像的情況下。)
- 是否需要額外的scale或者bias運(yùn)算。
- 保存模型路徑。
整理完畢,你即可拿到你想要的mlmodel模型啦。
3. 如何驗(yàn)證模型轉(zhuǎn)化是否成功
我們有以下的思路:
- 先轉(zhuǎn)化成可推理的model
- 將一張圖片作為輸入,輸入到這個(gè)model里面
- 獲取這個(gè)推理的輸出,查看結(jié)果是否存在問題
驗(yàn)證代碼如下:
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是我遇到現(xiàn)在最簡(jiǎn)單的一套推理框架。所有的類和轉(zhuǎn)化都圍繞'簡(jiǎn)單'和‘易用’來設(shè)計(jì)的一樣,不得不敬佩。(只能說iOS的系統(tǒng)設(shè)計(jì)的真的是太好了。)
4.1 如何轉(zhuǎn)化輸入
在mlmodel模型里面,只支持三種格式輸入,即Gray,BGR和RGB。我們先使用Gray來說明。
在iOS中,所有的圖像數(shù)據(jù)都可以使用CVPixelBufferRef的形式存在,特比的,這個(gè)Buffer甚至可以綁定紋理,跟隨者Shader語句的執(zhí)行,也一起改變過來,那么,如何將Gray的數(shù)據(jù)存放到Buffer里面然后進(jìn)行推理呢?
第一步,我們需要?jiǎng)?chuàng)建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);
第二步,復(fù)制數(shù)據(jù)源頭到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);
到此,我們接收到轉(zhuǎn)化數(shù)據(jù)了。直接送給推理框架即可。
4.2 輸出的數(shù)據(jù)
如果我們沒有指定輸出為Image,那么輸出的數(shù)據(jù)即為MLMultiArray,這個(gè)是一個(gè)類似數(shù)組的東西,我們可以通過MLMultiArray[i]的形式來訪問第(i/height, i%height)行。
但是,我們這里主要說下CVPixelBufferRef如何轉(zhuǎn)化的。
第一種方式: 就是把不具備iOSSurface的CVPixelBufferRef轉(zhuǎn)化為具備iOSSurface的數(shù)據(jù),道理和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的屬性呢?目前還在查詢解決方案,后續(xù)存在會(huì)及時(shí)更新。
5. 總結(jié)
到此,完整的一個(gè)問題"存在已有的onnx模型,可否轉(zhuǎn)化成mlmodel模型,以供iOS端上使用。"就得到了處理。
=========
附增:
關(guān)于如何設(shè)置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)