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

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

扫描二维码关注公众号,回复: 17214384 查看本文章
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 人像识别 系列文章

猜你喜欢

转载自blog.csdn.net/EthanCo/article/details/133382921