android cpp

MainActivity

kotlin 复制代码
package com.example.moth

import android.content.Intent
import android.content.res.AssetManager
import android.graphics.Bitmap
import android.os.Bundle
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import android.widget.Button
import android.widget.TextView
import android.widget.Toast
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.navigation.NavigationView
import androidx.navigation.findNavController
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.navigateUp
import androidx.navigation.ui.setupActionBarWithNavController
import androidx.navigation.ui.setupWithNavController
import androidx.drawerlayout.widget.DrawerLayout
import androidx.appcompat.app.AppCompatActivity
import com.example.moth.databinding.ActivityMainBinding
import java.io.ByteArrayOutputStream
import okhttp3.MediaType
import okhttp3.MultipartBody
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import com.example.moth.httpInterface.RecognizeClient.recognizeService
import com.example.moth.httpInterface.PredictResponse
import com.example.moth.httpInterface.PredictResult


class MainActivity : AppCompatActivity() {

    private lateinit var appBarConfiguration: AppBarConfiguration
    private lateinit var binding: ActivityMainBinding
    private var selectedBitMap: Bitmap? = null
    private lateinit var recognizeResultTextView: TextView

    private lateinit var assetManager: AssetManager

    companion object {
        // 加载本地库
        init {
            System.loadLibrary("native-lib")
        }
    }

    // 声明静态native方法
    private external fun staticProcessImage(bitmap: Bitmap): String

    private val insectMapping = mapOf(
        "0" to "稻纵卷叶螟",
        "1" to "亚洲稻螟虫",
        "2" to "白蛾",
        "3" to "鬼脸天蛾",
        "4" to "玉米螟",
        "5" to "夜盗蛾",
        "6" to "桃蛀螟",
        "7" to "甘蓝夜蛾",
        "8" to "草地贪夜蛾",
        "9" to "斜带葡萄天蛾",
        "10" to "棉斜纹夜蛾",
        "11" to "枯叶夜蛾",
        "12" to "横线尾夜蛾"
    )

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // 检查token
        val sharedPreferences = getSharedPreferences("AppPreferences", MODE_PRIVATE)
        val token = sharedPreferences.getString("token", null)

        if(token == null){
            val intent = Intent(this, LoginActivity::class.java)
            startActivity(intent)
            finish()
            return
        }

        assetManager = assets

        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        setSupportActionBar(binding.appBarMain.toolbar)

        binding.appBarMain.fab.setOnClickListener { view ->
            Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                .setAction("Action", null)
                .setAnchorView(R.id.fab).show()
        }
        val drawerLayout: DrawerLayout = binding.drawerLayout
        val navView: NavigationView = binding.navView
        val navController = findNavController(R.id.nav_host_fragment_content_main)
        // Passing each menu ID as a set of Ids because each
        // menu should be considered as top level destinations.
        appBarConfiguration = AppBarConfiguration(
            setOf(
                R.id.nav_home, R.id.nav_gallery, R.id.nav_slideshow
            ), drawerLayout
        )
        setupActionBarWithNavController(navController, appBarConfiguration)
        navView.setupWithNavController(navController)

        recognizeResultTextView = findViewById(R.id.recognizeResult)
        val recognizeButton: Button = findViewById(R.id.recognizeButton)
        val uploadButton: Button = findViewById(R.id.recognizeOnlineButton)

