女人自慰AV免费观看内涵网,日韩国产剧情在线观看网址,神马电影网特片网,最新一级电影欧美,在线观看亚洲欧美日韩,黄色视频在线播放免费观看,ABO涨奶期羡澄,第一导航fulione,美女主播操b

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

如何用Bazel構(gòu)建C++項(xiàng)目

Tensorflowers ? 來(lái)源:TensorFlow ? 作者:TensorFlow ? 2020-11-18 17:23 ? 次閱讀

前言

眾所周知,C/C++ 語(yǔ)言具備很強(qiáng)可移植性,作為高級(jí)的底層語(yǔ)言能兼容各式各樣的系統(tǒng)環(huán)境或應(yīng)用。因此很多企業(yè)更偏向于將算法用 C/C++ 實(shí)現(xiàn),從而減少不同業(yè)務(wù)平臺(tái)下的算法維護(hù)成本。所以,我們對(duì) TensorFlow Lite 的 C++ 接口有很強(qiáng)的現(xiàn)實(shí)需求。然而,關(guān)于 TensorFlow Lite C++ 接口的詳細(xì)教程和案例不太常見(jiàn),但它實(shí)際上并不復(fù)雜。因而,我參考 MediaPipe 整理一個(gè)案例項(xiàng)目分享到社區(qū),希望能幫助有需要的同學(xué)。

編譯構(gòu)建

我們創(chuàng)建一個(gè) C++ 項(xiàng)目后,一般會(huì)先考慮編譯環(huán)境的搭建問(wèn)題。

Bazel 是一個(gè)類似 Make、Maven 和 Gradle 的構(gòu)建與測(cè)試工具。它的高級(jí)構(gòu)建語(yǔ)言具有很好的可讀性。Bazel 支持多語(yǔ)言跨平臺(tái)的構(gòu)建項(xiàng)目。它還支持大量用戶協(xié)作開(kāi)發(fā)涵蓋多個(gè)代碼倉(cāng)庫(kù)的大型代碼庫(kù)。它具有構(gòu)建語(yǔ)言可讀性強(qiáng)、構(gòu)建高速可靠、跨平臺(tái)兼容、大規(guī)模構(gòu)建和擴(kuò)展構(gòu)建等優(yōu)點(diǎn)。因此,我們這個(gè)項(xiàng)目采用 Bazel 作為構(gòu)建工具,方便 TensorFlow Lite 與 OpenCV 等第三方庫(kù)的代碼版本管理。首先,我們一起了解一下如何用 Bazel 構(gòu)建 C++ 項(xiàng)目。

設(shè)置構(gòu)建環(huán)境

在構(gòu)建項(xiàng)目之前,我們需要設(shè)置項(xiàng)目的構(gòu)建環(huán)境 (Workspace)。構(gòu)建環(huán)境表示一個(gè)目錄包含所有我們的代碼源文件與 Bazel 的構(gòu)建結(jié)果輸出文件。其中有些文件會(huì)引導(dǎo) Bazel 如何進(jìn)行項(xiàng)目編譯:

WORKSPACE,它一般被放在項(xiàng)目的根目錄底下,負(fù)責(zé)導(dǎo)入第三方庫(kù)的代碼控制與管理。

BUILD,通常一個(gè)項(xiàng)目有很多個(gè),它們負(fù)責(zé)告訴 Bazel 如何編譯項(xiàng)目的各個(gè)不同模塊。通常,構(gòu)建環(huán)境下的每個(gè)模塊包 (Package) 目錄下都會(huì)有一個(gè) BUILD。

下面是我們案例項(xiàng)目的目錄結(jié)構(gòu):

image-classifier ├── LICENSE ├── README.md ├── WORKSPACE ├── image_classifier │ ├── BUILD │ ├── apps │ │ ├── desktop │ │ │ ├── BUILD │ │ │ └── main.cc │ └── cc │ ├── BUILD │ ├── classifier_float_mobilenet.cc │ ├── classifier_float_mobilenet.h │ ├── image_classifier.cc │ ├── image_classifier.h │ ├── image_classify_service.cc │ ├── image_classify_service.h │ └── utils.h └── third_party ├── BUILD ├── com_google_absl_f863b622fe13612433fdf43f76547d5edda0c93001.diff ├── opencv_linux.BUILD ├── opencv_macos.BUILD └── org_tensorflow_compatibility_fixes.diff

我們將項(xiàng)目劃分成兩個(gè)模塊,第三方庫(kù) third_party 和圖像分類模塊 image_classifier,其中 image_classifier 又分成 apps 應(yīng)用模塊和 cc 代碼實(shí)現(xiàn)模塊。每模塊的具體設(shè)計(jì)后文詳細(xì)介紹,我們先看看構(gòu)建環(huán)境的細(xì)節(jié)配置。

