Android ncnn-android-yolov8-seg源码解析 : 实现人像分割

news/2024/7/10 20:45:55 标签: android, python, YOLO8, NCNN, YOLO, 人像分割, 人体识别

1. 前言

上篇文章,我们已经将人像分割ncnn-android-yolov8-seg项目运行起来了,后续文章我们会抽取出Demo中的核心代码,在自己的项目中,来接入人体识别人像分割功能。

先来看下效果,整个图像的是相机的原图,左上角部分,是我们进行人像识别、人像分割后,处理得到的图像 (未做镜像处理,所以暂时和原图左右是相反的)

在这里插入图片描述

那我们要怎么在自己的项目中,实现人像分割功能呢 ?

我们看ncnn-android-yolov8-seg的源码,可以发现, 这个项目里的相机也是用c/c++,但是在我们项目中,使用的Java层的Camera API来实现的。要想在自己项目里集成ncnn,那就需要把ncnn-android-yolov8-seg里的核心代码给抽离,然后对接到JavaCamera API中。

那需要怎么做呢 ? 接下来我们先来看一下它的源码

2. 源码分析

首先,我们来分析一下 Digital2Slave/ncnn-android-yolov8-seg Demo 中的源码

2.1 加载模型

加载模型是在Java_com_tencent_yolov8ncnn_Yolov8Ncnn_loadModel方法中,这里附上源码

JNIEXPORT jboolean JNICALL Java_com_tencent_yolov8ncnn_Yolov8Ncnn_loadModel(JNIEnv* env, jobject thiz, jobject assetManager, jint modelid, jint cpugpu)
{
    if (modelid < 0 || modelid > 6 || cpugpu < 0 || cpugpu > 1)
    {
        return JNI_FALSE;
    }

    AAssetManager* mgr = AAssetManager_fromJava(env, assetManager);

    __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "loadModel %p", mgr);

    const char* modeltypes[] =
    {
        "n",
        "s",
    };

    const int target_sizes[] =
    {
        320,
        320,
    };

    const float mean_vals[][3] =
    {
        {103.53f, 116.28f, 123.675f},
        {103.53f, 116.28f, 123.675f},
    };

    const float norm_vals[][3] =
    {
        { 1 / 255.f, 1 / 255.f, 1 / 255.f },
        { 1 / 255.f, 1 / 255.f, 1 / 255.f },
    };

    const char* modeltype = modeltypes[(int)modelid];
    int target_size = target_sizes[(int)modelid];
    bool use_gpu = (int)cpugpu == 1;

    // reload
    {
        ncnn::MutexLockGuard g(lock);

        if (use_gpu && ncnn::get_gpu_count() == 0)
        {
            // no gpu
            delete g_yolo;
            g_yolo = 0;
        }
        else
        {
            if (!g_yolo)
                g_yolo = new Yolo;
            g_yolo->load(mgr, modeltype, target_size, mean_vals[(int)modelid], norm_vals[(int)modelid], use_gpu);
        }
    }

    return JNI_TRUE;
}
2.1.1 modelid : 选择某个模型

根据modelid,来选择modeltypes中具体的某个模型。

const char* modeltypes[] =
{
    "n",
    "s",
};
const char* modeltype = modeltypes[(int)modelid];

这里的模型类别是和项目中asserts文件夹下的模型对应的,yolov8是个模型簇,从小到大包括:yolov8nyolov8syolov8myolov8lyolov8x等。
在这里插入图片描述
通常yolov8n速度最快,具体见下表
在这里插入图片描述

2.1.2 cpugpu : 使用CPU或GPU

接着来看Java_com_tencent_yolov8ncnn_Yolov8Ncnn_loadModel方法,
还有一个参数cpugpu是用来决定使用CPU还是GPU0CPU1GPU

bool use_gpu = (int)cpugpu == 1;
2.1.3 初始化模型

最后,调用g_yolo->load()来初始化模型

g_yolo->load(mgr, modeltype, target_size, mean_vals[(int)modelid], norm_vals[(int)modelid], use_gpu);

