文章轉載于微信公眾號:GiantPandaCV
作者: 阿呆
開發環境選擇
- 本文操作系統為Windows,因為Windows上的安卓模擬器選擇較多,并且真機調試也比較方便;
- 交叉編譯在Windows和Ubuntu上都進行了嘗試,都可行,但是如果是Ubuntu上交叉編譯之后再挪到Windows的話,容易出幺蛾子;
- 我目前使用的最穩定的工具版本組合是:ndk18、androidstudio4.1、cmake3.10、gradle6.5、MinGW(CodeBlocks自帶)。
1. PyTorch模型轉NCNN
這一小節是介紹如何將自己重新訓練過的PyTorch模型轉成ncnn,如果沒有重訓練需求的話,可以直接跳過這一節。
(1) 整體步驟
理想情況下,從PyTorch轉到ncnn只需要下面兩步:
- PyTorch轉ONNX
torch.onnx._export(model,x,path,opset_version=11)
- ONNX轉NCNN
./onnx2ncnnmodel.onnxmodel.parammodel.bin
遇到問題的適合解決思路如下:
convert.png
下面介紹一下我在做ChineseOCRLite中的PSENet模型轉換的過程中遇到的問題。
(2)實際操作的時候可能會遇到各種問題
問題1:ReLU6不支持
概述:ReLU6算子在轉換的時候容易出現不支持的情況,需要使用其他算子替代
解決:使用torch.clamp替代(雖然ReLU6可以通過組合ReLU的方式實現,但是組合得到的ReLU6在NCNN中容易轉換失敗,不建議使用。)
defrelu6(x,inplace=True):
returntorch.clamp(x,0,6)
問題2:Resize算子轉換問題
概述:因為各個框架對Resize算子的支持都不盡相同,在轉換過程中總會出現一些問題,pytorch中的interpolate算子轉換成ONNX之后變成很多零散的算子,如cast、shape等,這些在ncnn里面不支持。你可以選擇手動修改文件,也可以使用下面這個自動的方法:
解決:使用onnx/_simplifier對onnx模型進行簡化,可以合并這些零散的算子。
python-monnxsimmodel.onnxmodel_sim.onnx
問題3:關于轉ONNX及使用onnx/_simplifier過程中出現的一系列奇怪問題
概述:使用不同版本的ONNX可能會遇到不同的問題,比如提示conv層無輸入等(具體錯誤名稱記不清了)。
解決:下載最新ONNX源碼編譯安裝(onnx/_simplifier中出現的一些錯誤也可以通過安裝最新ONNX來解決)
gitclonehttps://github.com/onnx/onnx.git
sudoapt-getinstallprotobuf-compilerlibprotoc-dev
cdONNX
pythonsetup.pyinstall
問題4:模型輸出結果的尺寸固定
概述:直接轉換得到的onnx模型的Resize算子都是固定輸出尺寸的,無論輸入多大的圖片都會輸出同樣大小的特征圖,這無疑會影響到模型的精度及靈活性。
解決:修改NCNN模型的param文件,將Resize算子修改成按比例resize。
直接轉換得到的param文件中的Interp算子是這樣的:
Interp913119019130=21=1.000000e+002=1.000000e+003=6404=640
從下面的ncnn源碼中可以看到,0代表resize/_type,1和2分別是高和寬的縮放比例,3和4分別是輸出的高和寬。
intInterp::load_param(constParamDict&pd)
{
resize_type=pd.get(0,0);
height_scale=pd.get(1,1.f);
width_scale=pd.get(2,1.f);
output_height=pd.get(3,0);
output_width=pd.get(4,0);
return0;
}
我們只需將其修改成如下格式即可實現按比例resize:
Interp913119019130=11=4.000000e+002=4.000000e+00
問題5:NCNN模型輸出結果與ONNX模型不同
解決:逐層對比NCNN與onnx模型的輸出結果
使用onnxruntime(Python)和NCNN(C++)分別提取每個節點的輸出,進行對比。對于ncnn比較簡單,可以使用
extractor.extract(node_name,preds);
來提取不同節點的輸出。
問題5衍生問題1:ONNX沒有提供提取中間層輸出的方法
解決:給要提取的層添加一個輸出節點,代碼如下:
deffind_node_by_name(graph,node_name):
fornodeingraph.node:
ifnode.output[0]==node_name:
returnnode
returnNone
defadd_extra_output_node(model,target_node,output_name):
extra_output=helper.make_empty_tensor_value_info(output_name)
target_output=target_node.output[0]
identity_node=helper.make_node("Identity",inputs=[target_output],outputs=[output_name],name=output_name)
model.graph.node.append(identity_node)
model.graph.output.append(extra_output)
returnmodel
修改模型之后再使用
out=sess.run([output_name],{"input.1":img.astype(np.float32)})
就可以獲取到模型的中間層輸出了。
問題5衍生問題2:發現最后一個Resize層的輸出有差異
解決:參考chineseocr/_lite里面的代碼把mode由bilinear改成了nearest(這里錯誤的原因可能是wenmuzhou/PSENet.pytorch中的模型最后一個F.interpolate中的align/_corners參數設置成了True。據說NCNN只實現了align/_corners為False的情況)。
這里修改之后的模型跟原模型之間是會有少許誤差的,如果誤差不可接受,就要重新訓練才行。
2. 交叉編譯opencv與ncnn
交叉編譯工作可以在windows上進行,使用的是MinGW + cmkae3.10 + AndroidNDK18。可以參考Windows下編譯OpenCV android(https://www.cnblogs.com/zhxmdefj/p/13094954.html
)
沒有windows C++環境的話,也可以選擇在linux上進行。
如果是在linux交叉編譯,然后復制到windows的話,需要修改一下opencv中cmake配置文件中的路徑。
(1)android ndk下載
最初選擇的是r20b,因為和CMake之間的兼容問題,切換到了18b。
wgethttps://dl.google.com/android/repository/android-ndk-r18b-linux-x86_64.zip?hl=zh_cn
mvandroid-ndk-r18b-linux-x86_64.zip?hl=zh_cnandroid-ndk-r18b-linux-x86_64.zip
unzipandroid-ndk-r18b-linux-x86_64.zip
(2)編譯opencv
利用android中提供的android.toolchain.cmake 工具鏈可以快速的編譯opencv的arm版。
這里選擇的arm平臺是armeabi-v7a,便于在老舊手機上運行。
folde
if[[!-d"$folder"]];then
echo"$foldernotfound,creatingfolder..."
mkdirbuild_arm
fi
cdbuild_arm
cmake/
-DCMAKE_TOOLCHAIN_FILE=/
/home/dai/soft/android-ndk-r18b/build/cmake/android.toolchain.cmake/
-DANDROID_NDK=/home/dai/soft/android-ndk-r18b/
-DCMAKE_BUILD_TYPE=Release/
-DBUILD_ANDROID_PROJECTS=OFF/
-DBUILD_ANDROID_EXAMPLES=OFF/
-DANDROID_ABI=armeabi-v7a/
-DANDROID_NATIVE_API_LEVEL=21..
make-j4
(3)編譯ncnn
編譯選項參考ncnn wiki(https://github.com/Tencent/nc...
folde
if[[!-d"$folder"]];then
echo"$foldernotfound,creatingfolder..."
mkdirbuild_arm
fi
cdbuild_arm
cmake/
-DCMAKE_TOOLCHAIN_FILE=/
/home/dai/soft/android-ndk-r18b/build/cmake/android.toolchain.cmake/
-DANDROID_AB/
-DANDROID_ARM_NEON=ON/
-DANDROID_PLATFORM=android-14/
..
make-j4
(4)chineseocr/_lite的PC端測試
與ncnn有關的代碼位于ncnn/_project目錄下。在有opencv和ncnn庫的基礎上,可以先在pc端跑一下識別代碼。
cdncnn_project/ocr
mkdirbuild_arm
cdbuild_arm
cmake..
make-j4
編譯完成之后
./TextRecognition../test1.jpg
可以看到輸出結果:
psenet前向時間:0.462291s
psenetdecode時間:0.0604791s
boxzie10
預測結果:
一
統
;名
稱
丹正珍
類住
型
有限責
所
中山市
角度檢測和文字識別總時間:1.52042s
3. NCNN模型的安卓端部署
因為代碼較長,這一部分只介紹把PC端代碼遷移到安卓端的思路,想看詳細代碼的同學請移步文末的Github地址。
遷移的整體思路如下圖所示:
android/_flow.png
下面一一介紹圖中內容
UI界面
這個demo的UI界面中至少應包含三個元件:
Button——用于選擇相冊圖片
ImageView——用于展示圖片及文本檢測框
TextView——用于展示識別結果
界面截圖如下(TextView在沒有文字的時候是不顯示的):
UI.jpg
界面res/layout/activity/_main.xml文件修改。
Java部分
模型推理是在C++中完成的,Java部分的代碼主要是利用安卓的API實現圖片讀取、文本檢測框繪制和結果展示等功能。
需要傳入到C++函數的內容包括Bitmap圖片和AssetManager對象。
從C++接收到的是一個包含文本框和識別結果的字符串。
C++部分
C++負責模型推理,推理代碼與PC端無異,只是安卓端的文件讀取與PC端不同,需要修改文件讀取代碼,比如crnn的模型加載代碼就需要改成下面的樣子:
intmodel::init(AAssetManager*mgr,conststd::stringcrnn_param,conststd::stringcrnn_bin)
{
intret1=crnn.load_param(mgr,crnn_param.c_str());
intret2=crnn.load_model(mgr,crnn_bin.c_str());
LOGI("ret1is%d,ret2is%d",ret1,ret2);
return(ret1||ret2);
}
另外還需要把Java部分傳過來的Bitmap轉換成cv::Mat,代碼如下:
//convertbitmaptomat
int*data=NULL;
AndroidBitmapInfoinfo={0};
AndroidBitmap_getInfo(env,bitmap,&info);
AndroidBitmap_lockPixels(env,bitmap,(void**)&data);
//這里偷懶只寫了RGBA格式的轉換
LOGI("infoformatRGBA?%d",info.format==ANDROID_BITMAP_FORMAT_RGBA_8888);
cv::Mattest(info.height,info.width,CV_8UC4,(char*)data);//RGBA
cv::Matimg_bgr;
cvtColor(test,img_bgr,CV_RGBA2BGR);
最終識別結果
最終得到的demo識別結果如下圖所示:
result.jpg
本項目完整代碼請移步github:
https://github.com/Arctanxy/D...
推薦閱讀
更多嵌入式AI技術干貨請關注嵌入式AI專欄。
審核編輯:符乾江
-
AI
+關注
關注
88文章
34781瀏覽量
277134 -
深度學習
+關注
關注
73文章
5557瀏覽量
122658 -
pytorch
+關注
關注
2文章
809瀏覽量
13869
發布評論請先 登錄
AI大模型端側部署正當時:移遠端側AI大模型解決方案,激活場景智能新范式

自制 AirTag,支持安卓/鴻蒙/PC/Home Assistant,無需擁有 iPhone
AI模型部署邊緣設備的奇妙之旅:目標檢測模型
如何在 PyTorch 中訓練模型
新手小白怎么通過云服務器跑pytorch?
基于Pytorch訓練并部署ONNX模型在TDA4應用筆記

評論