workspace(name = "image_classifier") load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") skylib_version = "0.9.0" http_archive( name = "bazel_skylib", type = "tar.gz", url = "https://github.com/bazelbuild/bazel-skylib/releases/download/{}/bazel_skylib-{}.tar.gz".format (skylib_version, skylib_version), sha256 = "1dde365491125a3db70731e25658dfdd3bc5dbdfd11b840b3e987ecf043c7ca0", ) load("@bazel_skylib//lib:versions.bzl", "versions") versions.check(minimum_bazel_version = "2.0.0") # ABSL cpp library lts_2020_02_25 http_archive( name = "com_google_absl", urls = [ "https://github.com/abseil/abseil-cpp/archive/20200225.tar.gz", ], # Remove after https://github.com/abseil/abseil-cpp/issues/326 is solved. patches = [ "@//third_party:com_google_absl_f863b622fe13612433fdf43f76547d5edda0c93001.diff" ], patch_args = [ "-p1", ], strip_prefix = "abseil-cpp-20200225", sha256 = "728a813291bdec2aa46eab8356ace9f75ac2ed9dfe2df5ab603c4e6c09f1c353" ) new_local_repository( name = "linux_opencv", path = "/usr", build_file="@//third_party:opencv_linux.BUILD" ) new_local_repository( name = "macos_opencv", build_file = "@//third_party:opencv_macos.BUILD", path = "/usr", ) # Needed by TensorFlow http_archive( name = "io_bazel_rules_closure", sha256 = "e0a111000aeed2051f29fcc7a3f83be3ad8c6c93c186e64beb1ad313f0c7f9f9", strip_prefix = "rules_closure-cf1e44edb908e9616030cc83d085989b8e6cd6df", urls = [ "http://mirror.tensorflow.org/github.com/bazelbuild/rules_closure/archive/cf1e44edb908e9616030cc83d085989b8e6cd6df.tar.gz", "https://github.com/bazelbuild/rules_closure/archive/cf1e44edb908e9616030cc83d085989b8e6cd6df.tar.gz", # 2019-04-04 ], ) #Tensorflow repo should always go after the other external dependencies. # 2020-08-30 _TENSORFLOW_GIT_COMMIT = "57b009e31e59bd1a7ae85ef8c0232ed86c9b71db" _TENSORFLOW_SHA256= "de7f5f06204e057383028c7e53f3b352cdf85b3a40981b1a770c9a415a792c0e" http_archive( name = "org_tensorflow", urls = [ "https://github.com/tensorflow/tensorflow/archive/%s.tar.gz" % _TENSORFLOW_GIT_COMMIT, ], patches = [ "@//third_party:org_tensorflow_compatibility_fixes.diff", ], patch_args = [ "-p1", ], strip_prefix = "tensorflow-%s" % _TENSORFLOW_GIT_COMMIT, sha256 = _TENSORFLOW_SHA256, ) load("@org_tensorflow//tensorflow:workspace.bzl", "tf_workspace") tf_workspace(tf_repo_name = "org_tensorflow")

上面是 image-classifier 的 WORKSPACE 配置,他導(dǎo)入 versions 對(duì)象檢查 Bazel 版本,加載 http_archive 函數(shù)管理 org_tensorflow、opencv、abseil 等類似的第三方庫(kù)。其中 abseil 庫(kù)很值得推薦,它是集成不少 C++14/17 新特性的工具庫(kù),類似于 Boost 卻體積特別輕巧方便。我們經(jīng)常會(huì)在 Google 開(kāi)源代碼中看見(jiàn)它們的身影,如 absl::make_unique,absl::StrJoin 等等,因此我把這個(gè)項(xiàng)目引入到代碼里方便一些字符串和智能指針的處理。

接著,我們看看不同目錄下的 BUILD 文件是如何配置的。

image_classifier/apps/desktop/BUILD

cc_binary( name = "image_classifier.exe", srcs = ["main.cc"], deps = [ "@//third_party:opencv", "http://image_classifier/cc:image_classifier", ], )

我們看到 image_classifier/apps/desktop/BUILD 正在描述一個(gè)可執(zhí)行文件的編譯依賴關(guān)系。其中,cc_binary 就表示編譯的輸出結(jié)果是二進(jìn)制可執(zhí)行文件,name 表示這個(gè)輸出文件的名字,srcs 是可執(zhí)行文件編譯時(shí)依賴的一些源文件,deps 是指編譯鏈接過(guò)程中依賴的其他模塊目錄。我們很容易觀察出,這個(gè)目錄的 BUILD 其實(shí)描述的是一個(gè)桌面應(yīng)用的主函數(shù)編譯過(guò)程,畢竟 srcs 依賴了一個(gè) apps/desktop/main.cc(碼農(nóng)們的命名習(xí)慣)。另外,還可以看到 deps 的依賴表里面的 "@//third_party:opencv" 比 "http://image_classifier/cc:image_classifier" 多了一個(gè) @ 符號(hào),它表示外部第三方庫(kù)的依賴。而 "http://image_classifier/cc:image_classifier" 表示我們從目錄 image_classifier/cc 引用 image_classifier 模塊。

image_classifier 模塊的 BUILD 描述如下:

image_classifier/cc/BUILD

cc_library( name = "image_classifier", srcs = glob(["*.cc"]), hdrs = glob(["*.h"]), visibility = ["http://visibility:public"], deps = [ "@com_google_absl//absl/memory", "@org_tensorflow//tensorflow/lite:builtin_op_data", "@org_tensorflow//tensorflow/lite/kernels:builtin_ops", "@org_tensorflow//tensorflow/lite:framework", "@//third_party:opencv", ], )

