关于YOLO8学习(六)安卓部署ncnn模型--图片检测

在这里插入图片描述

教学视频地址

B站

前文

关于YOLO8学习(一)环境搭建,官方检测模型部署到手机
关于YOLO8学习(二)数据集收集,处理
关于YOLO8学习(三)训练自定义的数据集
关于YOLO8学习(四)模型转换为ncnn
关于YOLO8学习(五)安卓部署ncnn模型–视频检测

简介

前文第五章,讲述了部署自定义模型后,进行安卓视频检测人脸。
本文将会讲解:
(1)使用前文可以运行的demo,讲解如何使用模型对图片进行识别,并且标注,自定义返回结果

注意!!文末有安卓关键代码源码

效果图

在这里插入图片描述

开发环境

win10、python 3.11、cmake、pytorch2.0.1+cu117、pycharm、ultralytics==8.0.134
要特别注意,不要升级到python312,目前onnx还没支持到312,所以转换了,会导致转换模型失效。
对于上述环境如何配置,请看我之前的文章。
安卓:as4+,ndk21+,jdk11

开发过程

本次实现Yolo8的安卓图片检测,对于新手来说,应该会十分坎坷,因为整个流程,涉及到了java,安卓,jni,python这几个语言的糅合,话不多说,直接上干活。

先说思路,图片检测的原理,就是通过传入的图片对象,然后使用ncnn自带的一些方法,检测出目标的位置,然后返回给上层坐标系,标签等信息,在通过安卓canvas进行绘制。

(1)对于“检测出目标的位置”这个流程,是不是时曾相识?没错,前面的文章中,我们已经实现了视频的检测,细心一点的朋友可能会发现,源码中的yolo.cpp类,detect和draw方法,不就是检测和绘制吗?正确
(2)对于返回给上层的信息,是不是也可以通过jni定义好,然后c层直接封装,java曾直接解析,就可以了?

关键点说完了,下面直接看关键的代码:
在这里插入图片描述
博主先在Yolov8Ncnn.java文件中,定义了一个jni方法,名字为

    public native List<HashMap<String, String>> detectedStaticPic(Bitmap bitmap);

这个方法,就是要求c层,返回一个列表,列表每一项中,都是一个key,value组成的map,那么这个map,就可以放下我们的x,y,label等信息了。然后再看输入对象是一个bitmap。到此位置,jni层的java类已经定义完成。

接着在yolov8ncnn.cpp文件中,直接创建对应的方法,具体代码如下:


/**
 * 檢測圖片
 * */
extern "C"
JNIEXPORT jobject JNICALL
Java_com_north_light_yolov8ncnn_Yolov8Ncnn_detectedStaticPic(JNIEnv *env, jobject thiz,
                                                         jobject bitmap) {
    return g_yolo->detected_static_pic(env, thiz, bitmap);
}

可以看到,这里直接调用回yolo.cpp的方法,同样,按照c语言的习惯,我们直接在yolo.h和yolo.cpp定义好即可。具体代码如下:

yolo.h
 jobject detected_static_pic(JNIEnv *pEnv, jobject pJobject, jobject pJobject1);
yolo.cpp
jobject Yolo::detected_static_pic(JNIEnv *env, jobject thiz, jobject bitmap)

定义好了以后,所有有关的检测逻辑,都在yolo.cpp里面编写,最后返回一个List<HashMap<String,String>>对象给上层即可。


至此,我们已经搭建完成整个开发骨架了,接下来,就是具体业务的编写:
这里先提供一个思路,后面会放出所有的代码:

