在安卓上執行yolov8目標檢測模型(ncnn)

shizidushu發表於2024-07-25

在安卓上執行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.pyC2fforward 方法為:

    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.pyDetectforward 方法為:

    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.parambest.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.binyolov8n.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_classYolo::draw中定義的class_names,使其和自己的模型一致

最後使用android studio開啟,配置好ndk依賴等,執行

相關文章