【端侧部署faceLandmark106pts】faceLandmark106pts部署至全志开发板T736
本文参照《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
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)