image_classifier/cc/BUILD 正在描述一個(gè) C++ 庫(kù)文件的編譯依賴關(guān)系。很容易注意到,這個(gè) BUILD 文件與前面都寫(xiě)區(qū)別。首先,我用 cc_library 函數(shù)告訴 Bazel 這個(gè)目錄的編譯輸出的結(jié)果是一個(gè)庫(kù)文件。其次,我用 glob 函數(shù)實(shí)現(xiàn)對(duì) image_classifier/cc/目錄下所有 .cc 和 .h 文件進(jìn)行依賴,hdrs 表示需要依賴包含的頭文件。然后,我通過(guò) visiblity 屬性對(duì)外部模塊公開(kāi) API 的細(xì)節(jié),方便 apps/desktop 等其他模塊的調(diào)用,具體細(xì)節(jié)可以參考 Bazel 的編譯規(guī)則說(shuō)明。最后,不難發(fā)現(xiàn)我的 deps 引用了 TensorFlow Lite 的關(guān)鍵模塊,因?yàn)?TensorFlow Lite 在我的案例項(xiàng)目中屬于外部第三方庫(kù),所以關(guān)鍵模塊的路徑前面有一個(gè) @ 符號(hào)。

Bazel 的編譯規(guī)則說(shuō)明

https://docs.bazel.build/versions/3.6.0/be/c-cpp.html

構(gòu)建環(huán)境搭建完成后,我們就可以運(yùn)行 Bazel 進(jìn)行項(xiàng)目的編譯構(gòu)建。

$ bazel build -c opt --experimental_repo_remote_exec //image_classifier/apps/desktop:image_classifier.exe

其中 -c opt表示 C 的編譯優(yōu)化,--experimental_repo_remote_exec僅為處理第三方庫(kù)的編譯問(wèn)題。最后,以 MacOS 為例,我們可以執(zhí)行這個(gè)二進(jìn)制可執(zhí)行文件。

$ ./bazel-bin/image_classifier/apps/desktop/image_classifier.exe

如果有同學(xué)在構(gòu)建過(guò)程中遇到問(wèn)題,請(qǐng)到 Issue 反饋你構(gòu)建的情況。


代碼結(jié)構(gòu)

我們結(jié)合目錄結(jié)構(gòu)和構(gòu)建文件配置,分析源碼可以得到下面的代碼結(jié)構(gòu)示意圖。

這是案例項(xiàng)目的代碼結(jié)構(gòu)設(shè)計(jì),在企業(yè)開(kāi)發(fā)中我們總是希望自己的算法代碼無(wú)須修改即可跨平臺(tái)復(fù)用,減少維護(hù)成本,但算法的實(shí)現(xiàn)卻總會(huì)不斷地被優(yōu)化。因此,我設(shè)計(jì)一個(gè) ImageClassifyService 作為業(yè)務(wù)算法代理提供服務(wù),不同平臺(tái)的 APP 開(kāi)發(fā)者根據(jù)需求平臺(tái)的情況在接口適配層調(diào)用這個(gè)代理為應(yīng)用提供接口。比如,Android 平臺(tái)的開(kāi)發(fā)者可以在 JNI 層調(diào)用 C++ 類 ImageClassifyService 的 RecognizeImage 接口封裝圖像分類識(shí)別的功能給 Java 層使用。一般這種情況,我會(huì)把 ImageClassifyService 設(shè)計(jì)成單例方便管理,畢竟移動(dòng)端資源緊張,不會(huì)同時(shí)運(yùn)行兩個(gè)分類服務(wù)。哪怕出現(xiàn)墨菲定律的情況,我們也應(yīng)該修改 ImageClassifyService,讓他提供兩個(gè)適合同時(shí)分類服務(wù)的接口。但是,這對(duì)于一個(gè)入門(mén)教程案例來(lái)說(shuō)過(guò)于復(fù)雜,所以我沒(méi)在案例代碼做類似的實(shí)現(xiàn)。

然后,ImageClassifyService 有一個(gè) ImageClassifier 抽象成員負(fù)責(zé)完成具體的分類任務(wù)。前面說(shuō)過(guò),具體的圖像分類實(shí)現(xiàn)會(huì)經(jīng)常被修改優(yōu)化,甚至?xí)?A/B 測(cè)試。因此,我沿用 TFLite Android 官方案例的設(shè)計(jì)模式,讓 ImageClassifier 組合不同的實(shí)現(xiàn),如 ClassifierFloatMobileNet,ClassifierEfficientNet 等。

TFLite Android 官方案例
https://github.com/tensorflow/examples/tree/master/lite/examples/image_classification/android


模型分析

因?yàn)槲覀冋陂_(kāi)發(fā)的 C++ 項(xiàng)目與深度學(xué)習(xí)有關(guān),所以我們很難避免模型在不同推理框架的轉(zhuǎn)換問(wèn)題。然而,本教程主要目的是 TensorFlow Lite C++ 部署流程說(shuō)明,因此我不在本文詳細(xì)描述模型的轉(zhuǎn)換方法,有需要的讀者可以參考官方文檔。我的案例模型是從 TFLite Android 官方示例程序拷貝的,部署前我習(xí)慣于對(duì)準(zhǔn)備使用的模型進(jìn)行觀察分析,以便關(guān)注到一些模型的輸入預(yù)處理和輸出后處理的注意事項(xiàng)。TFLite 的模型分析工具有 visualize 和 minimal,其中 visualize 是官方主推的分析工具,能圖示模型的推理流程。而 minimal 作為 TFLite 的Examples也能顯示 TFLite 模型的詳情信息,但是無(wú)模型圖示。

