本文参照《NPU开发环境部署参考指南》,介绍了在Ubuntu系统下借助Docker镜像构建PC端模型转换环境的流程,通过容器化方案有效规避了环境依赖冲突。针对awnpu_model_zoo中未收录的模型,建议参考examples目录下的相近案例,自行适配前后处理代码并修正配置文件。若量化导出失败,需尝试裁剪模型结构或调整量化策略。文中所涉faceLandmark106pts 模型已在实际硬件平台完成推理验证。

环境配置
关于参考部署yolox的文章
【端侧部署yolo】yolo26seg部署至全志开发板T736
https://blog.csdn.net/troyteng/article/details/155444386?spm=1011.2124.3001.6209

相关文章

【端侧部署yolo】yolo26seg部署至全志开发板T736_yolov26-seg结构-CSDN博客

下载  镜像文件和AWNPU_Model_Zoo,创建自己的容器。

模型准备

buffalo_l 是 InsightFace 官方发布的 开源人脸分析模型套件 ,包含检测、识别、关键点等模型。模型下载链接:https://github.com/deepinsight/insightface/releases  buffalo_l.zip包,两个onnx,分别是:2d106det.onnx 和 det_10g.onnx
#进入docker 

sudo docker exec -it {your_docker_name} /bin/bash  #在/bin前面有空格
一键获取完整项目代码
bash
# 裁剪onnx: 这个模型可以不裁剪
 

bash
固化onnx模型的尺寸:
bash
在conda环境解决:转换到有环境的虚拟环境下,conda activate yolocd到faceLandmark106pts/convert_model(和原来一样)的目录:

python3 -m onnxsim 2d106det.onnx 2d106det_sim.onnx --overwrite-input-shape 1,3,640,640
python3 -m onnxsim det_sim.onnx det_sim_sim.onnx --overwrite-input-shape 1,3,640,640

此外,工程目录下的model_config.h头文件建议直接拷贝自官方提供的其他YOLO系列范例(如yolov5、yolov8、yolo11或yolo26)。

faceLandmark106pts的两个不同的model_config.h需要修改:

det_10g的convert_model:

# mean, scale
MEAN    = [127.5,127.5,127.5]
SCALE   = [1/128,1/128,1/128]

# reverse_channel: True bgr, False rgb
REVERSE_CHANNEL = True

2d_106det的convert_model:

# mean, scale
MEAN    = [0,0,0]
SCALE   = [1,1,1]

前处理代码:

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <stdio.h>
#include <string.h>
#include <cmath>

#include "model_config.h"

using namespace std;
using namespace cv;

float g_scale_letterbox = 1.0f;
float g_pad_left = 0.0f;
float g_pad_top = 0.0f;
float g_pad_right = 0.0f;
float g_pad_bottom = 0.0f;

static cv::Mat g_affine_matrix;

cv::Mat get_affine_matrix()
{
    return g_affine_matrix;
}

int face106_preprocess(const char* imagepath, void* buff_ptr, unsigned int buff_size)
{
    int input_w = LETTERBOX_COLS;
    int input_h = LETTERBOX_ROWS;
    int img_c = 3;
    int img_size = input_h * input_w * img_c;

    unsigned int data_size = img_size * sizeof(uint8_t);
    if (data_size > buff_size) {
        fprintf(stderr, "input buffer too small: need %u, have %u\n", data_size, buff_size);
        return -1;
    }

    cv::Mat img = cv::imread(imagepath, 1);
    if (img.empty()) {
        fprintf(stderr, "cv::imread %s failed\n", imagepath);
        return -1;
    }

    int h0 = img.rows;
    int w0 = img.cols;

    float scale = std::min((float)input_w / w0, (float)input_h / h0);
    int nw = (int)(w0 * scale);
    int nh = (int)(h0 * scale);

    cv::resize(img, img, cv::Size(nw, nh));

    cv::Mat img_new(input_h, input_w, CV_8UC3, (unsigned char*)buff_ptr);
    img_new.setTo(cv::Scalar(0, 0, 0));
    img.copyTo(img_new(cv::Rect(0, 0, nw, nh)));

    g_scale_letterbox = scale;
    g_pad_left = 0.0f;
    g_pad_top = 0.0f;
    g_pad_right = (float)(input_w - nw);
    g_pad_bottom = (float)(input_h - nh);

    return 0;
}