实现方法,函数中收到的是一个bitmap,而ncnn不能直接处理bitmap,而是处理rgb的mat,所以,这里就涉及到一个bitmap转为mat(rgb)的逻辑。再者,对于ncnn处理完成后,要返回刚才定义的List<HashMap<String,String>>对象给上层,然后再在安卓端使用canvas进行边框绘制。核心代码如下:
yolo.cpp
// 检测静态图片
jobject Yolo::detected_static_pic(JNIEnv *env, jobject thiz, jobject bitmap) {

    //结果数据
    jclass arrayListClass = env->FindClass("java/util/ArrayList");
    jmethodID arrayListInit = env->GetMethodID(arrayListClass, "<init>", "()V");
    jobject arrayListObj = env->NewObject(arrayListClass, arrayListInit);

    // 获取Bitmap信息
    AndroidBitmapInfo info;
    if (AndroidBitmap_getInfo(env, bitmap, &info) < 0) {
        return arrayListObj;
    }
    if (info.format != ANDROID_BITMAP_FORMAT_RGB_565 &&
        info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) {
        return arrayListObj;
    }

    // 锁定Bitmap像素
    void *pixels = nullptr;
    if (AndroidBitmap_lockPixels(env, bitmap, &pixels) < 0) {
        return arrayListObj;
    }

    // 计算RGB数据大小
    size_t pixelCount = info.width * info.height;
    size_t rgbDataSize = pixelCount * 3; // 每个像素3个字节(RGB)
    std::vector<unsigned char> rgbData(rgbDataSize);

    // 提取RGB值
    uint8_t *p = static_cast<uint8_t *>(pixels);
    for (size_t i = 0; i < pixelCount; ++i) {
        if (info.format == ANDROID_BITMAP_FORMAT_RGB_565) {
            // 对于RGB_565格式,需要转换为RGB888
            uint16_t pixel = *reinterpret_cast<uint16_t *>(p);
            rgbData[i * 3] = ((pixel >> 8) & 0xF8) | ((pixel >> 13) & 0x07); // R
            rgbData[i * 3 + 1] = ((pixel >> 3) & 0xF8) | ((pixel >> 11) & 0x07); // G
            rgbData[i * 3 + 2] = ((pixel << 3) & 0xF8) | ((pixel >> 5) & 0x07); // B
            p += 2; // RGB_565每像素2字节
        } else if (info.format == ANDROID_BITMAP_FORMAT_RGBA_8888) {
            // 直接提取RGB分量,忽略Alpha
            rgbData[i * 3] = p[0]; // R
            rgbData[i * 3 + 1] = p[1]; // G
            rgbData[i * 3 + 2] = p[2]; // B
            p += 4; // ARGB_8888每像素4字节
        }
    }
    int width = info.width;
    int height = info.height;
    // 解锁Bitmap像素
    AndroidBitmap_unlockPixels(env, bitmap);

    // 确保数据量与宽度、高度匹配
    if (rgbData.size() != static_cast<size_t>(width * height * 3)) {
        return arrayListObj;
    }
    cv::Mat mat(height, width, CV_8UC3, (void *) rgbData.data());
    //完成了数据的转换,开始检测
    std::vector<Object> objects;
    detect(mat, objects);
    draw(mat,objects);

//    __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "objects size %d", objects.size());

    if (objects.size() <= 0) {
        return arrayListObj;
    }

    //上述步骤,可以检测出具体的框
    //组装数据

    jmethodID addMethod = env->GetMethodID(arrayListClass, "add", "(Ljava/lang/Object;)Z");

    if (objects.size() > 0) {
        for (int i = 0; i < objects.size(); i++) {
            int label_pos = objects[i].label;

            jclass hashMapClass = env->FindClass("java/util/HashMap");
            jmethodID hashMapInit = env->GetMethodID(hashMapClass, "<init>", "()V");
            jobject hashMapObj = env->NewObject(hashMapClass, hashMapInit);

            jstring xKeyStr = charToString(env, "x");
            jstring xValue = intToString(env, objects[i].rect.x);

            jstring yKeyStr = charToString(env, "y");
            jstring yValue = intToString(env, objects[i].rect.y);

            jstring wKeyStr = charToString(env, "w");
            jstring wValue = intToString(env, objects[i].rect.width);

            jstring hKeyStr = charToString(env, "h");
            jstring hValue = intToString(env, objects[i].rect.height);

            jstring labelKeyStr = charToString(env, "label");
            jstring labelValue = intToString(env, objects[i].label);

            jstring labelStrKeyStr = charToString(env, "label_str");
            jstring labelStrValue = charToString(env, class_names[label_pos]);

            jstring probKeyStr = charToString(env, "prob");
            jstring probValue = floatToString(env, objects[i].prob);

            putKeyValueToMap(env, hashMapObj, hashMapClass, xKeyStr, xValue);
            putKeyValueToMap(env, hashMapObj, hashMapClass, yKeyStr, yValue);
            putKeyValueToMap(env, hashMapObj, hashMapClass, wKeyStr, wValue);
            putKeyValueToMap(env, hashMapObj, hashMapClass, hKeyStr, hValue);
            putKeyValueToMap(env, hashMapObj, hashMapClass, labelKeyStr, labelValue);
            putKeyValueToMap(env, hashMapObj, hashMapClass, labelStrKeyStr, labelStrValue);
            putKeyValueToMap(env, hashMapObj, hashMapClass, probKeyStr, probValue);

            env->CallBooleanMethod(arrayListObj, addMethod, hashMapObj);

            env->DeleteLocalRef(hashMapObj);

            env->DeleteLocalRef(xKeyStr);
            env->DeleteLocalRef(xValue);
            env->DeleteLocalRef(yKeyStr);
            env->DeleteLocalRef(yValue);
            env->DeleteLocalRef(wKeyStr);
            env->DeleteLocalRef(wValue);
            env->DeleteLocalRef(hKeyStr);
            env->DeleteLocalRef(hValue);
            env->DeleteLocalRef(labelKeyStr);
            env->DeleteLocalRef(labelValue);
            env->DeleteLocalRef(labelStrKeyStr);
            env->DeleteLocalRef(labelStrValue);
            env->DeleteLocalRef(probKeyStr);
            env->DeleteLocalRef(probValue);
        }
    }

    return arrayListObj;
}