官方文檔
https://tensorflow.google.cn/lite/convert?hl=zh_cn

TFLite Android 官方示例程序
https://github.com/tensorflow/examples/tree/master/lite/examples/image_classification/android

Examples
https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/examples/minimal

Interpreter has 103 tensors and 31 nodes Inputs: 87 Outputs: 86 Tensor 0 MobilenetV1/Conv2d_0/weights kTfLiteFloat32 kTfLiteMmapRo 3456 bytes ( 0.0 MB) 32 3 3 3 Tensor 1 MobilenetV1/Conv2d_10_depthwise/depthwise_weights kTfLiteFloat32 kTfLiteMmapRo 18432 bytes ( 0.0 MB) 1 3 3 512 Tensor 2 MobilenetV1/Conv2d_10_pointwise/weights kTfLiteFloat32 kTfLiteMmapRo 1048576 bytes ( 1.0 MB) 512 1 1 512 ... Tensor 84 MobilenetV1/MobilenetV1/Conv2d_9_pointwise/Conv2D_bias kTfLiteFloat32 kTfLiteMmapRo 2048 bytes ( 0.0 MB) 512 Tensor 85 MobilenetV1/MobilenetV1/Conv2d_9_pointwise/Relu6 kTfLiteFloat32 kTfLiteArenaRw 401408 bytes ( 0.4 MB) 1 14 14 512 Tensor 86 MobilenetV1/Predictions/Reshape_1 kTfLiteFloat32 kTfLiteArenaRw 4004 bytes ( 0.0 MB) 1 1001 Tensor 87 input kTfLiteFloat32 kTfLiteArenaRw 602112 bytes ( 0.6 MB) 1 224 224 3 Tensor 88 (null) kTfLiteFloat32 kTfLiteArenaRwPersistent 3456 bytes ( 0.0 MB) 27 32 Tensor 89 (null) kTfLiteFloat32 kTfLiteArenaRwPersistent 8192 bytes ( 0.0 MB) 32 64 ... Tensor 102 (null) kTfLiteFloat32 kTfLiteArenaRwPersistent 4100096 bytes ( 3.9 MB) 1024 1001

上面的 MobileNetV1 模型,我們可以看到它有 102 個(gè)張量 (tensor),其中 15 個(gè)中間特征映射 (Feature Map) 張量沒(méi)有節(jié)點(diǎn)名字 (Node Name) 而不可見(jiàn)。我們分析模型的輸入輸出張量,Tensor 87 和 Tensor 86。這個(gè) MobileNetV1 的張量索引 (Tensor Index) 比較獨(dú)特,它的輸入張量索引為 87 與輸出索引的 86 鄰近,張量索引其實(shí)只是 TensorFlow Lite 對(duì)模型參數(shù)和中間特征映射的內(nèi)存進(jìn)行編號(hào)標(biāo)記,方便在 AllocateTensors 安排模型執(zhí)行順序時(shí)找到對(duì)應(yīng)的張量。另外,我們還能看到這兩個(gè)輸入輸出內(nèi)存的 Memory 類型都是 kTfLiteArenaRw,它表示內(nèi)存可讀寫(xiě)。有的模型參數(shù)的 Memory 類型是 kTfLiteMmapRo 是只讀內(nèi)存,一般我們代碼無(wú)法訪問(wèn)。還有的是 kTfLiteDynamic 類型,它會(huì)根據(jù)輸入情況動(dòng)態(tài)調(diào)整內(nèi)存大小,我只在 ResizeOp 遇到過(guò)這種類型。有時(shí) ResizeOp 的輸出張量大小 (Size) 是固定 kTfLiteMmapRo 的,動(dòng)態(tài)修改 ResizeOp 的輸入大小會(huì)導(dǎo)致 AllocateTensors 分配內(nèi)存不對(duì)的情況。關(guān)于這個(gè) ResizeOp Dynamic Shape 的問(wèn)題,我們將在后文詳細(xì)討論。現(xiàn)在,我們基本清楚 MobileNetV1 的 tflite 模型細(xì)節(jié),下面我們看看如何利用這些模型細(xì)節(jié)進(jìn)行推理實(shí)現(xiàn)圖像分類算法。


算法實(shí)現(xiàn)

在了解模型細(xì)節(jié)信息后,我們就可以按照下面的基本流程實(shí)現(xiàn)算法的部署。

