天天看點

tensorflow模型部署系列————嵌入式(c/c++ android)部署(附源碼)

摘要

本文為系列部落格tensorflow模型部署系列的一部分,用于實作通用模型的部署。本文主要實作用tflite接口調用tensorflow模型進行推理。實作了tensorflow在邊緣計算及一些低成本方案、物聯網或工業級應用中使用的輕量級模型部署方案,并提供相關示例源代碼。相關源碼見連結

引言

本文為系列部落格tensorflow模型部署系列的一部分,用于tflite實作通用模型的部署。本文主要使用pb格式的模型檔案,其它格式的模型檔案請先進行格式轉換,參考tensorflow模型部署系列————預訓練模型導出。從模型檔案中擷取輸入輸出op名請參考部落格tensorflow模型部署系列————單機python部署

主題

上一篇博文tensorflow模型部署系列————單機JAVA部署就如何使用JAVA語言加載模型檔案并利用模型檔案進行推理進行講解,博文中也開放了使用

JAVA

進行模型推理的代碼,目前多數智能終端都已經支援JAVA,但在一些低成本方案、物聯網或工業級應用中更多的是使用嵌入式linux甚至是單片機,主要使用的編譯語言是C。博文tensorflow模型部署系列————單機C++部署介紹了使用C/C++進行模型部署的方法,但實際應用時因為tensorflow庫檔案太大(已超過100M),限制了tensorflow在嵌入式上的應用。目前tensorflow提供了專門用于嵌入式的tflite架構(僅700K左右)。tflite的目标是嵌入式部署,嵌入式部署需要針對處理器進行特殊優化。google官方除了android庫以外,并沒有已編譯好的檔案可供下載下傳。本文先對tflite及運作作簡單介紹,然後分别對tflite在python c/c++ java上的部署進行解說。

tflite介紹

應用場景

tflite是google為深度學習在嵌入式物聯網應用而推出的輕量級架構。它提供了python、java和C++接口,同時可以将浮點運算轉換為整數運算,進而在特定的硬體平台上加快推理速度。tflite使用的模型不是pb檔案,而是更小的基于FlatBuffers的模型檔案。

tflite主要有兩個元件:推理元件和模型轉換元件。推理元件用于運作模型;模型轉換元件用于将需要的模型轉換為tflite模型檔案

模型檔案轉換

模型檔案轉換時需要指定輸入輸出張量名,從模型檔案中擷取輸入輸出op名請參考部落格tensorflow模型部署系列————單機python部署

從session轉換

import tensorflow as tf

img = tf.placeholder(name="img", dtype=tf.float32, shape=(1, 64, 64, 3))
var = tf.get_variable("weights", dtype=tf.float32, shape=(1, 64, 64, 3))
val = img + var
out = tf.identity(val, name="out")

with tf.Session() as sess:
  sess.run(tf.global_variables_initializer())
  converter = tf.lite.TFLiteConverter.from_session(sess, [img], [out])
  tflite_model = converter.convert()
  open("converted_model.tflite", "wb").write(tflite_model)
           

從pb檔案轉換

import tensorflow as tf

graph_def_file = "/path/to/Downloads/mobilenet_v1_1.0_224/frozen_graph.pb"
input_arrays = ["input"]
output_arrays = ["MobilenetV1/Predictions/Softmax"]

converter = tf.lite.TFLiteConverter.from_frozen_graph(
  graph_def_file, input_arrays, output_arrays)
tflite_model = converter.convert()
open("converted_model.tflite", "wb").write(tflite_model)
           

python部署

python語言目前在嵌入式系統上應用的還不多。這裡把python也拿出來講有兩個原因:

  1. 可以快速的在上位機上評估tflite模型檔案的準确率等性能
  2. 後面講解java和c++部署代碼時,參考python代碼,以便于讀者更好地了解tflite的運作原理

安裝

python版不需要額外安裝,安裝完畢tensorflow

pip install tensorflow

後就可以使用了

tf.lite

API講解

tflite的python接口詳見官方文檔。這裡針對常用接口做一個說明。

  • 加載模型檔案
    interpreter = tf.lite.Interpreter(model_path=tflite_file)
               
  • 建立tensors
    interpreter.allocate_tensors()
               
  • 擷取輸入輸出OP
    input_details = interpreter.get_input_details()
    output_details = interpreter.get_output_details()
               
  • 張量填充
    interpreter.set_tensor(input_details[0]['index'], d)
               
  • 運作推理
    interpreter.invoke()
               
  • 擷取張量值
    interpreter.get_tensor(output_details[0]['index'])
               

