当前位置: 首页 > article >正文

将pytroch模型转为paddlelite模型并集成到android程序中

这两天尝试把pytorch构建的yolov5模型和opencv集成到安卓程序中,做个笔记记录一下流程。

使用anaconda新建环境

python版本:3.9

需要下载的库

pytorch和paddlepaddle支持(以下为具体下载版本与机器有关)

pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu126
conda install paddlepaddle-gpu==3.0.0b1 paddlepaddle-cuda=12.3 -c paddle -c nvidia

p.s.安装库时pytorch和paddlepaddle如果一个是cpu版本一个是gpu版本在可选的paddlepaddle转paddlelite步骤中会报错。

paddlepaddle转换支持

pip install x2paddle paddlelite requests pandas

python程序执行所需的库

pip install opencv-python

python转换程序

pytorch转换

import torch
import cv2
from torchvision.ops import nms
from torchvision import transforms
import numpy as np
import torch.nn as nn

# paddlelite不支持silu算子,使用x * sigmoid(x)替换
class replace_lay(nn.Module):
    def __init__(self):
        super(replace_lay, self).__init__()
        self.sigmoid_lay = nn.Sigmoid()

    def forward(self, x):
        x = x * self.sigmoid_lay(x)
        return x

# 定义一个函数,递归遍历模型并替换所有 SiLU 为 ReLU
def replace_silu_with_relu(model):
    # 收集需要替换的层信息
    layers_to_replace = []

    def collect_layers(module, prefix=""):
        for name, sub_module in module.named_children():
            full_name = f"{prefix}.{name}" if prefix else name
            if isinstance(sub_module, nn.SiLU):  # 如果当前模块是 SiLU
                layers_to_replace.append((module, name))  # 记录父模块和层名
            else:
                # 如果当前模块有子模块,递归调用
                collect_layers(sub_module, full_name)

    # 第一步:收集需要替换的层
    collect_layers(model)

    # 第二步:批量替换
    for parent_module, layer_name in layers_to_replace:
        setattr(parent_module, layer_name, replace_lay())

obj_type = [
    'person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train', 'truck', 'boat',
    'traffic light', 'fire hydrant', 'stop sign', 'parking meter', 'bench', 'bird', 'cat',
    'dog', 'horse', 'sheep', 'cow', 'elephant', 'bear', 'zebra', 'giraffe', 'backpack',
    'umbrella', 'handbag', 'tie', 'suitcase', 'frisbee', 'skis', 'snowboard', 'sports ball',
    'kite', 'baseball bat', 'baseball glove', 'skateboard', 'surfboard', 'tennis racket',
    'bottle', 'wine glass', 'cup', 'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple',
    'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza', 'donut', 'cake', 'chair',
    'couch', 'potted plant', 'bed', 'dining table', 'toilet', 'tv', 'laptop', 'mouse', 'remote',
    'keyboard', 'cell phone', 'microwave', 'oven', 'toaster', 'sink', 'refrigerator', 'book',
    'clock', 'vase', 'scissors', 'teddy bear', 'hair drier', 'toothbrush'
]

target_index = obj_type.index('dog')

origin_img = cv2.imread('img/dog.jpg')
scale_matrix = np.array([origin_img.shape[1] / 224, origin_img.shape[0] / 224, origin_img.shape[1] / 224, origin_img.shape[0] / 224])