// minimal.cc 官方案例實(shí)現(xiàn) // Load model std::unique_ptr model = tflite::BuildFromFile(filename); TFLITE_MINIMAL_CHECK(model != nullptr); // Build the interpreter with the InterpreterBuilder. // Note: all Interpreters should be built with the InterpreterBuilder, // which allocates memory for the Intrepter and does various set up // tasks so that the Interpreter can read the provided model. tflite::BuiltinOpResolver resolver; tflite::InterpreterBuilder builder(*model, resolver); std::unique_ptr interpreter; builder(&interpreter); TFLITE_MINIMAL_CHECK(interpreter != nullptr); // Allocate tensor buffers. TFLITE_MINIMAL_CHECK(interpreter->AllocateTensors() == kTfLiteOk); printf("=== Pre-invoke Interpreter State === "); // This line can print the details of tflite model from interpreter. tflite::PrintInterpreterState(interpreter.get()); // Fill input buffers // TODO(user): Insert code to fill input tensors. // Note: The buffer of the input tensor with index `i` of type T can // be accessed with `T* input = interpreter->typed_input_tensor(i);` // Run inference TFLITE_MINIMAL_CHECK(interpreter->Invoke() == kTfLiteOk); printf(" === Post-invoke Interpreter State === "); tflite::PrintInterpreterState(interpreter.get()); // Read output buffers // TODO(user): Insert getting data out code. // Note: The buffer of the output tensor with index `i` of type T can // be accessed with `T* output = interpreter->typed_output_tensor(i);`

大致分為 5 個(gè)步驟: 1. 從文件加載模型并建立模型解釋器 (Interpreter),BuiltinOpResolver 表示用 TFLite 內(nèi)部算子 (Ops) 解析模型,如果有自定義算子 (Custom Ops) 的情況,我們會(huì)在這個(gè)階段進(jìn)行算子注冊(cè)。自定義算子是屬于高階技能,這份入門(mén)級(jí)教程不做過(guò)多詳細(xì)介紹,有興趣的同學(xué)可以參考官方文檔。將 BuiltinOpResolver 和 FlatBufferModel 組合構(gòu)造出一個(gè)解釋器建造者 (Interpreter Builder),利用這個(gè)建造者初始化模型解釋器。這時(shí),解釋器里面已經(jīng)擁有模型的具體細(xì)節(jié)信息,并知道該用何種實(shí)現(xiàn)運(yùn)行這個(gè)模型。

官方文檔
https://tensorflow.google.cn/lite/guide/ops_custom?hl=zh_cn

2. 分配張量推理運(yùn)行內(nèi)存 (Allocate tensor buffers),因?yàn)榇蠖嗲闆r下深度學(xué)習(xí)模型的運(yùn)行內(nèi)存消耗都比較固定,所以提前計(jì)算分配有利于減少動(dòng)態(tài)內(nèi)存分配的資源消耗。然而,有時(shí)候我們會(huì)遇到類似人臉識(shí)別、文本識(shí)別等后級(jí)網(wǎng)絡(luò)模型的輸入圖像的數(shù)量并不確定的情況,畢竟檢測(cè)器能從圖像定位多少個(gè)目標(biāo)與場(chǎng)景有關(guān),場(chǎng)景包含目標(biāo)的個(gè)數(shù)是隨機(jī)的。這時(shí),我們可以利用 ResizeInputTensor 設(shè)置輸入 batch size。代碼片段如下:

// kInputIndex 是輸入張量索引,kNum 是輸入圖片張數(shù),即 batch size。 interpreter_->ResizeInputTensor(kInputIndex, {kNum, kInputHeight, kInputWidth, kInputChannels}); // 按照新的輸入張量的大小重新分配內(nèi)存。 interpreter_->AllocateTensors(); // 循環(huán)填充輸入張量的內(nèi)存,其中 kInputIndex 是輸入張量索引。 float* input_buffer = interpreter_-> typed_tensor(kInputIndex); const int kInputBytes = sizeof(float)*kInputWidth*kInputHeight*kInputChannels; cv::Size input_buffer_size(kInputWidth, kInputHeight); int buffer_index = 0; for(auto& image : images) { cv::Mat input_image; // 輸入預(yù)處理操作。 cv::resize(image, input_image, input_buffer_size); cv::cvtColor(input_image, input_image, cv::COLOR_BGR2GRAY); input_image.convertTo(input_image, CV_32F, 2.f/255, -0.5); // 填充輸入張量的內(nèi)存,batch size > 1 時(shí),注意 // input_buffer 的數(shù)據(jù)類型需要強(qiáng)制轉(zhuǎn)換。因?yàn)?buffer_index 是按 byte 為單位進(jìn)行地址偏移的。 memcpy((uchar*)(input_buffer)+ buffer_index, input_image.data, kInputBytes); buffer_index += kInputBytes; }

3. 將輸入數(shù)據(jù)填入輸入張量。一般我們會(huì)在這步做一些數(shù)據(jù)預(yù)處理操作比如白化、數(shù)據(jù)類型轉(zhuǎn)換等。如果是多圖片同時(shí)預(yù)測(cè)的情況,可以參考上面的代碼片段。

4. 運(yùn)行深度學(xué)習(xí)網(wǎng)絡(luò)模型推斷過(guò)程,這時(shí)候只用簡(jiǎn)單調(diào)用 Interpreter::Invoke 接口,檢查是否有返回錯(cuò)誤即可。