部署代碼

模型封裝庫

模型封裝類主要包括兩個方法:初始化和推理。

初始化隻用于處理與

invoke

無關的所有操作,以減少推理時的操作,保證模型推理高效運作。初始化主要進行的操作包括:模型檔案加載、擷取輸入輸出張量。

推理主要進行的操作有:輸入張量填充、

interpreter.invoke

、和輸出張量擷取。

模型封裝類示例代碼

經過模型封裝類封裝以後,示例代碼就很簡單了。隻用準備資料,然後推理就行了。

C++部署

API講解

這裡針對tflite的c++常用接口做一個說明,并跟将C接口跟本文中的python部署代碼進行對應,以便于讀者更好地了解接口

  • 加載模型檔案,對應python代碼

    interpreter = tf.lite.Interpreter(model_path=tflite_file)

    std::unique_ptr<Interpreter> interpreter;
    std::unique_ptr<tflite::FlatBufferModel> model = tflite::FlatBufferModel::BuildFromFile(model_file);
    InterpreterBuilder builder(*model, resolver);
    builder(&interpreter);
               
  • 建立tensors,對應python代碼

    interpreter.allocate_tensors()

    interpreter->AllocateTensors()
               
  • 擷取輸入輸出OP,對應python代碼

    interpreter.get_input_details()

    interpreter.get_output_details()

    in_index = interpreter->inputs()[0];
    out_index = interpreter->outputs()[0];
               
  • 張量填充,對應python代碼

    interpreter.set_tensor(input_details[0]['index'], d)

    memcpy(interpreter->typed_tensor<float>(in_index), &input_vals[0], INPUT_SIZE*sizeof(input_vals[0]));
               
  • 運作推理,對應python代碼

    interpreter.invoke()

    interpreter->Invoke()
               
  • 擷取張量值,對應python代碼

    interpreter.get_tensor(output_details[0]['index'])

    float* output = interpreter->typed_tensor<float>(out_index);
    memcpy(&output_vals[0], output, OUTPUT_SIZE*sizeof(output_vals[0]));
               

部署代碼

模型封裝庫

模型封裝類主要包括兩個方法:初始化和推理。

初始化隻用于處理與

invoke

無關的所有操作,以減少推理時的操作,保證模型推理高效運作。初始化主要進行的操作包括:模型檔案加載、擷取輸入輸出張量。

推理主要進行的操作有:輸入張量填充、

interpreter.invoke

、和輸出張量擷取。

模型封裝類示例代碼

經過模型封裝類封裝以後,示例代碼就很簡單了。隻用準備資料,然後推理就行了。

編譯

tflite的編譯需要使用tensorflow源代碼,下面給出簡單的編譯步驟。後續在我的部落格中會放出各平台下已編譯好的tflite庫檔案,請持續關注

  1. 下載下傳tensorflow源碼
  2. 拷貝代碼檔案夾

    C++

    tensorflow/lite/tools/make/

  3. tensorflow/lite/tools/make/Makefile

    檔案中增加如下代碼
    HELLOW_TFLIET := hellow_tf
    HELLOW_TFLIET_BINARY := $(BINDIR)$(HELLOW_TFLIET)
    
    HELLOW_TFLIET_SRCS := \
    tensorflow/lite/tools/make/C++/model.cc \
    tensorflow/lite/tools/make/C++/example.cc
    
    INCLUDES += \
    -Itensorflow/lite/tools/make/C++/
    	
    ALL_SRCS += \
      $(HELLOW_TFLIET_SRCS)
    
    CORE_CC_EXCLUDE_SRCS += \
    $(wildcard tensorflow/lite/tools/make/C++/model.cc) \
    $(wildcard tensorflow/lite/tools/make/C++/example.cc)
    
    HELLOW_TFLIET_OBJS := $(addprefix $(OBJDIR), \
    $(patsubst %.cc,%.o,$(patsubst %.c,%.o,$(HELLOW_TFLIET_SRCS))))
    
    $(HELLOW_TFLIET): $(LIB_PATH) $(HELLOW_TFLIET_OBJS) 
    	@mkdir -p $(BINDIR)
    	$(CXX) $(CXXFLAGS) $(INCLUDES) \
    	-o $(HELLOW_TFLIET_BINARY) $(HELLOW_TFLIET_OBJS) \
    	$(LIBFLAGS) $(LIB_PATH) $(LDFLAGS) $(LIBS)
               
  4. 執行編譯指令

    make hellow_tf -j8 -f tensorflow/lite/tools/make/Makefile