        // 设置离线识别按钮的点击事件
        recognizeButton.setOnClickListener {
            selectedBitMap?.let {bitmap ->
                val inferenceResult = staticProcessImage(bitmap)
                val inferenceTop3 = inferenceResult.split(" ")
                val mappedResult = inferenceTop3.map { insectMapping[it] ?: " "}
                val returnText = "离线识别的结果是:$mappedResult"
                recognizeResultTextView.text = returnText
            } ?: run {
                recognizeResultTextView.text = "请先选择图片"
            }
        }
        // 设置上传识别按钮的点击事件
        uploadButton.setOnClickListener {
            selectedBitMap?.let {bitmap ->
                val imageStream = bitmap2ByteArray(bitmap)
                uploadImage(imageStream)
            } ?: run{
                recognizeResultTextView.text = "请先选择图片"
            }
        }

    }

    private fun bitmap2ByteArray(bitmap: Bitmap): ByteArray {
        val outputStream = ByteArrayOutputStream()
        bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
        return outputStream.toByteArray()
    }

    private fun uploadImage(byteArray: ByteArray){
        val requestBody = okhttp3.RequestBody.create(MediaType.parse("image/jpeg"), byteArray)
        val filePart = MultipartBody.Part.createFormData("image", "image.jpg", requestBody)
        recognizeService.predict(filePart).enqueue(object : Callback<PredictResponse> {
            override fun onResponse(
                call: Call<PredictResponse>,
                response: Response<PredictResponse>
            ) {
                if(response.isSuccessful && response.body()?.success == true) {
                    val result = response.body()?.predictions
                    if(result == null){
                        Toast.makeText(this@MainActivity, "上传图片成功,但未返回识别结果", Toast.LENGTH_SHORT).show()
                    }else {
                        val labels: List<String> = result.map { it.label }
                        val mappedLabels = labels.map { insectMapping[it] ?: " "}
                        val returnText = "上传识别的结果是:$mappedLabels"
                        recognizeResultTextView.text = returnText
                    }
                }else {
                    Toast.makeText(this@MainActivity, "上传图片失败", Toast.LENGTH_SHORT).show()
                }
            }

            override fun onFailure(call: Call<PredictResponse>, t: Throwable) {
                Toast.makeText(this@MainActivity, "请求失败", Toast.LENGTH_SHORT).show()
                Log.e("MainActivity", "上传图片失败: ${t.message}")
            }

        })
    }

    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        // Inflate the menu; this adds items to the action bar if it is present.
        menuInflater.inflate(R.menu.main, menu)
        return true
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        when(item.itemId){
            R.id.logout -> {
                val sharedPreferences = getSharedPreferences("AppPreferences", MODE_PRIVATE)
                val editor = sharedPreferences.edit()
                editor.remove("token")
                editor.apply()
                val intent = Intent(this, LoginActivity::class.java)
                startActivity(intent)
                finish()
                return true
            }
        }
        return super.onOptionsItemSelected(item)
    }

    override fun onSupportNavigateUp(): Boolean {
        val navController = findNavController(R.id.nav_host_fragment_content_main)
        return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
    }
    fun setSelectedBitMap(bitmap: Bitmap){
        selectedBitMap = bitmap
    }
}

CMakeList

bash 复制代码
project(moth)

cmake_minimum_required(VERSION 3.10.0)

# 设置Android环境
set(CMAKE_SYSTEM_NAME Android)
set(CMAKE_ANDROID_NDK $ENV{ANDROID_NDK_HOME})
set(CMAKE_ANDROID_STL_TYPE c++_static)

# 设置 ncnn 库路径
set(ncnn_DIR ${CMAKE_SOURCE_DIR}/ncnn/${ANDROID_ABI}/lib)

# 设置opencv 库路径
set(opencv_DIR ${CMAKE_SOURCE_DIR}/opencv/native)

# 查找并连接libandroid.so
find_library(android-lib android)

# 添加 ncnn 库
add_library(ncnn SHARED IMPORTED)
set_target_properties(ncnn PROPERTIES IMPORTED_LOCATION ${ncnn_DIR}/libncnn.so)

# 添加opencv 库
add_library(opencv SHARED IMPORTED)
set_target_properties(opencv PROPERTIES IMPORTED_LOCATION ${opencv_DIR}/libs/${ANDROID_ABI}/libopencv_java4.so)

include_directories(${opencv_DIR}/jni/include)

# 添加cpp源码
add_library(native-lib SHARED native-lib.cpp)