5. 如果模型推斷過(guò)程沒(méi)有發(fā)生錯(cuò)誤,那么網(wǎng)絡(luò)模型的推斷結(jié)果就會(huì)被放到 Interpreter 的輸出張量上。我們只需要讀取并按照業(yè)務(wù)邏輯進(jìn)行后處理解析,就能得到期望的業(yè)務(wù)結(jié)果。

值得注意的是,ResizeInputTensor,AllocateTensors,Invoke都是有返回值可以檢查的,我建議盡量不要直接用默認(rèn)的 assert 斷言處理。因?yàn)槲以?MacOS 用 Bazel 構(gòu)建時(shí),發(fā)現(xiàn) assert(interpreter_->AllocateTensors())竟然沒(méi)有執(zhí)行,這可能是 Bazel 構(gòu)建程序時(shí)會(huì)默認(rèn)屏蔽 assert 斷言,具體情況請(qǐng)感興趣的同學(xué)自行研究,所以我教程案例中寫(xiě)了一個(gè) CHECK 宏函數(shù)處理這個(gè)問(wèn)題。

另外,我們還需要注意 Interpreter::typed_tensor 與 Interpreter::typed_input_tensor的細(xì)微差別,他們的輸入?yún)?shù)雖然都是索引 (Index),但是 typed_tensor 的參數(shù)是張量索引,而 typed_input_tensor是輸入張量的序號(hào),比如 MobileNetV1模型的輸入張量索引是 87 但序號(hào)是 0,假如我不小心錯(cuò)寫(xiě)成 float* input_buffer = interpreter_ -> typed_input_tensor( interpreter_ -> inputs()[0] ),此時(shí)我們往 input_buffer 進(jìn)行內(nèi)存拷貝,就會(huì)出現(xiàn)內(nèi)存寫(xiě)入錯(cuò)誤的問(wèn)題。因?yàn)椋覀冋趯?xiě)的這個(gè)內(nèi)存屬性可能已經(jīng)不是可讀寫(xiě)的了。同理,typed_output_tensor 也需要關(guān)注類似的問(wèn)題。

最后,我們討論一下關(guān)于 ResizeOp 的問(wèn)題。前文提到, AllocateTensor 有時(shí)無(wú)法正確推理 ResizeOp 的輸出結(jié)果大小 (Size),從而導(dǎo)致內(nèi)存錯(cuò)誤的情況。發(fā)生該問(wèn)題的主要原因是,模型轉(zhuǎn)換器 (TFLite Converter) 一般會(huì)認(rèn)為 ResizeOp 的輸出大小 (Size) 是常量,并在轉(zhuǎn)換過(guò)程對(duì)其常量化,導(dǎo)致縮放算子輸出大小固定 (Fixed ResizeOp Output Size) 的情況。對(duì)于這個(gè)問(wèn)題,我們討論下面兩種解決思路。

思路一

首先,我們考慮修改模型轉(zhuǎn)換部分的 Python 代碼,用 tf.shape 獲取輸入張量的大小,從而動(dòng)態(tài)控制 ResizeOp 的縮放比例,實(shí)現(xiàn)對(duì)其輸出結(jié)果大小的修改。代碼片段大致如下:

import tensorflow.compat.v1 as tf import numpy as np tf.disable_v2_behavior() input_t = tf.placeholder(dtype=tf.float32, shape=[1, None, None, 3]) shape = tf.shape(input_t) h = shape[1] // 2 w = shape[2] // 2 out_t = tf.compat.v1.image.resize_bilinear(input_t, [h, w]) with tf.Session() as sess: converter = tf.lite.TFLiteConverter(sess.graph_def, [input_t], [out_t]) tfl_model = converter.convert() interpreter = tf.lite.Interpreter(model_content=tfl_model) input_index = (interpreter.get_input_details()[0]['index']) interpreter.resize_tensor_input(input_index, tensor_size=[1, 300, 300, 3]) try: interpreter.allocate_tensors() except ValueError: assert False random_input = np.array(np.random.random([1, 300, 300, 3]), dtype=np.float32) interpreter.set_tensor(input_index, random_input) interpreter.invoke() output_index = (interpreter.get_output_details()[0]['index']) result = interpreter.get_tensor(output_index) print(result.shape)

從上面的代碼片段,我們只要修改 resize_tensor_input 的 tensor_size,result.shape 就是它的 0.5 倍。

思路二

另外,我們還可以考慮在模型轉(zhuǎn)換時(shí)配置適合的輸入大小,然后在預(yù)處理做一些 Crop-Padding-Resize 的操作,最后對(duì)模型的輸出結(jié)果按照 Reisze 的比例進(jìn)行解析得到我們期望的結(jié)果。具體的操作流程與原理如下圖所示:

深度學(xué)習(xí)模型在訓(xùn)練階段其實(shí)也是通過(guò)預(yù)處理固定輸入大小的,因此推理階段使用原有的輸入大小其實(shí)并不會(huì)引入太多的誤差。

TensorFlow Lite 現(xiàn)在也開(kāi)始逐漸支持 Dynamic Shape ,同時(shí)也有一些修改 tflite::Interpreter 的模型信息的 Hack 技巧,這里我不一一介紹了,有興趣可以關(guān)注我知乎的其他文章。

知乎
https://www.zhihu.com/people/hu-xu-hua-4