JAVA(android)部署

tflite官方代碼提供了直接在android代碼上使用tflite庫的方法,本文針對android中使用庫為例進行說明。

android環境配置

要在android代碼中使用tflite庫,隻需在工程配置中加入

implementation 'org.tensorflow:tensorflow-lite:0.0.0-nightly'

即可,另外需要注意的是android打包時會對assets下不認識的檔案壓縮,這會導緻模型檔案讀取失敗,解決方法是在配置檔案添加

android {
    ...
    aaptOptions {
        noCompress "tflite"
    }
    ...
}
           

部署代碼

模型封裝庫

模型封裝類主要包括兩個方法:初始化、關閉和推理。

初始化隻用于處理與

invoke

無關的所有操作,以減少推理時的操作,保證模型推理高效運作。初始化主要進行的操作包括:模型檔案加載。

推理主要進行的操作有:

interpreter.invoke

模型封裝類示例代碼

經過模型封裝類封裝以後,示例代碼就很簡單了。隻用準備資料,然後推理就行了。

示例代碼

  • tflite的python模型部署代碼
    • tflite的python模型封裝庫代碼
    • tflite的python模型封裝庫示例代碼
  • tflite的c/c++模型部署代碼
    • tflite的c++模型封裝庫代碼
    • tflite的c++模型封裝庫示例代碼
    • 編譯步驟
  • tflite的c/c++模型部署代碼——去除對tensorflow源碼的依賴
    • tflite的c++模型封裝庫代碼
    • [tflite的c++模型封裝庫示例代碼](https://github.com/gdyshi/model_deployment/blob/master/tflite/lib_app /example.cc)
  • tflite的java(android)模型部署代碼
    • tflite的java模型封裝庫代碼
    • tflite的java模型封裝庫示例代碼
  • pb檔案轉換為tflite模型檔案代碼
  • 從tensorflow的session轉換為tflite模型檔案代碼

後記

因最近在做一個嵌入式移植的實際項目,前面我放出的源碼需要修改多處Makefile,容易出錯,加上評論區有人咨詢代碼編譯相關問題,故對嵌入式部署博文及代碼進行更新。主要目的是簡化編譯操作,不需要下載下傳整個tensorflow代碼,隻需引用相關頭檔案就可完成編譯。

要達到以上目的,需要去除編譯自已寫的代碼時對tflite相關源碼的依賴,一種方式是将tensorflow代碼先編譯為庫,這樣大家在編譯自己的特殊應用時隻需連結編譯好的庫即可。因為官方的編譯腳本也在不斷更新中,我這裡主要把修改點進行說明。同樣的,我會把相關代碼放到github裡。

編譯tflib

主要通過修改官方編譯示例的編譯腳本來實作。

  1. 複制一份編譯腳本
    cp Makefile Makefile_tflib
    cp build_rpi_lib.sh build_tflib.sh
               
  2. Makefile_tflib

    檔案修改如下行
    $(LIB_PATH): tensorflow/lite/schema/schema_generated.h $(LIB_OBJS)
        @mkdir -p $(dir [email protected])
        $(AR) $(ARFLAGS) $(LIB_PATH) $(LIB_OBJS)
    ++++$(CC) -shared -o $(LIBDIR)libtensorflow-lite.so $(LIB_OBJS)
               
  3. build_tflib.sh

    檔案中将

    Makefile

    替換為

    Makefile_tflib

  4. 加載交叉編譯環境,然後執行

    ./build_tflib.sh

    進行編譯

    編譯完成後,會在

    ./gen/rpi_armv7l/lib

    下生成

    libtensorflow-lite.a

    libtensorflow-lite.so

    檔案。隻要嵌入式環境的編譯器沒變,我們就可以一直用生成的這兩個檔案
  5. 打包庫檔案。
    mkdir -p mpapp/tflib/lib
    cp ./gen/rpi_armv7l/lib/libtensorflow* mpapp/tflib/lib
               

編譯應用代碼

執行

build.sh

即可

附錄

參考

  • tflite官方網址
  • tensorflow 官方 java接口文檔