img_to_tensor_transform = transforms.Compose([
    # cv2转PLT
    transforms.ToPILImage(),
    # 尺寸转为224*224
    transforms.Resize((224, 224)),
    # PLT转Tensor
    transforms.ToTensor(),
    # 归一化
    # transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])
x = img_to_tensor_transform(origin_img).unsqueeze(0)

print(x)


model = torch.hub.load('ultralytics/yolov5', 'yolov5n', pretrained=True)
replace_silu_with_relu(model)

model.eval()
result = model(x).cpu().detach().numpy()[0]
print(result.shape)

confidence = result[:, 4]
scores = confidence * result[:, 5 + target_index]
mask = scores > 0.5
boxes = result[mask][:, :4]
boxes = torch.tensor(boxes)
scores = torch.tensor(scores[mask])
boxes_indices = nms(boxes, scores, 0.5)
boxes = (boxes.numpy()[boxes_indices] * scale_matrix)
boxes = boxes.astype(int)
if len(boxes_indices) == 1:
    boxes = [boxes]
for box in boxes:
    x, y, w, h = box
    w = int(w / 2)
    h = int(h / 2)
    print(x, y, w, h)
    cv2.rectangle(origin_img, (x - w, y - h), (x + w, y + h), (0, 255, 0), 2)
cv2.imshow('result', origin_img)
cv2.waitKey(0)

# cv2.resize(origin_img, (224, 224), interpolation=cv2.INTER_CUBIC)

# pytorch转paddlepaddle
from x2paddle.convert import pytorch2paddle
pytorch2paddle(model,
               save_dir="model/yolov5n_paddle",
               jit_type="trace",
               input_examples=[torch.randn(1, 3, 224, 224)])


# pytorch转paddlelite
from x2paddle.convert import pytorch2paddle
pytorch2paddle(model,
               'model/android',
               jit_type="trace",
               input_examples=[torch.randn(1, 3, 224, 224)],
               convert_to_lite=True,
               lite_valid_places="arm",
               lite_model_type="naive_buffer")

下图为运行结果 model/android下是一步转为paddlelite的结果,model/yolov5n_paddle是pytorch转paddlepaddle的结果

pytorch2paddlelite

paddlepaddle验证并转换(可选)

from model.yolov5n_paddle.x2paddle_code import AutoShape as YOLOV5
import paddle
import cv2
import paddle.vision.transforms as T
import numpy as np
from tools import nms

obj_type = [
    'person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train', 'truck', 'boat',
    'traffic light', 'fire hydrant', 'stop sign', 'parking meter', 'bench', 'bird', 'cat',
    'dog', 'horse', 'sheep', 'cow', 'elephant', 'bear', 'zebra', 'giraffe', 'backpack',
    'umbrella', 'handbag', 'tie', 'suitcase', 'frisbee', 'skis', 'snowboard', 'sports ball',
    'kite', 'baseball bat', 'baseball glove', 'skateboard', 'surfboard', 'tennis racket',
    'bottle', 'wine glass', 'cup', 'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple',
    'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza', 'donut', 'cake', 'chair',
    'couch', 'potted plant', 'bed', 'dining table', 'toilet', 'tv', 'laptop', 'mouse', 'remote',
    'keyboard', 'cell phone', 'microwave', 'oven', 'toaster', 'sink', 'refrigerator', 'book',
    'clock', 'vase', 'scissors', 'teddy bear', 'hair drier', 'toothbrush'
]

target_index = obj_type.index('person')

origin_img = cv2.imread('img/dog.jpg')
scale_matrix = np.array([origin_img.shape[1] / 224, origin_img.shape[0] / 224, origin_img.shape[1] / 224, origin_img.shape[0] / 224])

img_to_tensor_transform = T.Compose([
    # 尺寸转为224*224
    T.Resize((224, 224)),
    # 转Tensor
    T.ToTensor(),

])
rgb_img = cv2.cvtColor(origin_img, cv2.COLOR_BGR2RGB)
# print(rgb_img)
x = img_to_tensor_transform(rgb_img).unsqueeze(0).cuda()
print(x)

paddle.disable_static()
params = paddle.load(r'model\yolov5n_paddle\model.pdparams')
model = YOLOV5()
model.set_dict(params, use_structured_name=True)
model.eval()

result = model(x).detach().numpy()[0]

print(result)

confidence = result[:, 4]
scores = confidence * result[:, 5 + target_index]
mask = scores > 0.5
boxes = result[mask][:, :4]
scores = scores[mask]
# boxes = paddle.to_tensor(boxes, dtype='float32')
# scores = paddle.to_tensor(scores, dtype='float32')
boxes_indices = nms(boxes, scores, 0.5)
boxes = (boxes[boxes_indices] * scale_matrix)
boxes = boxes.astype(int)
for box in boxes:
    x, y, w, h = box
    w = int(w / 2)
    h = int(h / 2)
    print(x, y, w, h)
    cv2.rectangle(origin_img, (x - w, y - h), (x + w, y + h), (0, 255, 0), 2)
cv2.imshow('result', origin_img)
cv2.waitKey(0)


# 引用Paddlelite预测库
from paddlelite.lite import *
# 1. 创建opt实例
opt=Opt()
# 2. 指定输入模型地址
opt.set_model_dir("model/yolov5n_paddle/inference_model")
# 3. 指定转化类型: arm、x86、opencl、npu
opt.set_valid_places("arm")
# 4. 指定模型转化类型: naive_buffer、protobuf
opt.set_model_type("naive_buffer")
# 5. 输出模型地址
opt.set_optimize_out("model/yolov5n_opt")
# 6. 执行模型优化
opt.run()

结果model/yolov5n_opt.nb就是转换出来的文件跟上面的opt是一样的

paddlepaddle2paddlelite

安卓开发部分

项目构建

项目以paddlelite提供的预编译库为基础构建

paddlelite预编译库下载

预编译库随时间会有改变,这里给出一个参考

android_sdk

压缩报内的demo/java/android/PaddlePredictor就是我们需要的项目框架

编写项目前的准备

启动前修改

查询同一目录下的prepare_demo.bash文件可以得知jar包和os文件的路径

  1. 复制压缩包的java/so/libpaddlelitejni.so到项目框架的app\src\main\jniLibs\arm64-v8a和app\src\main\jniLibs\armeabi-v7a

  2. 复制压缩包的java/jar/PaddlePredictor.jar到项目框架的app\libs

  3. 修改项目的gradle\wrapper\gradle-wrapper.properties

  • services.gradle.org/distributions 改为 mirrors.cloud.tencent.com/gradle 使用腾讯的gradle镜像
  1. 使用androidstudio启动项目

编译报错解决

使用java8

到oracle_java官网下载并安装java8

在setting中选择使用java8 如果你的这里没有需要选择下面的add选项手动添加刚刚下载的java8

指定java8

在app模型的gradle中指定使用java8编译,如果你的项目结构跟我的不一样,点击左上角的下拉选框切换为Project

指定使用java8编译

添加opencv依赖

修改app模块的build.gradle的dependencies,新增依赖

dependencies {
    implementation 'com.quickbirdstudios:opencv:4.5.3.0'
}

编写安卓程序

编写页面

修改activity_main.xml

<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">


    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >

<!--        <ImageView-->
<!--            android:id="@+id/image_view"-->
<!--            android:layout_width="wrap_content"-->
<!--            android:layout_height="wrap_content" />-->

        <org.opencv.android.JavaCameraView
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:id="@+id/yolo_view" />

<!--        <ImageView-->
<!--            android:id="@+id/imageView"-->
<!--            android:layout_width="match_parent"-->
<!--            android:layout_height="wrap_content"-->
<!--            android:scaleType="centerCrop" />-->


    </LinearLayout>


</android.support.constraint.ConstraintLayout>

编写逻辑处理

新增实体类IndexAndScore存放bounding box的索引 位置 置信度信息
import java.util.ArrayList;
import java.util.List;

public class IndexAndScore {
    private List<Integer> indices = new ArrayList<>();

    private List<int[]> boxList = new ArrayList<>();

    private List<Float> score = new ArrayList<>();

    public IndexAndScore() {
    }

    public void add(int index, int[] position, float score) {
        this.indices.add(index);
        this.boxList.add(position);
        this.score.add(score);
    }


    public List<int[]> getBoxList() {
        return boxList;
    }

    public void setBoxList(List<int[]> boxList) {
        this.boxList = boxList;
    }

    public List<Integer> getIndices() {
        return indices;
    }

    public List<Float> getScore() {
        return score;
    }
}
新增工具类实现非极大值抑制
public class NonMaximumSuppression {

    /**
     * 非极大值抑制 (NMS)
     *
     * @param boxes       边界框列表,每个框格式为 [x1, y1, x2, y2],表示左上角和右下角坐标
     * @param scores      每个框的置信度分数
     * @param threshold   IoU 阈值,用于判断是否抑制
     * @return            经过 NMS 处理后保留的框索引
     */
    public static List<Integer> nms(List<int[]> boxes, final List<Float> scores, float threshold) {
        // 保存最终保留的框索引
        List<Integer> pickedIndices = new ArrayList<>();

        // 创建一个索引数组,并按照分数从高到低排序
        Integer[] sortedIndices = new Integer[scores.size()];
        for (int i = 0; i < scores.size(); i++) {
            sortedIndices[i] = i;
        }

        // 使用 Comparator 接口进行排序
        Arrays.sort(sortedIndices, new Comparator<Integer>() {
            @Override
            public int compare(Integer i1, Integer i2) {
                return Float.compare(scores.get(i2), scores.get(i1)); // 降序排序
            }
        });

        while (sortedIndices.length > 0) {
            // 当前最高分的框
            int current = sortedIndices[0];
            pickedIndices.add(current);

            // 计算当前框与其他框的 IoU
            List<Integer> remainingIndices = new ArrayList<>();
            for (int i = 1; i < sortedIndices.length; i++) {
                int other = sortedIndices[i];
                float iou = calculateIoU(boxes.get(current), boxes.get(other));

                // 如果 IoU 小于阈值,则保留
                if (iou <= threshold) {
                    remainingIndices.add(other);
                }
            }

            // 更新剩余框
            sortedIndices = remainingIndices.toArray(new Integer[0]);
        }

        return pickedIndices;
    }

    /**
     * 计算两个框的交并比 (IoU)
     *
     * @param box1 第一个框 [x1, y1, x2, y2]
     * @param box2 第二个框 [x1, y1, x2, y2]
     * @return IoU 值
     */
    private static float calculateIoU(int[] box1, int[] box2) {
        // 计算交集区域的坐标
        int interX1 = Math.max(box1[0], box2[0]);
        int interY1 = Math.max(box1[1], box2[1]);
        int interX2 = Math.min(box1[2], box2[2]);
        int interY2 = Math.min(box1[3], box2[3]);

        // 计算交集面积
        int interArea = Math.max(0, interX2 - interX1) * Math.max(0, interY2 - interY1);

        // 计算每个框的面积
        int box1Area = (box1[2] - box1[0]) * (box1[3] - box1[1]);
        int box2Area = (box2[2] - box2[0]) * (box2[3] - box2[1]);

        // 计算并集面积
        int unionArea = box1Area + box2Area - interArea;

        // 返回 IoU
        return (float) interArea / unionArea;
    }

}
修改MainActivity
package com.baidu.paddle.lite;


import android.content.Context;
import android.content.res.AssetManager;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;

import com.baidu.paddle.lite.entity.IndexAndScore;
import com.baidu.paddle.lite.tools.NonMaximumSuppression;

import org.opencv.android.CameraActivity;
import org.opencv.android.CameraBridgeViewBase;
import org.opencv.android.OpenCVLoader;
import org.opencv.core.Mat;
import org.opencv.core.Point;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.imgproc.Imgproc;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;



public class MainActivity extends CameraActivity implements CameraBridgeViewBase.CvCameraViewListener2 {

    private static final String    TAG  = "Main";

    private static final int inputScale = 224;

    private static final Scalar BOX_COLOR         = new Scalar(0, 255, 0);

    private List<String> object_target_list = Arrays.asList(
            "person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck", "boat",
            "traffic light", "fire hydrant", "stop sign", "parking meter", "bench", "bird", "cat",
            "dog", "horse", "sheep", "cow", "elephant", "bear", "zebra", "giraffe", "backpack",
            "umbrella", "handbag", "tie", "suitcase", "frisbee", "skis", "snowboard", "sports ball",
            "kite", "baseball bat", "baseball glove", "skateboard", "surfboard", "tennis racket",
            "bottle", "wine glass", "cup", "fork", "knife", "spoon", "bowl", "banana", "apple",
            "sandwich", "orange", "broccoli", "carrot", "hot dog", "pizza", "donut", "cake", "chair",
            "couch", "potted plant", "bed", "dining table", "toilet", "tv", "laptop", "mouse", "remote",
            "keyboard", "cell phone", "microwave", "oven", "toaster", "sink", "refrigerator", "book",
            "clock", "vase", "scissors", "teddy bear", "hair drier", "toothbrush"
    );

    private Mat mRgba;

    private Size mInputSize = new Size(inputScale, inputScale);

    private CameraBridgeViewBase   mOpenCvCameraView;

    private static final String[] EXTENSIONS_TO_COPY = {"nb", "jpg"};

    private PaddlePredictor predictor;

    public MainActivity() {
        Log.i(TAG, "Instantiated new " + this.getClass());
    }

    private static void copyAssetsToSdcard(Context context, File destFolder, String[] extensions) {
        AssetManager assetManager = context.getAssets();

        try {
            // List all files in the assets folder once
            String[] assetFiles = assetManager.list("");
            if (assetFiles == null) return;

            for (String assetFileName : assetFiles) {
                // Check if file matches any of the provided extensions
                for (String extension : extensions) {
                    if (assetFileName.endsWith("." + extension)) {
                        File outFile = new File(destFolder, assetFileName);

                        // Skip if file already exists
                        if (outFile.exists()) break;

                        // Copy the file from assets to the destination folder
                        try (InputStream inputStream = assetManager.open(assetFileName);
                             OutputStream outputStream = new FileOutputStream(outFile)) {

                            byte[] buffer = new byte[1024];
                            int bytesRead;
                            while ((bytesRead = inputStream.read(buffer)) != -1) {
                                outputStream.write(buffer, 0, bytesRead);
                            }
                        }
                        break; // No need to check further extensions
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        Log.i(TAG, "called onCreate");
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 复制assets的内容到手机外存
        File sdcardDataFolder = this.getExternalFilesDir(null);
        if (sdcardDataFolder == null) {
            Log.e(TAG, "Failed to get external storage directory");
            return;
        }
        copyAssetsToSdcard(this, sdcardDataFolder, EXTENSIONS_TO_COPY);

        if (OpenCVLoader.initDebug()) {
            Log.i(TAG, "OpenCV loaded successfully");
        } else {
            Log.e(TAG, "OpenCV initialization failed!");
            (Toast.makeText(this, "OpenCV initialization failed!", Toast.LENGTH_LONG)).show();
            return;
        }

        // 实例化 PaddlePredictor
        MobileConfig config = new MobileConfig();
        config.setModelFromFile(sdcardDataFolder.getAbsolutePath() + "/opt.nb");
        config.setPowerMode(PowerMode.LITE_POWER_FULL);
        config.setThreads(1);
        predictor = PaddlePredictor.createPaddlePredictor(config);

        // 为opencv展示框设置监听器
        mOpenCvCameraView = findViewById(R.id.yolo_view);
        mOpenCvCameraView.setMaxFrameSize(720, 720);
        mOpenCvCameraView.setCvCameraViewListener(this);

    }

    @Override
    public void onPause() {
        super.onPause();
        if (mOpenCvCameraView != null)
            mOpenCvCameraView.disableView();
    }

    @Override
    public void onResume() {
        super.onResume();
        if (mOpenCvCameraView != null)
            mOpenCvCameraView.enableView();
    }

    @Override
    protected List<? extends CameraBridgeViewBase> getCameraViewList() {
        return Collections.singletonList(mOpenCvCameraView);
    }

    public void onDestroy() {
        super.onDestroy();
        if (mOpenCvCameraView != null)
            mOpenCvCameraView.disableView();
    }

    public void onCameraViewStarted(int width, int height) {
        mRgba = new Mat();
    }

    public void onCameraViewStopped() {
        mRgba.release();
    }


    public Mat onCameraFrame(CameraBridgeViewBase.CvCameraViewFrame inputFrame) {

//        Log.d("camera", "获取到数据");
        // opencv的读取结果是横向的 进行旋转
        mRgba = inputFrame.rgba();
//        Imgproc.resize(mRgba, mRgba, new Size(720,720), Imgproc.INTER_CUBIC);
        mRgba = rotateImage(mRgba);
        // 记录宽高,并计算缩放比例
        int rows = mRgba.rows();
        int cols = mRgba.cols();
//        Log.d(TAG, "onCameraFrame: " + rows + "\t" + cols);
        int changeRows = rows / inputScale;
        int changeCols = cols / inputScale;

        // 获取tensor输入数据
        float[] inputData = processFrame(mRgba);
//       Log.d("input", Arrays.toString(inputData));

        // 输入数据到 Tensor
        Tensor inputTensor = predictor.getInput(0);
        inputTensor.resize(new long[]{1, 3, inputScale, inputScale});
        inputTensor.setData(inputData);

        // 执行预测器,获取结果
        predictor.run();
        Tensor outputTensor = predictor.getOutput(0);
        float[] outputData = outputTensor.getFloatData();
//        Log.d("output", Arrays.toString(outputData));


        Map<Integer, IndexAndScore> classes = new HashMap<>();
        for (int i = 0 ; i < outputData.length / 85; i++) {
            // 获取每个类别的置信度
            float confidence = outputData[i * 85 + 4];
            if (confidence < 0.5) {
                continue;
            }
//            Log.d("congraduation", "发现一个物体");

            // 获取类别及其类别置信度
            int classId = 0;
            float max_score = 0;
            for (int offset = 0; offset < 80; ++offset) {
                if (outputData[i * 85 + 5 + offset] > max_score) {
                    classId = offset;
                    max_score = outputData[i * 85 + 5 + offset];
                }
            }
            if (max_score < 0.5) {
                continue;
            }


            // 计算坐标
            int xc = (int) (outputData[i * 85] * changeCols);
            int yc = (int) (outputData[i * 85 + 1] * changeRows);
            int w = (int) (outputData[i * 85 + 2] * changeCols);
            int h = (int) (outputData[i * 85 + 3] * changeRows);
            int x1 = xc - w / 2;
            int y1 = yc - h / 2;
            int x2 = xc + w / 2;
            int y2 = yc + h / 2;

//            Log.d("congraduation", "找到一个目标:" + object_target_list.get(classId));

//            Log.d("position", w + "\t" + h + "\t" + w * changeCols + "\t" + h * changeRows);

            // 新增类别的box信息
            if (classes.get(classId) == null) {
                classes.put(classId, new IndexAndScore());
            }
            classes.get(classId).add(i, new int[]{x1, y1, x2, y2}, max_score);
//            Imgproc.rectangle(mRgba, new org.opencv.core.Point(x - w, y - h), new org.opencv.core.Point(x +  w, y +  h), BOX_COLOR, 2);
        }


        for (Map.Entry<Integer, IndexAndScore> integerIndexAndScoreEntry : classes.entrySet()) {
            Integer classId = integerIndexAndScoreEntry.getKey();
            IndexAndScore indexAndScore = integerIndexAndScoreEntry.getValue();
            // 非极大值抑制
            List<Integer> pickedIndices = NonMaximumSuppression.nms(indexAndScore.getBoxList(), indexAndScore.getScore(), 0.1f);

            // 绘制框
            for (Integer pickedIndex : pickedIndices) {
                int[] box = indexAndScore.getBoxList().get(pickedIndex);
//                Log.d("position", box[1] + "\t" + box[3] + "\t" + box[0] + "\t" + box[2]);
//                float score = indexAndScore.getScore().get(pickedIndex);
                Imgproc.rectangle(mRgba, new org.opencv.core.Point(box[0], box[1]), new org.opencv.core.Point(box[2], box[3]), BOX_COLOR, 2);
                Imgproc.putText(mRgba, object_target_list.get(classId), new org.opencv.core.Point(box[0], box[1]), 3, 1, BOX_COLOR);
            }
        }
        return mRgba;
    }

    /**
     * 处理帧为tensor输入数据
     * @param rgbaMat 帧数据
     * @return tensor输入数据
     */
    private float[] processFrame(Mat rgbaMat) {

        // 转换为 RGB
        Mat rgbMat = new Mat();
//        warpAffine(rgbaMat, rgbMat, rotate, rgbaMat.size());
//        Mat floatMat = new Mat();
//        Mat nornalizeMat = new Mat();
        Imgproc.cvtColor(rgbaMat, rgbMat, Imgproc.COLOR_RGBA2RGB);
//        int rows = mRgba.rows();
//        int cols = mRgba.cols();

//        rgbMat.convertTo(floatMat, CvType.CV_32F);
//        Core.divide(floatMat, new Scalar(255.0), nornalizeMat);
//
//        // 标准化(减去均值并除以标准差)
//        Scalar mean = new Scalar(0.485, 0.456, 0.406); // ImageNet 均值
//        Scalar std = new Scalar(0.229, 0.224, 0.225);  // ImageNet 标准差
//
//        Mat normalizedImage = new Mat();
//        Core.subtract(nornalizeMat, mean, normalizedImage);
//        Core.divide(normalizedImage, std, normalizedImage);

//        // 将图像数据转换为 float 数组
//        float[] imageData = new float[(int) (normalizedImage.total() * normalizedImage.channels())];
//        normalizedImage.get(0, 0, imageData);
//
//        return imageData;

        // 调整尺寸
        Imgproc.resize(rgbMat, rgbMat, new Size(inputScale, inputScale), Imgproc.INTER_CUBIC);
        float[] inputData = new float[3 * inputScale * inputScale];
        for (int i = 0; i < inputScale; i++) {
            for (int j = 0; j < inputScale; j++) {
                double[] pixel = rgbMat.get(i, j);
                inputData[0 * inputScale * inputScale + i * inputScale + j] = (float)pixel[0] / 255.0f; // (float) ((pixel[0] - 0.485) / 0.229); // R
                inputData[1 * inputScale * inputScale + i * inputScale + j] = (float)pixel[1] / 255.0f; // (float) ((pixel[1] - 0.456) / 0.224); // G
                inputData[2 * inputScale * inputScale + i * inputScale + j] = (float)pixel[2] / 255.0f; // (float) ((pixel[2] - 0.406) / 0.225); // B
            }
        }
        return inputData;
    }

    /**
     * 旋转图像
     * @param src 原图像
     * @return 旋转后的图像
     */
    public static Mat rotateImage(Mat src) {
        Mat dst = new Mat();
        Point center = new Point(src.cols() / 2, src.rows() / 2);
        Mat rotationMatrix = Imgproc.getRotationMatrix2D(center, 270, 1.0);

        Size size = new Size(src.cols(), src.rows());
        Imgproc.warpAffine(src, dst, rotationMatrix, size);

        return dst;
    }

}
在AndroidManifest.xml中添加权限请求信息

mainfest标签中添加

 <supports-screens android:resizeable="true"
        android:smallScreens="true"
        android:normalScreens="true"
        android:largeScreens="true"
        android:anyDensity="true" />

    <uses-permission android:name="android.permission.CAMERA"/>

    <uses-feature android:name="android.hardware.camera" android:required="false"/>
    <uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
    <uses-feature android:name="android.hardware.camera.front" android:required="false"/>
    <uses-feature android:name="android.hardware.camera.front.autofocus" android:required="false"/>

模型复制

把之前转换的模型移动到assets目录下注意加载的名称要对应

模型复制到安卓

手机进入开发者模式打开usb调试即可运行程序

运行结果


http://www.kler.cn/a/612417.html

相关文章:

  • [学成在线]06-视频分片上传
  • C# 打印模板设计-ACTIVEX打印控件-多模板加载
  • 卷积神经网络 - 关于LeNet-5的若干问题的解释
  • 【新人系列】Golang 入门(九):defer 详解 - 下
  • FPGA_YOLO学习(一)
  • 【HTML】KaTeX 常用公式字符
  • 问题分析4
  • 数据结构与算法:双向广搜
  • 第六届 蓝桥杯 嵌入式 省赛
  • ​​SenseGlove与Aeon Robotics携手推出HEART项目,助力机器人培训迈向新台阶
  • uniapp自定义目录tree(支持多选、单选、父子联动、全选、取消、目录树过滤、异步懒加载节点、v-model)vue版本
  • 免费使用!OpenAI 全量开放 GPT-4o 图像生成能力!
  • QT记事本
  • RISC-V AIA学习3---APLIC 第二部分(APLIC 中断域的内存映射控制区域)
  • 【软测】AI助力测试用例
  • 快速入手-基于Django-rest-framework的ModelSerializer模型序列化器(三)
  • 华为、浪潮、华三链路聚合概述
  • python使用cookie、session、selenium实现网站登录(爬取信息)
  • 用 Python 也能做微服务?
  • Vue+SpringBoot:整合JasperReport作PDF报表,并解决中文不显示问题