效果展現(xiàn)

可以看到我的教程案例效果與官方教程的效果基本一致。


未來(lái)的工作

有些同學(xué)可能發(fā)現(xiàn)我并沒(méi)有把代碼類圖結(jié)構(gòu)中的 Raspberry Pi 和 Android 部分進(jìn)行實(shí)現(xiàn)。實(shí)際上,我只完成了 TFLite C++ API 應(yīng)用的主干流程。因此,我仍需努力完成 Raspberry Pi 的編譯支持與 Android 的應(yīng)用層案例實(shí)現(xiàn)。而且,Tensorflow Lite 團(tuán)隊(duì)最近又推出了能減少開(kāi)發(fā)工作量的新特性——Tensorflow Lite Library TaskAPI,現(xiàn)在這一新特性在 tflite-support 的項(xiàng)目里面與 Android TFLite metadata 代碼生成器放在一起。

tflite-support
https://github.com/tensorflow/tflite-support

所以,我希望未來(lái)能在這個(gè)教程案例項(xiàng)目集成類似 TFLite-support 的新特性幫助大家節(jié)省工作量。另外,這個(gè)案例代碼只有 TFLite 算子的標(biāo)準(zhǔn) C++ 實(shí)現(xiàn),并未涉及 GPUSIMD 等指令集優(yōu)化的 TFLite Delegate API 應(yīng)用。盡管這些算子指令優(yōu)化受限于移動(dòng)設(shè)備的訪存帶寬影響,未必達(dá)到顯著優(yōu)化效果,但我相信隨著硬件設(shè)備與軟件框架的更新迭代,這些問(wèn)題終將被一一解決。

這個(gè)教程案例
http://github.com/SunAriesCN/image-classifier

TFLite Delegate API
https://tensorflow.google.cn/lite/performance/delegates

這個(gè)開(kāi)源的教程案例項(xiàng)目現(xiàn)在可能并不完美,畢竟我的個(gè)人的時(shí)間和能力都相當(dāng)有限。然而,我期望這個(gè)項(xiàng)目最終能幫助各位開(kāi)發(fā)者在人工智能時(shí)代展現(xiàn)出自身優(yōu)勢(shì),應(yīng)用開(kāi)發(fā)者做有趣好玩的智能應(yīng)用,架構(gòu)性能優(yōu)化師能讓用戶體驗(yàn)流暢的智能交互,算法研發(fā)人員能帶來(lái)各種奇妙的黑科技等等。

責(zé)任編輯:lq

聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • C++
    C++
    +關(guān)注

    關(guān)注

    22

    文章

    2117

    瀏覽量

    74777
  • 代碼
    +關(guān)注

    關(guān)注

    30

    文章

    4886

    瀏覽量

    70251
  • tensorflow
    +關(guān)注

    關(guān)注

    13

    文章

    330

    瀏覽量

    61030

原文標(biāo)題:社區(qū)分享 | TensorFlow Lite C++ API 開(kāi)源案例教程