2.2 操作相机

ncnn-android-yolov8-seg Demo中的相机操作,都是通过NDKCamera API来完成的

static MyNdkCamera* g_camera = 0;

JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
    __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "JNI_OnLoad");

    g_camera = new MyNdkCamera;

    return JNI_VERSION_1_4;
}
2.2.1 打开相机

打开相机就是调用g_camera->open()

JNIEXPORT jboolean JNICALL Java_com_tencent_yolov8ncnn_Yolov8Ncnn_openCamera(JNIEnv* env, jobject thiz, jint facing)
{
    if (facing < 0 || facing > 1)
        return JNI_FALSE;

    __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "openCamera %d", facing);

    g_camera->open((int)facing);

    return JNI_TRUE;
}
2.2.2 关闭相机

关闭相机是调用g_camera->close()

JNIEXPORT jboolean JNICALL Java_com_tencent_yolov8ncnn_Yolov8Ncnn_closeCamera(JNIEnv* env, jobject thiz)
{
    __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "closeCamera");

    g_camera->close();

    return JNI_TRUE;
}

2.3 人体检测

人体检测是和NDKCamera相关联的,在相机回调的on_image_render方法中,完成了人体检测

2.3.1 on_image_render : 进行人体检测

来看一下on_image_render的源码,主要是通过g_yolo->detect()进行人体检测,g_yolo->draw()标注人体位置,用框框出来。

void MyNdkCamera::on_image_render(cv::Mat& rgb) const
{
    // nanodet
    {
        ncnn::MutexLockGuard g(lock);
        //cv::resize()

        if (g_yolo)
        {
            __android_log_print(ANDROID_LOG_DEBUG, "myncnn", "g_yolo:true");

            auto start = std::chrono::high_resolution_clock::now();

            std::vector<Object> objects;
            g_yolo->detect(rgb, objects); //人体检测

            start = std::chrono::high_resolution_clock::now();

            g_yolo->draw(rgb, objects); //标注人体位置,用框框出来
        }
        else
        {
            __android_log_print(ANDROID_LOG_DEBUG, "myncnn", "g_yolo:false");
            draw_unsupported(rgb);
        }
    }

    draw_fps(rgb); //绘制当前多少帧率
}
2.3.2 on_image_render 什么时候被调用 ?

那么on_image_render方法是什么时候被调用的呢 ? 来看ndkcamera.cpp中的on_image方法

可以看到,这里会对NV21图像做裁剪和旋转操作,再转成RGB格式,然后才传递给on_image_render()方法处理

// crop and rotate nv21
cv::Mat nv21_croprotated(roi_h + roi_h / 2, roi_w, CV_8UC1);
{
    const unsigned char* srcY = nv21 + nv21_roi_y * nv21_width + nv21_roi_x;
    unsigned char* dstY = nv21_croprotated.data;
    ncnn::kanna_rotate_c1(srcY, nv21_roi_w, nv21_roi_h, nv21_width, dstY, roi_w, roi_h, roi_w, rotate_type);

    const unsigned char* srcUV = nv21 + nv21_width * nv21_height + nv21_roi_y * nv21_width / 2 + nv21_roi_x;
    unsigned char* dstUV = nv21_croprotated.data + roi_w * roi_h;
    ncnn::kanna_rotate_c2(srcUV, nv21_roi_w / 2, nv21_roi_h / 2, nv21_width, dstUV, roi_w / 2, roi_h / 2, roi_w, rotate_type);
}

// nv21_croprotated to rgb
cv::Mat rgb(roi_h, roi_w, CV_8UC3);
ncnn::yuv420sp2rgb(nv21_croprotated.data, roi_w, roi_h, rgb.data);

on_image_render(rgb);
2.3.3 人体检测的流程

也就是说,从相机中得到的NV21数据,会先进行旋转,然后转成RGB格式,再交由g_yolo->detect()进行人体检测,通过g_yolo->draw()来标注人体位置。

