在安卓上執行yolov8目標檢測模型(ncnn)
關於
- 首次發表日期:2024-07-25
- 本人不會Java和Android,如有錯誤,敬請指出
- 主要是整理一下以下資料
- https://medium.com/@gary.tsai.advantest/top-tutorials-for-deploying-custom-yolov8-on-android-️-dd6746afc1e6
- https://github.com/FeiGeChuanShu/ncnn-android-yolov8
- https://github.com/Digital2Slave/ncnn-android-yolov8-seg/wiki/Convert-yolov8‐seg-to-ncnn-model-step-by-step
準備環境
首先準備conda環境,由於使用的是較舊版本的yolov8庫,這兒也採用相應版本的pytorch
conda create -n ncnn_yolov8 python=3.8
conda activate ncnn_yolov8
pip install torch==1.8.2 torchvision==0.9.2 torchaudio==0.8.2 --extra-index-url https://download.pytorch.org/whl/lts/1.8/cu111
git clone https://github.com/ultralytics/ultralytics
cd ultralytics
git checkout b9b0fd8bf409c822b7fcb21d65722b242f5307fc
pip install -r requirements.txt
pip install -e .
訓練
第一步,先更新yolov8的設定,主要是配置一下資料集資料夾:
新增update_settings.py
檔案:
# update_settings.py
from ultralytics import settings
import os
current_folder = os.path.dirname(__file__)
datasets_dir = '/mnt/d/0-Datasets/ultralytics_datasets_dir/datasets'
weights_dir = os.path.join(current_folder, "weights")
runs_dir = os.path.join(current_folder, "runs")
settings.update({'datasets_dir': datasets_dir})
settings.update({'weights_dir': weights_dir})
settings.update({'runs_dir': runs_dir})
os.makedirs(weights_dir, exist_ok=True)
os.makedirs(runs_dir, exist_ok=True)
python update_settings.py
然後進行訓練,這兒以coco8
資料集為例。
新增train.py
檔案:
# train.py
from ultralytics import YOLO
import torch
resume = False
if not resume:
# Create a new YOLO model from scratch
model = YOLO('yolov8n.yaml').load('yolov8n.pt')
else:
model = YOLO('runs/detect/train/weights/last.pt')
device_count = torch.cuda.device_count()
model.train(data='coco8.yaml', imgsz=640, epochs=10, resume=resume, batch = 32 * device_count, device=list(range(0,device_count)))
python train.py
匯出權重檔案
匯出onnx檔案
from ultralytics import YOLO
model = YOLO("runs/detect/train4/weights/best.pt")
# Export model
success = model.export(task="detection", format="onnx", opset=12, imgsz=640, simplify=True)
安裝ncnn
首先需要確保cuda的版本是11.8。
我同時安裝了12.2和11.8,預設版本配置在.bashrc
檔案中,之前預設的版本是12.2,所以我修改了一下:
export PATH=/usr/local/cuda-11.8/bin${PATH:+:${PATH}}
source ~/.bashrc
然後安裝:
cd ..
git clone https://github.com/Tencent/ncnn.git
cd ncnn
git submodule update --init
sudo apt install build-essential git cmake libprotobuf-dev protobuf-compiler libvulkan-dev vulkan-utils libopencv-dev
# build part
mkdir build && cd build
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local/ ..
make -j$(nproc)
sudo make install
匯出ncnn
修改 ultralytics/ultralytics/nn/modules/block.py
中 C2f
的 forward
方法為:
def forward(self, x):
# """Forward pass through C2f layer."""
# y = list(self.cv1(x).chunk(2, 1))
# y.extend(m(y[-1]) for m in self.m)
# return self.cv2(torch.cat(y, 1))
# !< https://github.com/FeiGeChuanShu/ncnn-android-yolov8
x = self.cv1(x)
x = [x, x[:, self.c:, ...]]
x.extend(m(x[-1]) for m in self.m)
x.pop(1)
return self.cv2(torch.cat(x, 1))
修改 ultralytics/ultralytics/nn/modules/head.py
中 Detect
的 forward
方法為:
def forward(self, x):
shape = x[0].shape # BCHW
for i in range(self.nl):
x[i] = torch.cat((self.cv2[i](x[i]), self.cv3[i](x[i])), 1)
if self.training:
return x
elif self.dynamic or self.shape != shape:
self.anchors, self.strides = (x.transpose(0, 1) for x in make_anchors(x, self.stride, 0.5))
self.shape = shape
# box, cls = torch.cat([xi.view(shape[0], self.no, -1) for xi in x], 2).split((self.reg_max * 4, self.nc), 1)
# dbox = dist2bbox(self.dfl(box), self.anchors.unsqueeze(0), xywh=True, dim=1) * self.strides
# y = torch.cat((dbox, cls.sigmoid()), 1)
# return y if self.export else (y, x)
return torch.cat([xi.view(shape[0], self.no, -1) for xi in x], 2).permute(0, 2, 1)
匯出
cd ultralytics/runs/detect/train/weights
onnx2ncnn best.onnx best.param best.bin
這一步將生成best.param
和best.bin
這2個檔案
配置android demo
這兒將使用 https://github.com/FeiGeChuanShu/ncnn-android-yolov8 提供的android demo
git clone https://github.com/FeiGeChuanShu/ncnn-android-yolov8
cd ncnn-android-yolov8/ncnn-android-yolov8
下載ncnn-YYYYMMDD-android-vulkan.zip,移到到 app/src/main/jni/
中,並解壓
下載opencv-mobile,移到到 app/src/main/jni/
中,並解壓
開啟 app/src/main/jni/CMakeLists.txt
,確保 opencv 和 ncnn 的版本與下載的版本匹配,修改後類似如下:
project(yolov8ncnn)
cmake_minimum_required(VERSION 3.10)
set(OpenCV_DIR ${CMAKE_SOURCE_DIR}/opencv-mobile-4.6.0-android/sdk/native/jni)
find_package(OpenCV REQUIRED core imgproc)
set(ncnn_DIR ${CMAKE_SOURCE_DIR}/ncnn-20220420-android-vulkan/${ANDROID_ABI}/lib/cmake/ncnn)
find_package(ncnn REQUIRED)
add_library(yolov8ncnn SHARED yolov8ncnn.cpp yolo.cpp ndkcamera.cpp)
target_link_libraries(yolov8ncnn ncnn ${OpenCV_LIBS} camera2ndk mediandk)
將 assets 資料夾中的權重檔案備份,然後移入之前匯出的ncnn權重,並重新命名為 yolov8n.bin
和 yolov8n.param
根據 yolov8n.param
檔案的最後一行:
Permute Transpose_227 1 1 380 output0 0=1
修改 app\src\main\jni\yolo.cpp
中的輸出層名稱,修改後的那一行如下:
ex.extract("output0", out);
然後修改 app\src\main\jni\yolo.cpp
中的 generate_proposals
函式中定義的num_class
和Yolo::draw
中定義的class_names
,使其和自己的模型一致
最後使用android studio開啟,配置好ndk依賴等,執行