int face_align_preprocess(const cv::Mat& img, const float* bbox,
                          unsigned char* input_data, cv::Size input_size)
{
    float x1 = bbox[0];
    float y1 = bbox[1];
    float x2 = bbox[2];
    float y2 = bbox[3];

    float w = x2 - x1;
    float h = y2 - y1;
    float cx = (x1 + x2) * 0.5f;
    float cy = (y1 + y2) * 0.5f;

    float max_side = std::max(w, h);
    float _scale = (float)input_size.width / (max_side * 1.5f);

    float half_size = (float)input_size.width * 0.5f;

    cv::Mat M = (cv::Mat_<float>(2, 3) <<
        _scale, 0.0f, half_size - cx * _scale,
        0.0f, _scale, half_size - cy * _scale);

    g_affine_matrix = M.clone();

    cv::Mat aimg(input_size, CV_8UC3, input_data);
    cv::warpAffine(img, aimg, M, input_size,
                   cv::INTER_LINEAR, cv::BORDER_CONSTANT, cv::Scalar(0, 0, 0));

    return 0;
}

后处理yolo26_seg_post.cpp

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>
#include <stdio.h>
#include <vector>
#include <cmath>
#include <algorithm>

#include "model_config.h"

using namespace std;

extern cv::Mat get_affine_matrix();

static void distance2bbox(const cv::Point2f& anchor, float dx, float dy, float rx, float ry,
                          float& x1, float& y1, float& x2, float& y2)
{
    x1 = anchor.x - dx;
    y1 = anchor.y - dy;
    x2 = anchor.x + rx;
    y2 = anchor.y + ry;
}

int scrfd_postprocess(float** output, int output_cnt, int img_h, int img_w,
                      std::vector<FaceDetectResult>& dets)
{
    const float det_thresh = 0.5f;
    const float nms_thresh = 0.4f;

    int fmc = 3;
    int num_anchors = 1;

    if (output_cnt == 9) {
        fmc = 3;
        num_anchors = 2;
    } else if (output_cnt == 15) {
        fmc = 5;
        num_anchors = 1;
    }

    const int feat_stride_fpn[] = {8, 16, 32, 64, 128};
    const int input_h = LETTERBOX_ROWS;
    const int input_w = LETTERBOX_COLS;

    float scale = g_scale_letterbox;

    vector<float> scores_list;
    vector<float> bboxes_list;

    for (int idx = 0; idx < fmc; idx++) {
        const float* scores_ptr = output[idx];
        const float* bbox_preds_ptr = output[idx + fmc];

        int stride = feat_stride_fpn[idx];
        int height = input_h / stride;
        int width = input_w / stride;
        int total_anchors = height * width * num_anchors;

        vector<cv::Point2f> anchor_centers;
        anchor_centers.reserve(total_anchors);
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                cv::Point2f pt((float)((x + 0.5f) * stride), (float)((y + 0.5f) * stride));
                for (int a = 0; a < num_anchors; a++) {
                    anchor_centers.push_back(pt);
                }
            }
        }

        for (int i = 0; i < total_anchors; i++) {
            float score = scores_ptr[i];
            if (score < det_thresh) continue;
            float scale_dx = 2.3f;
            float scale_dy = 3.7f;
            float scale_rx = 2.5f;
            float scale_ry = 2.5f;
            float dx = bbox_preds_ptr[0 * total_anchors + i] * stride * scale_dx;
            float dy = bbox_preds_ptr[1 * total_anchors + i] * stride * scale_dy;
            float rx = bbox_preds_ptr[2 * total_anchors + i] * stride * scale_rx;
            float ry = bbox_preds_ptr[3 * total_anchors + i] * stride * scale_ry;

            float x1, y1, x2, y2;
            distance2bbox(anchor_centers[i], dx, dy, rx, ry, x1, y1, x2, y2);

            if (x2 <= x1 || y2 <= y1) continue;

            scores_list.push_back(score);
            bboxes_list.push_back(x1);
            bboxes_list.push_back(y1);
            bboxes_list.push_back(x2);
            bboxes_list.push_back(y2);
        }
    }

    if (scores_list.empty()) {
        return 0;
    }

    vector<int> indices(scores_list.size());
    for (int i = 0; i < (int)indices.size(); i++) indices[i] = i;

    sort(indices.begin(), indices.end(), [&](int a, int b) {
        return scores_list[a] > scores_list[b];
    });

    vector<bool> suppressed(scores_list.size(), false);
    vector<int> keep;

    for (int i = 0; i < (int)indices.size(); i++) {
        int idx = indices[i];
        if (suppressed[idx]) continue;

        keep.push_back(idx);

        float x1 = bboxes_list[idx * 4 + 0];
        float y1 = bboxes_list[idx * 4 + 1];
        float x2 = bboxes_list[idx * 4 + 2];
        float y2 = bboxes_list[idx * 4 + 3];
        float area_a = (x2 - x1 + 1.0f) * (y2 - y1 + 1.0f);

        for (int j = i + 1; j < (int)indices.size(); j++) {
            int jdx = indices[j];
            if (suppressed[jdx]) continue;

            float x1_j = bboxes_list[jdx * 4 + 0];
            float y1_j = bboxes_list[jdx * 4 + 1];
            float x2_j = bboxes_list[jdx * 4 + 2];
            float y2_j = bboxes_list[jdx * 4 + 3];

            float inter_x1 = max(x1, x1_j);
            float inter_y1 = max(y1, y1_j);
            float inter_x2 = min(x2, x2_j);
            float inter_y2 = min(y2, y2_j);

            float inter_w = max(0.0f, inter_x2 - inter_x1 + 1.0f);
            float inter_h = max(0.0f, inter_y2 - inter_y1 + 1.0f);
            float inter_area = inter_w * inter_h;
            float area_j = (x2_j - x1_j + 1.0f) * (y2_j - y1_j + 1.0f);
            float iou = inter_area / (area_a + area_j - inter_area);

            if (iou >= nms_thresh) {
                suppressed[jdx] = true;
            }
        }
    }

    dets.clear();
    for (int idx : keep) {
        FaceDetectResult det;
        det.x1 = bboxes_list[idx * 4 + 0] / scale;
        det.y1 = bboxes_list[idx * 4 + 1] / scale;
        det.x2 = bboxes_list[idx * 4 + 2] / scale;
        det.y2 = bboxes_list[idx * 4 + 3] / scale;
        det.score = scores_list[idx];

        det.x1 = max(0.0f, min(det.x1, (float)(img_w - 1)));
        det.y1 = max(0.0f, min(det.y1, (float)(img_h - 1)));
        det.x2 = max(0.0f, min(det.x2, (float)(img_w - 1)));
        det.y2 = max(0.0f, min(det.y2, (float)(img_h - 1)));

        dets.push_back(det);
    }

    return (int)dets.size();
}