到这里,我们核心源码就分析的差不多了,那我们怎么将该功能集成到自己的项目中呢 ?
我们在下一篇文章中来实现下 : Android 在自己的项目接入OpenCV+YOLOv8+NCNN,实现人像分割-CSDN博客。

3. Android 人像识别 系列文章

  • OpenCV相关

    • Visual Studio 2022 cmake配置opencv开发环境_opencv visualstudio配置_氦客的博客-CSDN博客
    • 在Visual Studio上,使用OpenCV实现人脸识别_氦客的博客-CSDN博客
    • Android Studio 接入OpenCV,并实现灰度图效果_氦客的博客-CSDN博客
    • Android 使用OpenCV实现实时人脸识别,并绘制到SurfaceView上_氦客的博客-CSDN博客
  • NCNN+YOLO8.html" title=YOLO8>YOLO8相关


http://www.niftyadmin.cn/n/5074266.html

相关文章

Javascript 笔记:object

一部分object可以见&#xff1a;JavaScript 笔记 初识JavaScript&#xff08;变量&#xff09;_UQI-LIUWJ的博客-CSDN博客 1 in操作符 2 hasOwnProperty 3 获取一个object所拥有的所有property 不去原型链上找 4 定义data property

TS类中属性的封装

我们在如下的代码中&#xff0c;我们在类中设置属性&#xff0c;创建的对象可以随意修改自身的属性&#xff0c;对象中的属性可以任意被修改导致对象中的数据非常不安全。 // 创建一个Person类 class Person {name: string;age: number;constructor(name: string, age: number…

Linux 实践项目之论坛搭建

目录 一、思路 1、环境搭建&#xff08;lamp--Linux apache mysql php &#xff09; 2、关闭防火墙SELinux启动服务 3、将论坛源代码上传至/var/www/html路径下 4、设置MySQL数据库名称和密码 5、浏览器上搭建Discuz论坛 二、实操 1、安装 2、关闭防火墙SELinux启动服务…

8.2 JUC - 7.线程安全集合类概述

目录 一、遗留的线程安全集合二、使用 Collections 装饰的线程安全集合三、java.util.concurrent.* 包下的对象 线程安全集合类可以分为三大类&#xff1a; 一、遗留的线程安全集合 Hashtable &#xff0c; Vector 二、使用 Collections 装饰的线程安全集合 Collections.sy…

【轻松玩转MacOS】外部设备篇

引言 在开始之前&#xff0c;我们先来了解一下为什么要连接外部设备。想象一下&#xff0c;你正在享受MacOS带来的便捷和高效&#xff0c;突然需要打印一份文件&#xff0c;但你发现打印机无法连接&#xff1b;或者你需要将手机投屏到电脑上&#xff0c;却不知道该如何操作。这…

实现手机无人直播带货怎么操作,有哪些方法?

随着本地生活的火热&#xff0c;越来越多的商家开始想着通过直播带货的方式去拓客引流&#xff0c;毕竟&#xff0c;你不跟上时代&#xff0c;时代就有可能淘汰你。所以&#xff0c;不管你懂不懂短视频平台运营&#xff0c;不管你懂不懂怎么直播带货&#xff0c;但这条路早晚都…

10.8号作业

LED三盏灯的交替闪烁 .text .global _start _start: /* 1. led灯的初始化 *//* 1.1 使能GPIOE、DPIOF外设控制器的时钟 */ldr r0, 0x50000A28ldr r1, [r0]orr r1, r1, #(0x3 << 4)str r1, [r0]/* 1.2 设置PE10、PE8、PF10引脚为输出模式 */ldr r0, 0x50006000ldr r1, […

全面解析TCP协议(三次握手、四次挥手,头部报文格式)

简介 TCP&#xff08;Transmission Control Protocol&#xff09;是一种面向连接的、可靠的传输层协议&#xff0c;用于在计算机网络中传输数据&#xff0c;它提供了可靠的、有序的、基于字节流的传输&#xff0c;并通过拥塞控制机制来保证网络的稳定性&#xff0c;TCP协议可以…