# 添加C++标准库
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 链接 ncnn 库
target_link_libraries(native-lib ${android-lib} ncnn opencv jnigraphics c++_shared)

native-lib.cpp

cpp 复制代码
//
// Created by Dell on 2025/2/20.
//
//
// Created by Dell on 2025/2/20.
//
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#include <jni.h>
#include <string>
#include <android/asset_manager.h>
#include <android/asset_manager_jni.h>
#include <android/bitmap.h>
#include <opencv2/opencv.hpp>
#include "ncnn/arm64-v8a/include/ncnn/net.h"


// 中心裁切并归一化处理
cv::Mat preprocessImage(const cv::Mat& inputImage, int targetWidth, int targetHeight) {

    cv::Mat resizedImage;
    cv::resize(inputImage, resizedImage, cv::Size(targetWidth, targetHeight));

    // 将裁切后的图像转换为浮点型并归一化
    cv::Mat normalizedImage;
    resizedImage.convertTo(normalizedImage, CV_32F, 1.0 / 255.0);

    // 归一化处理
    cv::Mat channels[3];
    cv::split(normalizedImage, channels);

    // 对每个通道进行归一化
    for (int i = 0; i < 3; ++i) {
        channels[i] = (channels[i] - 0.485) / 0.229;  // 第一个通道R
        if (i == 1) {
            channels[i] = (channels[i] - 0.456) / 0.224;  // 第二个通道G
        } else if (i == 2) {
            channels[i] = (channels[i] - 0.406) / 0.225;  // 第三个通道B
        }
    }

    // 合并通道
    cv::merge(channels, 3, normalizedImage);

    return normalizedImage;
}


