1. 前言
上篇文章,我们已经将人像分割的ncnn-android-yolov8-seg
项目运行起来了,后续文章我们会抽取出Demo
中的核心代码,在自己的项目中,来接入人体识别和人像分割功能。
先来看下效果,整个图像的是相机的原图,左上角部分,是我们进行人像识别、人像分割后,处理得到的图像 (未做镜像处理,所以暂时和原图左右是相反的)
那我们要怎么在自己的项目中,实现人像分割功能呢 ?
我们看ncnn-android-yolov8-seg
的源码,可以发现, 这个项目里的相机也是用c/c++
,但是在我们项目中,使用的Java
层的Camera API
来实现的。要想在自己项目里集成ncnn
,那就需要把ncnn-android-yolov8-seg
里的核心代码给抽离,然后对接到Java
的Camera 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 gpudelete 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
是个模型簇,从小到大包括:yolov8n
、yolov8s
、yolov8m
、yolov8l
、yolov8x
等。
通常yolov8n
速度最快,具体见下表
2.1.2 cpugpu : 使用CPU或GPU
接着来看Java_com_tencent_yolov8ncnn_Yolov8Ncnn_loadModel
方法,
还有一个参数cpugpu
是用来决定使用CPU
还是GPU
,0
为CPU
,1
为GPU
。
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中的相机操作,都是通过NDK
的Camera 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 人体检测
人体检测是和NDK
的Camera
相关联的,在相机回调的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相关
- Android 导入ncnn-android-yolov8-seg : 实现人体识别和人像分割-CSDN博客
- Android ncnn-android-yolov8-seg源码解析 : 实现人像分割-CSDN博客
- Android 在自己的项目接入OpenCV+YOLOv8+NCNN,实现人像分割-CSDN博客