int face106_postprocess(float* output, int output_size, cv::Size input_size,
                        std::vector<LandmarkPoint>& landmarks)
{
    int lmk_num = 106;
    int lmk_dim = 2;

    landmarks.clear();
    landmarks.resize(lmk_num);

    for (int i = 0; i < lmk_num; i++) {
        landmarks[i].x = output[i * lmk_dim + 0];
        landmarks[i].y = output[i * lmk_dim + 1];
        landmarks[i].z = 0.0f;
    }

    float half_size = (float)input_size.width * 0.5f;
    for (auto& pt : landmarks) {
        pt.x = (pt.x + 1.0f) * half_size;
        pt.y = (pt.y + 1.0f) * half_size;
    }

    cv::Mat M = get_affine_matrix();
    if (!M.empty()) {
        cv::Mat IM;
        cv::invertAffineTransform(M, IM);

        for (auto& pt : landmarks) {
            float x = pt.x;
            float y = pt.y;
            pt.x = IM.at<float>(0, 0) * x + IM.at<float>(0, 1) * y + IM.at<float>(0, 2);
            pt.y = IM.at<float>(1, 0) * x + IM.at<float>(1, 1) * y + IM.at<float>(1, 2);
        }
    }

    return lmk_num;
}

void draw_results(cv::Mat& img, const std::vector<FaceDetectResult>& dets,
                  const std::vector<std::vector<LandmarkPoint>>& all_landmarks)
{
    for (size_t i = 0; i < dets.size(); i++) {
        const FaceDetectResult& det = dets[i];

        cv::rectangle(img,
                      cv::Point((int)det.x1, (int)det.y1),
                      cv::Point((int)det.x2, (int)det.y2),
                      cv::Scalar(0, 255, 0), 2);

        if (i < all_landmarks.size()) {
            for (const auto& pt : all_landmarks[i]) {
                cv::circle(img, cv::Point((int)pt.x, (int)pt.y), 2, cv::Scalar(0, 255, 0), -1);
            }
        }

        char text[64];
        sprintf(text, "%.2f", det.score);
        cv::putText(img, text, cv::Point((int)det.x1, (int)det.y1 - 5),
                    cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 255, 0));
    }
}