文章出處:【微信號(hào):tensorflowers,微信公眾號(hào):Tensorflowers】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦
    熱點(diǎn)推薦

    C++學(xué)到什么程度可以找工作?

    C++學(xué)到什么程度可以找工作?要使用C++找到工作,特別是作為軟件開(kāi)發(fā)人員或相關(guān)職位,通常需要掌握以下幾個(gè)方面: 1. **語(yǔ)言基礎(chǔ)**:你需要對(duì)C++的核心概念有扎實(shí)的理解,包括但不限于指針、內(nèi)存
    發(fā)表于 03-13 10:19

    Spire.XLS for C++組件說(shuō)明

    Spire.XLS for C++ 是一款專業(yè)的 C++ Excel 組件,可以用在各種 C++ 框架和應(yīng)用程序中。Spire.XLS for C++ 提供了一個(gè)對(duì)象模型 Excel
    的頭像 發(fā)表于 01-14 09:40 ?514次閱讀
    Spire.XLS for <b class='flag-5'>C++</b>組件說(shuō)明

    AKI跨語(yǔ)言調(diào)用庫(kù)神助攻C/C++代碼遷移至HarmonyOS NEXT

    ,真正做到所“鍵”即所得。 這一創(chuàng)新框架的出現(xiàn),正是為了解決開(kāi)發(fā)者在遷移C/C++項(xiàng)目到HarmonyOS NEXT時(shí)面臨的核心痛點(diǎn)。傳統(tǒng)的NAPI接口調(diào)用復(fù)雜,學(xué)習(xí)成本高,開(kāi)發(fā)者需要耗費(fèi)大量精力進(jìn)行適配
    發(fā)表于 01-02 17:08

    同樣是函數(shù),在CC++中有什么區(qū)別

    ,即使沒(méi)有數(shù)據(jù)返回,也得寫(xiě) void。 第二個(gè)函數(shù)名。 C語(yǔ)言的函數(shù)名絕對(duì)不能重名,除了用上 weak 這樣的黑科技。同一個(gè)項(xiàng)目中,函數(shù)重名就會(huì)提示重復(fù)定義。 C++因?yàn)楹瘮?shù)重載的存在,函數(shù)名可以相同,只要參數(shù)有區(qū)別就行。這兩個(gè)
    的頭像 發(fā)表于 11-29 10:25 ?788次閱讀

    C7000 C/C++優(yōu)化指南用戶手冊(cè)

    電子發(fā)燒友網(wǎng)站提供《C7000 C/C++優(yōu)化指南用戶手冊(cè).pdf》資料免費(fèi)下載
    發(fā)表于 11-09 15:00 ?0次下載
    <b class='flag-5'>C</b>7000 <b class='flag-5'>C</b>/<b class='flag-5'>C++</b>優(yōu)化指南用戶手冊(cè)

    Klocwork 2024.3新特性速覽

    Klocwork 2024.3 為?C/C++?分析引擎和構(gòu)建上傳流程引入了新功能和性能改進(jìn)。此版本還附帶了增強(qiáng)的安全性和用戶體驗(yàn)改進(jìn),包括用于?SAML/OIDC?身份驗(yàn)證的?IDE?插件中更好
    的頭像 發(fā)表于 11-07 09:49 ?948次閱讀
    Klocwork 2024.3新特性速覽

    C語(yǔ)言和C++中結(jié)構(gòu)體的區(qū)別

    同樣是結(jié)構(gòu)體,看看在C語(yǔ)言和C++中有什么區(qū)別?
    的頭像 發(fā)表于 10-30 15:11 ?653次閱讀

    C7000優(yōu)化C/C++編譯器

    電子發(fā)燒友網(wǎng)站提供《C7000優(yōu)化C/C++編譯器.pdf》資料免費(fèi)下載
    發(fā)表于 10-30 09:45 ?0次下載
    <b class='flag-5'>C</b>7000優(yōu)化<b class='flag-5'>C</b>/<b class='flag-5'>C++</b>編譯器

    用GNU構(gòu)建裸機(jī)系統(tǒng)

    構(gòu)建和安裝GNU工具鏈,但是很難找到一個(gè)將GNU C/C++工具鏈用于裸機(jī)ARM系統(tǒng)的綜合例子,該系統(tǒng)將具有現(xiàn)實(shí)項(xiàng)目中所需的所有基本特性。即使你找到了這樣一個(gè)例子,你也很可能不知道為
    發(fā)表于 10-16 17:34 ?0次下載

    使用OpenVINO GenAI API在C++構(gòu)建AI應(yīng)用程序

    許多桌面應(yīng)用程序是使用 C++ 開(kāi)發(fā)的,而將生成式AI(GenAI)功能集成到這些應(yīng)用程序中可能會(huì)很具有挑戰(zhàn)性,尤其是因?yàn)槭褂孟?Hugging Face 這樣的 Python 庫(kù)的復(fù)雜性。C++
    的頭像 發(fā)表于 10-12 09:36 ?992次閱讀
    使用OpenVINO GenAI API在<b class='flag-5'>C++</b>中<b class='flag-5'>構(gòu)建</b>AI應(yīng)用程序

    OpenVINO2024 C++推理使用技巧

    很多人都使用OpenVINO新版的C++ 或者Python的SDK,都覺(jué)得非常好用,OpenVINO2022之后的版本C++ SDK做了大量的優(yōu)化與整理,已經(jīng)是非常貼近開(kāi)發(fā)的使用習(xí)慣與推理方式。與OpenCV的Mat對(duì)象對(duì)接方式更是幾乎無(wú)縫對(duì)接,非常的方便好用。
    的頭像 發(fā)表于 07-26 09:20 ?1435次閱讀

    c++編譯后鏈接失敗的原因?如何解決?

    /c++項(xiàng)目,將剛才新建的項(xiàng)目轉(zhuǎn)換為c++項(xiàng)目。 完成后點(diǎn)擊編譯,此時(shí)也是正常的。 新建一個(gè)cpp文件,將原
    發(fā)表于 07-25 08:13

    ModusToolbox 3.2在c代碼中包含c++代碼的正確步驟是什么?

    使用 ModusToolbox 3.2 我有一個(gè)用純 C 語(yǔ)言編寫(xiě)的 XMC4700 項(xiàng)目。 我正在嘗試添加一些 C++ 函數(shù),并將其合并到我的原始代碼中。 我可以構(gòu)建獨(dú)立的 .cpp
    發(fā)表于 07-23 08:21

    C++語(yǔ)言基礎(chǔ)知識(shí)

    電子發(fā)燒友網(wǎng)站提供《C++語(yǔ)言基礎(chǔ)知識(shí).pdf》資料免費(fèi)下載
    發(fā)表于 07-19 10:58 ?8次下載

    C++中實(shí)現(xiàn)類似instanceof的方法

    C++有多態(tài)與繼承,但是很多人開(kāi)始學(xué)習(xí)C++,有時(shí)候會(huì)面臨一個(gè)常見(jiàn)問(wèn)題,就是如何向下轉(zhuǎn)型,特別是不知道具體類型的時(shí)候,這個(gè)時(shí)候就希望C++ 可以向Java或者Python中有instanceof這個(gè)
    的頭像 發(fā)表于 07-18 10:16 ?857次閱讀
    <b class='flag-5'>C++</b>中實(shí)現(xiàn)類似instanceof的方法