【无人售货柜・RK+YOLO】篇 6:安卓端落地!RK3576 + 安卓系统,YOLO RKNN 模型实时推理保姆级教程

目录

[一、前置说明 & 新手扫盲](#一、前置说明 & 新手扫盲)

新手必守的红线

[二、第一步:环境 & 资源准备,新手零坑版](#二、第一步:环境 & 资源准备,新手零坑版)

三、第二步:创建安卓项目,配置环境

四、第三步:核心功能实现,全流程代码带注释

[模块 1:动态权限申请](#模块 1:动态权限申请)

[模块 2:RKNN 模型加载与初始化](#模块 2:RKNN 模型加载与初始化)

[模块 3:相机预览初始化(Camera2 API)](#模块 3:相机预览初始化(Camera2 API))

[模块 4:实时推理 + 结果解析 + 渲染](#模块 4:实时推理 + 结果解析 + 渲染)

[五、编译运行,部署到 RK3576 板子上](#五、编译运行,部署到 RK3576 板子上)

[新手必踩的坑 & 解决方案](#新手必踩的坑 & 解决方案)

最后说两句


大家好,我是黒漂技术佬。上一篇我们成功把训好的 YOLO 模型转换成了 RK3576 NPU 专用的 RKNN 模型,仿真验证精度、速度都完美达标。

这一篇,我们就直接落地到 RK3576 的安卓系统里,实现摄像头实时预览 + RKNN NPU 硬件加速推理 + 商品识别结果实时渲染的完整闭环,完全贴合无人售货柜的业务需求,新手跟着走,就能在你的售货柜主控上跑通实时商品识别。


一、前置说明 & 新手扫盲

先给新手打个底,说清楚前提要求和核心逻辑:

  1. 你已经有 RK3576 开发板 / 售货柜主控,刷好了安卓系统(安卓 10/11/12 都可以,推荐安卓 12,最稳)
  2. 你有基础的安卓开发能力,会用 Android Studio,能正常给 RK3576 板子装 APK
  3. 你已经有上一篇转好的best.rknn模型文件

【新手概念科普】RKNN 在安卓上怎么跑?RK3576 的安卓系统里,要调用 NPU 跑 RKNN 模型,必须用瑞芯微官方提供的RKNN Runtime Android SDK,它提供了 JNI 接口,让安卓 APP 能通过 Java/Kotlin 调用 NPU 的推理能力,实现硬件加速。

简单说:我们把 RKNN 模型、RKNN Runtime 的 so 库打包进 APK,APP 打开摄像头拿到预览帧,预处理后喂给 RKNN Runtime,调用 NPU 推理,拿到识别结果解析后渲染到屏幕上,就完成了整个实时识别流程。

新手必守的红线

  1. RKNN Runtime 版本必须和你转模型用的 RKNN-Toolkit2 版本完全一致!也就是 v1.6.0,版本不一致,必出现推理结果异常、闪退、甚至板子死机
  2. 安卓 minSdkVersion 必须≥21,targetSdkVersion 推荐 31,别用太高的版本,RK3576 的安卓系统兼容性不好
  3. 必须申请相机、存储权限,安卓 10 以上必须申请动态权限,不然相机打不开、模型加载不了
  4. 模型文件必须放到安卓的 assets 文件夹里,别放到其他地方,不然加载失败

二、第一步:环境 & 资源准备,新手零坑版

  1. 安装 Android Studio,官网下载最新稳定版,一键安装,配置好安卓 SDK(API Level 31)。

  2. 下载 RKNN-Toolkit2 v1.6.0 的安卓 SDK,瑞芯微官方 GitHub 地址:https://github.com/rockchip-linux/rknn-toolkit2,下载 v1.6.0 的 release 包,解压后在rknn-toolkit2/rknn_runtime/Android目录里,拿到我们需要的库文件:

    • 动态库:librknn_api.so(RK3576 是 64 位,只用 arm64-v8a 版本的就行)
    • 安卓封装类:RKNNRuntime.java(官方示例里有,我们直接用核心封装)
  3. 准备好你转好的best.rknn模型文件,还有你的商品类别名文件classes.txt,里面按训练时的顺序写好商品类别名,比如:

    plaintext

    复制代码
    kele_1
    xueli_2
    kuangquanshui_3
    shutiao_4
    kele_lingdu_5

三、第二步:创建安卓项目,配置环境

  1. 打开 Android Studio,创建新项目,选择 Empty Activity,语言选 Java(新手友好,官方示例都是 Java),包名比如com.shouhuogui.yolo,minSdkVersion 选 21,targetSdkVersion 选 31,点击创建。

  2. 配置 app 模块的build.gradle,确保支持 arm64-v8a 架构,打开 app 模块的 build.gradle,修改成以下内容:

    gradle

    复制代码
    android {
        compileSdk 31
    
        defaultConfig {
            applicationId "com.shouhuogui.yolo"
            minSdk 21
            targetSdk 31
            versionCode 1
            versionName "1.0"
    
            testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    
            // 新增:只支持arm64-v8a,RK3576专用
            ndk {
                abiFilters 'arm64-v8a'
            }
        }
    
        // 新增:确保so库能被正确打包
        sourceSets {
            main {
                jniLibs.srcDirs = ['src/main/jniLibs']
            }
        }
    
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            }
        }
        compileOptions {
            sourceCompatibility JavaVersion.VERSION_1_8
            targetCompatibility JavaVersion.VERSION_1_8
        }
    }
    
    dependencies {
        implementation 'androidx.appcompat:appcompat:1.6.1'
        implementation 'com.google.android.material:material:1.8.0'
        implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
        testImplementation 'junit:junit:4.13.2'
        androidTestImplementation 'androidx.test.ext:junit:1.1.5'
        androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
    }
  3. 导入 RKNN Runtime 库文件:

    • app/src/main目录下,创建jniLibs文件夹,再在里面创建arm64-v8a文件夹,把librknn_api.so放进去
    • app/src/main/java/你的包名目录下,创建RKNNRuntime.java文件,把官方的 NPU 推理封装类放进去,核心功能是加载模型、初始化 NPU、执行推理、释放资源
  4. 导入模型和类别文件:

    • app/src/main目录下,创建assets文件夹,把best.rknn模型文件、classes.txt类别文件放进去
  5. 申请权限:打开AndroidManifest.xml,在application标签前面,加上相机、存储权限:

    xml

    复制代码
    <!-- 相机权限 -->
    <uses-permission android:name="android.permission.CAMERA" />
    <!-- 存储权限 -->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <!-- 相机功能声明 -->
    <uses-feature android:name="android.hardware.camera" />
    <uses-feature android:name="android.hardware.camera.autofocus" />

到这里,项目的环境配置就全部完成了,接下来就是核心代码的编写。


四、第三步:核心功能实现,全流程代码带注释

我们把整个流程分成 4 个核心模块,每个模块都给你可直接复制的代码,还有详细的解释,全程贴合售货柜的业务需求。

模块 1:动态权限申请

安卓 6.0 以上,相机和存储权限必须动态申请,不然 APP 一打开就崩溃。我们把这部分代码写到MainActivity里:

java

运行

复制代码
public class MainActivity extends AppCompatActivity {
    // 需要申请的权限
    private static final String[] PERMISSIONS = {
            Manifest.permission.CAMERA,
            Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.WRITE_EXTERNAL_STORAGE
    };
    private static final int PERMISSION_REQUEST_CODE = 100;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 检查权限,没有就申请
        if (!checkPermissions()) {
            ActivityCompat.requestPermissions(this, PERMISSIONS, PERMISSION_REQUEST_CODE);
        } else {
            // 权限已获取,初始化相机和模型
            init();
        }
    }

    // 检查权限是否已获取
    private boolean checkPermissions() {
        for (String permission : PERMISSIONS) {
            if (ActivityCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
                return false;
            }
        }
        return true;
    }

    // 权限申请结果回调
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == PERMISSION_REQUEST_CODE) {
            boolean allGranted = true;
            for (int result : grantResults) {
                if (result != PackageManager.PERMISSION_GRANTED) {
                    allGranted = false;
                    break;
                }
            }
            if (allGranted) {
                init();
            } else {
                Toast.makeText(this, "必须获取所有权限才能使用APP", Toast.LENGTH_SHORT).show();
                finish();
            }
        }
    }

    // 初始化函数,后面写核心逻辑
    private void init() {
        // 1. 加载RKNN模型
        loadModel();
        // 2. 初始化相机预览
        initCamera();
    }
}

模块 2:RKNN 模型加载与初始化

这部分是核心,负责把 assets 里的 RKNN 模型加载到内存,初始化 NPU runtime,准备推理。先在 MainActivity 里定义全局变量:

java

运行

复制代码
// RKNN相关全局变量
private RKNNRuntime rknnRuntime;
private boolean isModelLoaded = false;
// 模型输入尺寸,和训练时一致
private static final int INPUT_WIDTH = 640;
private static final int INPUT_HEIGHT = 640;
// 商品类别数,改成你自己的数量
private static final int NUM_CLASSES = 10;
// 类别名列表
private List<String> classNames = new ArrayList<>();

然后写loadModel函数,加载模型和类别名:

java

运行

复制代码
private void loadModel() {
    new Thread(() -> {
        try {
            // 1. 加载类别名
            InputStream classStream = getAssets().open("classes.txt");
            BufferedReader reader = new BufferedReader(new InputStreamReader(classStream));
            String line;
            while ((line = reader.readLine()) != null) {
                classNames.add(line.trim());
            }
            reader.close();
            classStream.close();

            // 2. 加载RKNN模型文件
            InputStream modelStream = getAssets().open("best.rknn");
            byte[] modelData = new byte[modelStream.available()];
            modelStream.read(modelData);
            modelStream.close();

            // 3. 初始化RKNN Runtime,加载模型
            rknnRuntime = new RKNNRuntime();
            int ret = rknnRuntime.loadModel(modelData);
            if (ret != 0) {
                throw new Exception("加载RKNN模型失败,错误码:" + ret);
            }

            // 4. 初始化NPU运行环境
            ret = rknnRuntime.initRuntime();
            if (ret != 0) {
                throw new Exception("初始化RKNN Runtime失败,错误码:" + ret);
            }

            isModelLoaded = true;
            runOnUiThread(() -> Toast.makeText(this, "模型加载成功!", Toast.LENGTH_SHORT).show());

        } catch (Exception e) {
            e.printStackTrace();
            runOnUiThread(() -> Toast.makeText(this, "模型加载失败:" + e.getMessage(), Toast.LENGTH_SHORT).show());
        }
    }).start();
}

模块 3:相机预览初始化(Camera2 API)

安卓推荐用 Camera2 API,兼容性好,能拿到实时的预览帧数据。我们用 TextureView 来显示预览,先改activity_main.xml布局文件:

xml

复制代码
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- 相机预览控件 -->
    <TextureView
        android:id="@+id/textureView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <!-- 识别结果显示控件 -->
    <TextView
        android:id="@+id/tv_result"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:background="#80000000"
        android:textColor="#FFFFFF"
        android:textSize="16sp"
        android:padding="10dp" />

</RelativeLayout>

回到 MainActivity,定义相机相关的全局变量:

java

运行

复制代码
// 相机相关全局变量
private TextureView textureView;
private CameraDevice cameraDevice;
private CameraCaptureSession captureSession;
private static final int CAMERA_ID = 0; // 售货柜一般用后置摄像头

然后写initCamera函数,初始化相机预览:

java

运行

复制代码
private void initCamera() {
    textureView = findViewById(R.id.textureView);
    // 监听TextureView准备完成
    textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
        @Override
        public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) {
            // 打开相机
            openCamera();
        }

        @Override
        public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, int height) {}

        @Override
        public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {
            // 释放相机资源
            releaseCamera();
            return true;
        }

        @Override
        public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {}
    });
}