extern "C"
JNIEXPORT jstring JNICALL
JNICALL
Java_com_example_moth_MainActivity_staticProcessImage(JNIEnv* env, jobject thiz, jobject bitmap) {
    // 获取 AssetManager
    jclass clazz = env->GetObjectClass(thiz);
    jfieldID assetManagerFieldID = env->GetFieldID(clazz, "assetManager", "Landroid/content/res/AssetManager;");
    jobject assetManagerObj = env->GetObjectField(thiz, assetManagerFieldID);
    AAssetManager* assetManager = AAssetManager_fromJava(env, assetManagerObj);

    // 加载 .param 文件
    AAsset* paramAsset = AAssetManager_open(assetManager, "effivit.param", AASSET_MODE_BUFFER);
    if (!paramAsset) {
        return env->NewStringUTF("Failed to open model.param");
    }

    // 读取 .param 文件内容
    size_t paramSize = AAsset_getLength(paramAsset);
    char* paramBuffer = new char[paramSize];
    AAsset_read(paramAsset, paramBuffer, paramSize);
    AAsset_close(paramAsset);

    // 加载 .bin 文件
    AAsset* binAsset = AAssetManager_open(assetManager, "effivit.bin", AASSET_MODE_BUFFER);
    if (!binAsset) {
        delete[] paramBuffer;
        return env->NewStringUTF("Failed to open model.bin");
    }

    // 读取 .bin 文件内容
    size_t binSize = AAsset_getLength(binAsset);
    char* binBuffer = new char[binSize];
    AAsset_read(binAsset, binBuffer, binSize);
    AAsset_close(binAsset);

    // 创建 ncnn 网络
    ncnn::Net net;
    int ret = net.load_param(assetManager, "effivit.param");
    if (ret != 0) {
        delete[] paramBuffer;
        delete[] binBuffer;
        return env->NewStringUTF("Failed to load model.param");
    }
    ret = net.load_model(assetManager, "effivit.bin");
    if (ret != 0) {
        delete[] paramBuffer;
        delete[] binBuffer;
        return env->NewStringUTF("Failed to load model.bin");
    }

    // 释放内存
    delete[] paramBuffer;
    delete[] binBuffer;

    // 获取 Bitmap 信息
    AndroidBitmapInfo info;
    void* pixels;
    int retBitmap;

    if ((retBitmap = AndroidBitmap_getInfo(env, bitmap, &info)) < 0) {
        return env->NewStringUTF("AndroidBitmap_getInfo() failed!");
    }

    if (info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) {
        return env->NewStringUTF("Bitmap format is not RGBA_8888!");
    }

    if ((retBitmap = AndroidBitmap_lockPixels(env, bitmap, &pixels)) < 0) {
        return env->NewStringUTF("AndroidBitmap_lockPixels() failed!");
    }

    // 将 Bitmap 转换为 OpenCV Mat
    cv::Mat mat(info.height, info.width, CV_8UC4, pixels);
    cv::Mat mat3;
    cv::cvtColor(mat, mat3, cv::COLOR_RGBA2BGR);

    // 调整图像大小
    int targetWidth = 224;
    int targetHeight = 224;
    cv::Mat resizedMat;  //= preprocessImage(mat3, targetWidth, targetHeight);
    cv::resize(mat3, resizedMat, cv::Size(targetWidth, targetHeight));

    // 将 Mat 转换为 ncnn::Mat
    ncnn::Mat in = ncnn::Mat::from_pixels(resizedMat.data, ncnn::Mat::PIXEL_BGR, targetWidth, targetHeight);

    // 进行推理
    ncnn::Extractor ex = net.create_extractor();
    ex.input("input.1", in);
    ncnn::Mat out;
    ex.extract("1270", out);

    // 释放 Bitmap 像素数据
    AndroidBitmap_unlockPixels(env, bitmap);

    // 处理输出结果
    // 假设张量 out 的形状为 (1, 13),即 1 行 13 列
    float* outputData = (float*)out.data;
    int numClasses = out.w; // 获取类别数量(13)

    // 找到概率最大的三个下标
    std::vector<std::pair<float, int>> probIndexPairs;
    for (int i = 0; i < numClasses; i++) {
        probIndexPairs.push_back(std::make_pair(outputData[i], i));
    }

    // 按概率值从大到小排序
    std::sort(probIndexPairs.begin(), probIndexPairs.end(), [](const std::pair<float, int>& a, const std::pair<float, int>& b) {
        return a.first > b.first;
    });

    // 获取概率最大的三个下标
    std::string result;
    for (int i = 0; i < 3 && i < probIndexPairs.size(); i++) {
        result += std::to_string(probIndexPairs[i].second) + " ";
    }

    // 返回结果给调用方
    return env->NewStringUTF(result.c_str());
}

stb_image.h

c 复制代码
在这里插入代码片
相关推荐
林九生18 小时前
【实用技巧】MySQL 绿色版一键路径更新脚本详解 —— update_path.bat 深度解析
android·数据库·mysql
故渊at19 小时前
第十三板块:Android 综合架构与未来演进 | 第三十一篇:Android 架构演进与 Fuchsia OS 的挑战
android·架构·宏内核·微内核·fuchsia·ipc 性能博弈
aqi0019 小时前
一文速览 HarmonyOS 6.1.1 推出的十个新特性
android·华为·harmonyos·鸿蒙·harmony
matrixmind120 小时前
aiomysql:异步场景下的 MySQL 驱动
android·数据库·mysql·其他
随遇丿而安20 小时前
第8周:弹窗 / 提示组件全功能与弹窗优化
android
zh_xuan20 小时前
诡异Bug:输入框删除字符,却越删越多
android·bug
nwsuaf_huasir20 小时前
matlab绘制尺寸和字体合适的图片插入到latex的方法
android·开发语言·matlab
future_li20 小时前
Speed Tools:一套低侵入的 Android 插件化 + 动态换肤 + 字体切换框架
android
杊页20 小时前
第一板块:Android 系统基石与运行原理 | 第二篇:Android 编译、打包与安装机制
android·操作系统
故渊at20 小时前
第十二板块:Android 系统启动与初始化 | 第三十篇:Zygote 孵化机制与 System Server 的启动
android·wms·pms·ams·zygote·ipc