Yolov8Ncnn.java
// Tencent is pleased to support the open source community by making ncnn available.
//
// Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
//
// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except
// in compliance with the License. You may obtain a copy of the License at
//
// https://opensource.org/licenses/BSD-3-Clause
//
// Unless required by applicable law or agreed to in writing, software distributed
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.

package com.north.light.yolov8ncnn;

import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.Surface;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

public class Yolov8Ncnn implements Serializable {

    public native boolean loadModel(AssetManager mgr, int modelid, int cpugpu);

    /**
     * @param facing 0前置 1后置
     */
    public native boolean openYoloCamera(int facing);

    public native boolean closeYoloCamera();

    public native boolean setOutputWindow(Surface surface);

    public native List<HashMap<String, String>> detectedStaticPic(Bitmap bitmap);

    static {
        System.loadLibrary("yolov8ncnn");
    }

    private static final int[] colors = new int[]{
            Color.rgb(54, 67, 244),
            Color.rgb(99, 30, 233),
            Color.rgb(176, 39, 156),
            Color.rgb(183, 58, 103),
            Color.rgb(181, 81, 63),
            Color.rgb(243, 150, 33),
            Color.rgb(244, 169, 3),
            Color.rgb(212, 188, 0),
            Color.rgb(136, 150, 0),
            Color.rgb(80, 175, 76),
            Color.rgb(74, 195, 139),
            Color.rgb(57, 220, 205),
            Color.rgb(59, 235, 255),
            Color.rgb(7, 193, 255),
            Color.rgb(0, 152, 255),
            Color.rgb(34, 87, 255),
            Color.rgb(72, 85, 121),
            Color.rgb(158, 158, 158),
            Color.rgb(139, 125, 96)
    };

    public List<TrainDetectedObject> trainDataToDetectedObject(List<HashMap<String, String>> data) {
        if (data == null || data.size() == 0) {
            return new ArrayList<>();
        }
        List<TrainDetectedObject> result = new ArrayList<>();
        for (int i = 0; i < data.size(); i++) {
            HashMap<String, String> item = data.get(i);
            if (item.containsKey("label") && item.containsKey("label_str") &&
                    item.containsKey("prob") && item.containsKey("x") &&
                    item.containsKey("y") && item.containsKey("w") &&
                    item.containsKey("h")) {
                TrainDetectedObject obj = new TrainDetectedObject();
                obj.label = item.get("label");
                obj.labelName = item.get("label_str");
                obj.prob = Float.parseFloat(item.get("prob"));
                obj.x = Float.parseFloat(item.get("x"));
                obj.y = Float.parseFloat(item.get("y"));
                obj.w = Float.parseFloat(item.get("w"));
                obj.h = Float.parseFloat(item.get("h"));
                result.add(obj);
            }
        }
        return result;
    }


    public Bitmap showObjects(List<TrainDetectedObject> objects, Bitmap orgBitmap) {
        if (objects == null) {
            return null;
        }
        // draw objects on bitmap
        Bitmap rgba = orgBitmap.copy(Bitmap.Config.ARGB_8888, true);

        Canvas canvas = new Canvas(rgba);

        Paint paint = new Paint();
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(4);

        Paint textbgpaint = new Paint();
        textbgpaint.setColor(Color.WHITE);
        textbgpaint.setStyle(Paint.Style.FILL);

        Paint textpaint = new Paint();
        textpaint.setColor(Color.BLACK);
        textpaint.setTextSize(26);
        textpaint.setTextAlign(Paint.Align.LEFT);

        for (int i = 0; i < objects.size(); i++) {
            paint.setColor(colors[i % 19]);
            canvas.drawRect(objects.get(i).x, objects.get(i).y, objects.get(i).x + objects.get(i).w, objects.get(i).y + objects.get(i).h, paint);
            {
                String text = objects.get(i).labelName + " = " + String.format("%.1f", objects.get(i).prob * 100) + "%";
                float text_width = textpaint.measureText(text);
                float text_height = -textpaint.ascent() + textpaint.descent();

                float x = objects.get(i).x;
                float y = objects.get(i).y - text_height;
                if (y < 0)
                    y = 0;
                if (x + text_width > rgba.getWidth())
                    x = rgba.getWidth() - text_width;

                canvas.drawRect(x, y, x + text_width, y + text_height, textbgpaint);

                canvas.drawText(text, x, y - textpaint.ascent(), textpaint);
            }
        }
        return rgba;
    }
}

···

通过以上的方式,就可以实现jni层检测,安卓层绘制边框的逻辑了。

### 更详细的安卓代码,如下:
## 安卓demo源码:关注并回复 “yolo8检测人脸图片检测代码”即可
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/4d865e2d13444726b3ea24a9acb55897.jpeg)

that's all--------------------------------------------------------------------------------------------
  • 7
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值