// 打开相机
private void openCamera() {
    CameraManager cameraManager = (CameraManager) getSystemService(CAMERA_SERVICE);
    try {
        // 检查权限
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
            return;
        }
        // 打开相机
        cameraManager.openCamera(String.valueOf(CAMERA_ID), new CameraDevice.StateCallback() {
            @Override
            public void onOpened(@NonNull CameraDevice camera) {
                cameraDevice = camera;
                // 开始预览
                startPreview();
            }

            @Override
            public void onDisconnected(@NonNull CameraDevice camera) {
                camera.close();
                cameraDevice = null;
            }

            @Override
            public void onError(@NonNull CameraDevice camera, int error) {
                camera.close();
                cameraDevice = null;
            }
        }, null);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

// 开始相机预览
private void startPreview() {
    try {
        SurfaceTexture surfaceTexture = textureView.getSurfaceTexture();
        surfaceTexture.setDefaultBufferSize(1920, 1080); // 预览分辨率1080P
        Surface surface = new Surface(surfaceTexture);

        // 创建预览请求
        final CaptureRequest.Builder previewRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
        previewRequestBuilder.addTarget(surface);

        // 创建相机捕获会话
        cameraDevice.createCaptureSession(Collections.singletonList(surface), new CameraCaptureSession.StateCallback() {
            @Override
            public void onConfigured(@NonNull CameraCaptureSession session) {
                captureSession = session;
                previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
                previewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);

                // 无限循环预览
                try {
                    session.setRepeatingRequest(previewRequestBuilder.build(), null, null);
                    // 开启推理线程,每隔100ms取一帧做推理
                    startInferenceThread();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void onConfigureFailed(@NonNull CameraCaptureSession session) {}
        }, null);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

// 释放相机资源
private void releaseCamera() {
    if (captureSession != null) {
        captureSession.stopRepeating();
        captureSession.abortCaptures();
        captureSession.close();
        captureSession = null;
    }
    if (cameraDevice != null) {
        cameraDevice.close();
        cameraDevice = null;
    }
}

模块 4:实时推理 + 结果解析 + 渲染

这是最核心的部分,负责从 TextureView 拿到预览帧,预处理成模型需要的格式,喂给 RKNN NPU 推理,解析结果做 NMS 去重,然后把识别结果显示到屏幕上,完全适配售货柜的快照识别需求。

先定义推理相关的全局变量:

java

运行

复制代码
// 推理线程相关
private Thread inferenceThread;
private boolean isInferencing = false;
// 识别结果控件
private TextView tvResult;
// 置信度阈值和NMS阈值,和训练时一致
private static final float CONF_THRESHOLD = 0.5f;
private static final float NMS_THRESHOLD = 0.45f;

然后写startInferenceThread函数,开启推理线程,循环取帧推理:

java

运行

复制代码
private void startInferenceThread() {
    tvResult = findViewById(R.id.tv_result);
    isInferencing = true;
    inferenceThread = new Thread(() -> {
        while (isInferencing && isModelLoaded) {
            try {
                // 1. 从TextureView拿到预览帧Bitmap
                Bitmap bitmap = textureView.getBitmap();
                if (bitmap == null) {
                    Thread.sleep(100);
                    continue;
                }

                // 2. 图片预处理:缩放到640×640,转成RGB格式
                Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, INPUT_WIDTH, INPUT_HEIGHT, true);
                byte[] inputData = bitmapToInputData(resizedBitmap);

                // 3. RKNN NPU推理
                long startTime = System.currentTimeMillis();
                float[][] outputs = rknnRuntime.runModel(inputData);
                long inferenceTime = System.currentTimeMillis() - startTime;

                // 4. 解析推理结果,做NMS去重
                List<DetectionResult> results = parseOutputs(outputs, bitmap.getWidth(), bitmap.getHeight());

                // 5. 把结果渲染到屏幕上
                runOnUiThread(() -> {
                    // 绘制识别框和类别名
                    Canvas canvas = textureView.lockCanvas();
                    if (canvas != null) {
                        Paint paint = new Paint();
                        paint.setColor(Color.RED);
                        paint.setStyle(Paint.Style.STROKE);
                        paint.setStrokeWidth(3);
                        paint.setTextSize(40);

                        for (DetectionResult result : results) {
                            // 画识别框
                            paint.setColor(Color.RED);
                            paint.setStyle(Paint.Style.STROKE);
                            canvas.drawRect(result.rect, paint);
                            // 画类别名和置信度
                            paint.setStyle(Paint.Style.FILL);
                            paint.setColor(Color.WHITE);
                            String text = classNames.get(result.classId) + " " + String.format("%.2f", result.confidence);
                            canvas.drawText(text, result.rect.left, result.rect.top - 10, paint);
                        }
                        textureView.unlockCanvasAndPost(canvas);
                    }

                    // 更新结果文本
                    StringBuilder resultText = new StringBuilder();
                    resultText.append("推理耗时:").append(inferenceTime).append("ms | 识别到商品:");
                    for (DetectionResult result : results) {
                        resultText.append(classNames.get(result.classId)).append(" ");
                    }
                    tvResult.setText(resultText.toString());
                });

                // 释放Bitmap资源,避免内存泄漏
                bitmap.recycle();
                resizedBitmap.recycle();

                // 控制推理帧率,10帧/秒足够售货柜场景用,避免占用太多NPU资源
                Thread.sleep(100);

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    });
    inferenceThread.start();
}

// Bitmap转模型输入的byte数组,预处理
private byte[] bitmapToInputData(Bitmap bitmap) {
    int width = bitmap.getWidth();
    int height = bitmap.getHeight();
    int[] pixels = new int[width * height];
    bitmap.getPixels(pixels, 0, width, 0, 0, width, height);

    byte[] data = new byte[width * height * 3];
    int index = 0;
    // 转RGB格式,RKNN输入要求是NHWC格式,RGB通道
    for (int i = 0; i < height; i++) {
        for (int j = 0; j < width; j++) {
            int pixel = pixels[i * width + j];
            data[index++] = (byte) ((pixel >> 16) & 0xFF); // R
            data[index++] = (byte) ((pixel >> 8) & 0xFF);  // G
            data[index++] = (byte) (pixel & 0xFF);         // B
        }
    }
    return data;
}

// 解析模型输出,做NMS去重
private List<DetectionResult> parseOutputs(float[][] outputs, int imgWidth, int imgHeight) {
    List<DetectionResult> results = new ArrayList<>();
    float[] output = outputs[0];
    int numBoxes = output.length / (5 + NUM_CLASSES);

    // 缩放比例,把640×640的框映射回原始预览分辨率
    float xScale = (float) imgWidth / INPUT_WIDTH;
    float yScale = (float) imgHeight / INPUT_HEIGHT;

    // 遍历所有预测框
    for (int i = 0; i < numBoxes; i++) {
        int offset = i * (5 + NUM_CLASSES);
        float confidence = output[offset + 4];

        // 过滤置信度低的框
        if (confidence < CONF_THRESHOLD) {
            continue;
        }

        // 找到置信度最高的类别
        int classId = 0;
        float maxClassConf = 0;
        for (int j = 0; j < NUM_CLASSES; j++) {
            float classConf = output[offset + 5 + j];
            if (classConf > maxClassConf) {
                maxClassConf = classConf;
                classId = j;
            }
        }

        // 最终置信度 = 目标置信度 × 类别置信度
        float finalConf = confidence * maxClassConf;
        if (finalConf < CONF_THRESHOLD) {
            continue;
        }

        // 解析框的坐标,cx, cy, w, h → left, top, right, bottom
        float cx = output[offset] * xScale;
        float cy = output[offset + 1] * yScale;
        float w = output[offset + 2] * xScale;
        float h = output[offset + 3] * yScale;

        RectF rect = new RectF(
                cx - w / 2,
                cy - h / 2,
                cx + w / 2,
                cy + h / 2
        );

        results.add(new DetectionResult(classId, finalConf, rect));
    }

    // 执行NMS非极大值抑制,去重
    return nms(results);
}

// NMS非极大值抑制
private List<DetectionResult> nms(List<DetectionResult> results) {
    List<DetectionResult> finalResults = new ArrayList<>();
    // 按置信度从高到低排序
    results.sort((a, b) -> Float.compare(b.confidence, a.confidence));

    boolean[] suppressed = new boolean[results.size()];
    for (int i = 0; i < results.size(); i++) {
        if (suppressed[i]) continue;
        DetectionResult result = results.get(i);
        finalResults.add(result);

        // 和剩下的框做IOU比对
        for (int j = i + 1; j < results.size(); j++) {
            if (suppressed[j]) continue;
            DetectionResult other = results.get(j);
            // 同一个类别才做NMS
            if (other.classId != result.classId) continue;
            float iou = calculateIOU(result.rect, other.rect);
            if (iou > NMS_THRESHOLD) {
                suppressed[j] = true;
            }
        }
    }
    return finalResults;
}

// 计算两个框的IOU
private float calculateIOU(RectF a, RectF b) {
    float intersectionLeft = Math.max(a.left, b.left);
    float intersectionTop = Math.max(a.top, b.top);
    float intersectionRight = Math.min(a.right, b.right);
    float intersectionBottom = Math.min(a.bottom, b.bottom);

    float intersectionArea = Math.max(0, intersectionRight - intersectionLeft) * Math.max(0, intersectionBottom - intersectionTop);
    float unionArea = (a.right - a.left) * (a.bottom - a.top) + (b.right - b.left) * (b.bottom - b.top) - intersectionArea;

    return unionArea == 0 ? 0 : intersectionArea / unionArea;
}

// 识别结果实体类
private static class DetectionResult {
    int classId;
    float confidence;
    RectF rect;

    public DetectionResult(int classId, float confidence, RectF rect) {
        this.classId = classId;
        this.confidence = confidence;
        this.rect = rect;
    }
}

最后,别忘了在 Activity 销毁的时候,释放所有资源,避免内存泄漏、NPU 资源占用:

java

运行

复制代码
@Override
protected void onDestroy() {
    super.onDestroy();
    // 停止推理线程
    isInferencing = false;
    if (inferenceThread != null) {
        inferenceThread.interrupt();
        inferenceThread = null;
    }
    // 释放相机资源
    releaseCamera();
    // 释放RKNN资源
    if (rknnRuntime != null) {
        rknnRuntime.release();
        rknnRuntime = null;
    }
}

五、编译运行,部署到 RK3576 板子上

  1. 把 RK3576 开发板 / 售货柜主控用 USB 线连接到电脑,打开板子的开发者选项和 USB 调试
  2. 在 Android Studio 里,选择连接的设备,点击运行按钮,APP 就会自动安装到板子上
  3. 打开 APP,授予权限,就能看到相机实时预览,同时屏幕上会实时渲染商品识别的框、类别名、置信度,还有推理耗时

正常情况下,RK3576 的 NPU 推理耗时在 20~30ms,完全能做到实时识别,精度和电脑上的 ONNX 模型基本一致,完美满足无人售货柜的需求。


新手必踩的坑 & 解决方案

  1. 模型加载失败:检查 RKNN Runtime 版本和转模型的版本是否一致,检查模型文件是否放到了 assets 文件夹,检查 so 库是否正确导入
  2. 推理结果全错:检查预处理是否正确,RKNN 的输入是 NHWC 格式、RGB 通道,归一化是否和训练时一致,检查类别数是否正确
  3. 推理速度慢:检查是否真的用了 NPU 推理,有没有用 CPU 跑,检查模型是不是 INT8 量化的,有没有开启优化级别
  4. APP 闪退:检查权限是否申请,检查有没有在主线程做耗时操作(模型加载和推理必须放到子线程)
  5. 识别框位置不对:检查坐标缩放比例是否正确,有没有把 640×640 的框正确映射回原始预览分辨率

最后说两句

到这里,恭喜你!你已经完成了从 YOLO 模型训练→RKNN 转换→RK3576 安卓端实时推理的完整闭环,你的无人售货柜已经有了核心的商品识别能力。

下一卷,我们就把这个识别能力,和无人售货柜的业务逻辑结合起来,实现开门前基线快照→关门后商品比对→SKU 数量统计→自动结算的完整业务闭环,让你的识别能力真正变成能商用的售货柜系统。

相关推荐
ZPC82101 小时前
【无标题】
人工智能·pytorch·算法·机器人
人工智能AI技术1 小时前
华为AgentArts公测|企业级AI智能体开发与openJiuwen适配指南
人工智能
lovingsoft1 小时前
Cursor IDE 设置项功能介绍
人工智能
北京软秦科技有限公司1 小时前
AI审核如何守护游乐设施安全底线?IACheck成为检测报告智能审核新助手
人工智能·安全
研究点啥好呢1 小时前
3月19日GitHub热门项目推荐|OpenClaw棋逢对手
人工智能·ai·开源·github
Dfreedom.2 小时前
机器学习经典算法全景解析与演进脉络(无监督学习篇)
人工智能·学习·算法·机器学习·无监督学习
大傻^2 小时前
Spring AI Alibaba 文档智能处理:PDF、Markdown知识入库全链路
java·人工智能·spring·pdf·知识图谱·springai·springaialibaba
ar01232 小时前
AR远程专家指导:赋能工业、精密制造业
人工智能·ar
大傻^2 小时前
Spring AI Alibaba Deep Research:自动化深度调研与报告生成
人工智能·spring·自动化