MLCore 端上推理全流程

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)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容