模型转换
后续的一系列和其他模型一样,这里就直接给出转换的命令了,详细的说明可以参考yolo26seg的部署,或者《NPU_模型部署_开发指南》

#导出模型,注意这里需要分别导入两个onnx模型,需要生成两个nb文件

# using xxx_env.sh to create softlink 生成软连接,避免复用
./convert_model_env.sh
 
# 导入
# pegasus_import.sh <model_name>
./pegasus_import.sh 2d106det
./pegasus_import.sh det_10g

# 量化
# pegasus_quantize.sh <model_name> <quantize_type> <calibration_set_size>
./pegasus_quantize.sh 2d106det_sim int16 12
./pegasus_quantize.sh det_10g_sim int16 12
 
# 仿真(可选)
# pegasus_inference.sh <model_name> <quantize_type>
./pegasus_inference.sh 2d106det_sim int16
./pegasus_inference.sh det_10g_sim int16
 
# 导出nb模型
# pegasus_export_ovx_nbg.sh <model_name> <quantize_type> <platform>
./pegasus_export_ovx_nbg.sh 2d106det_sim int16 t736
./pegasus_export_ovx_nbg.sh det_10g_sim int16 t736

# 导出的模型文件存放在../model目录,例如:

../model/2d106det_sim_int16_t736.nb
../model/det_10g_sim_sim_int16_t736.nb

板端demo
含demo编译及运行说明。

解压opencv压缩包

# 进入目录
cd ../../../3rdparty/opencv/
# 解压,选择对应平台
# armhf, eg: V85x, R853
unzip opencv-3.4.16-gnueabihf-linux.zip
# linux aarch64, eg: T527/MR527/MR536/T536/A733/T736
unzip opencv-4.9.0-aarch64-linux-sunxi-glibc.zip
# android aarch64, eg: T527/A733/T736
unzip opencv-4.9.0-android.zip

准备交叉编译工具链
Linux

# 进入目录
cd ../../0-toolchains/
# 解压
# armhf, V85x, R853
unzip arm-openwrt-linux-muslgnueabi.zip
chmod 777 -R ./arm-openwrt-linux-muslgnueabi
# aarch64, MR527, T527, MR536, T536, A733, T736
tar xvf gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu.tar.xz
# aarch64 for debian11, T527, A733, T736
tar vxf gcc-arm-10.2-2020.11-x86_64-aarch64-none-linux-gnu.tar.xz

编译脚本会根据平台自动选择交叉编译工具链,若需使用其它路径的工具链,可在cmake_toolchain目录修改.cmake文件内容指定对应的交叉编译工具链路径。

编译、推理
这里以在Linux系统下进行编译推理,Android系统参考yolox的部署,这里直接给出命令行,根据自己的平台和系统进行修改就行了。

# 进入faceLandmark106pts目录,进行编译

cd ../examples/faceLandmark106pts/
./../build_linux.sh -t t736  #在faceLandmark106pts的目录运行这段代码

# 首先打开cmd输入:
adb shell

# 连接上板子之后:创建目录
mkdir -p mnt/UDISK  #外部存储

# 推送文件:
adb push adb push U:\docker_data\awnpu_model_zoo\examples\facemark\install\faceLandmark106pts/mnt/UDISK/  #将 faceLandmark106pts_post  #放到刚刚创建的存储

cd 到 faceLandmark106pts_demo_linux_t736目录下运行

# cd到目录
cd /mnt/UDISK/faceLandmark106pts_demo_linux_t736
 
#推理
./faceLandmark106pts_demo_t736 \
  -nb_det model/det_sim_int16_t736.nb \
  -nb_lmk model/2d106det_int16_t736.nb \
  -i model/test.jpg \
  -l 1 \
  -m 20

# 输出结果
detected 1 faces
face 0: (241.7, 55.2)-(366.8, 210.9) score=0.7612 wh=125x156
lmk preprocess (face 0): 3415 us
lmk inference (face 0): 1471 us
lmk output: ptr=0x2c386cc0, floats=212, first_4=[-0.0258, 0.6683, -0.5255, -0.2250]
face 0: 106 landmarks
result saved to output_facemark.jpg
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