AI Playground

AI Playground

AI Playground用户体验平台旨在为行业用户提供"开箱即用"的软硬一体化AI功能体验。无论是个人开发者还是企业创新团队,AI Playground都能提供全链路支持,让复杂的AI技术高效转化为可落地的实践路径,加速AI创新与产品化进程。

AI Playground简介

本节主要介绍AI Playground用户体验平台对Android端算法SDK的集成开发指南,旨在帮助开发者快速了解与应用部署各类AI算法。

集成的算法服务

算法类型 功能描述 应用场景
人脸识别 人脸检测、关键点定位、特征提取与比对 门禁考勤、身份验证
口罩检测 检测口罩佩戴状态(正确/错误/未佩戴) 公共场所安全监测
手势识别 识别常见手势动作 人机交互、智能家居
二维码识别 快速扫描和解码二维码/条形码 支付、信息获取
OCR文字识别 图片和相机流中的文字提取 证件识别、票据处理

部署前准备

开发环境要求

项目 要求
IDE Android Studio Arctic Fox (2020.3.1) 或更高版本
Android SDK compileSdk 32
Android NDK 21.4.7075529
CMake 3.22.1
Gradle 7.0+
JDK 1.8
目标设备 ARM64架构(arm64-v8a)
最低系统版本 Android 5.0 (API 21)
目标系统版本 Android 11 (API 30)

注: 设备需先连接adb工具,执行adb root,再执行adb shell setenforce 0

必需软件安装

Android Studio配置

1. 安装NDK和CMake

打开 Android Studio → Settings → Appearance & Behavior → System Settings → Android SDK

  • 切换到 SDK Tools 标签页

勾选以下组件并安装:

  • NDK (Side by side) → 选择版本 21.4.7075529
  • CMake → 选择版本 3.22.1
  • Android SDK Build-Tools
  • Android SDK Platform-Tools

2. 配置环境变量(Windows系统)

ANDROID_NDK_HOME = C:\Users\[用户名]\AppData\Local\Android\Sdk\ndk\21.4.7075529

Gradle配置

项目根目录 build.gradle:

plugins {
    alias(libs.plugins.android.application) apply false
    alias(libs.plugins.kotlin.android) apply false
}

项目依赖库

Java/Kotlin依赖

Native依赖库(.so文件)

项目依赖以下预编译的Native库(官网联系我们获取),需放置在 app/src/main/jniLibs/arm64-v8a/ 目录:

库名称 说明
libedge_computing_market.so 主算法库(项目编译产物)
libinference.so 推理引擎
libopencv_java4.so OpenCV图像处理库
libfacerecognition.so 人脸识别算法库
libmaskdet.so 口罩检测算法库
libpipeline.so 手势识别Pipeline库
libdeepocr.so OCR文字识别库
libzbar.so 二维码识别库
libzbar_ex.so 二维码扩展库
libjsoncpp.so JSON解析库
libc++_shared.so C++运行时库

模型文件部署

模型文件需部署到设备的指定目录,项目默认路径结构:

/data/user/0/com.quectel.edgecomputingmarket/files/model/
├── face        # 子模块名
│   └── sim.engine    # 子模块模型文件
├── hand
│   └── sim.engine
├── person
│   └── sim.engine
└── reid
    └── sim.engine

模型核心信息

模型算法列表

序号 算法名称 模型类型 输入尺寸 硬件加速 说明
1 人脸识别 识别模型 256×256 DSP 识别图片中的人脸
2 口罩检测 检测模型 640×640 DSP 三分类:正确/错误/未佩戴
3 手势检测 检测模型 640×640 DSP 检测手势区域
4 OCR文字识别 识别模型 - DSP 文字内容识别
5 二维码识别 解码算法 - CPU 支持QR Code等多种码制

各算法详细说明

人脸识别

功能模块:

  • 人脸检测:定位图片/视频流中的人脸位置,输出边界框坐标
  • 关键点定位:提取5个面部关键点(左眼、右眼、鼻尖、左嘴角、右嘴角)
  • 特征提取:生成128维人脸特征向量
  • 人脸比对:通过余弦相似度计算人脸相似性

技术参数:

参数 数值
检测置信度阈值 0.5
NMS阈值 0.5
最小人脸面积比例 1%(图像面积)
特征维度 128维
相似度阈值 0.6
口罩检测

检测类别:

类别ID 类别名称 说明
0 Correctly_Worn 正确佩戴口罩
1 Not_Worn 未佩戴口罩
2 Incorrectly_Worn 错误佩戴口罩(仅遮口/仅遮鼻等)

技术参数:

参数 默认值
置信度阈值 0.25
NMS阈值 0.45
最小检测框面积 1%(图像面积)
手势识别

支持的手势类型:

序号 手势名称 说明
1 like 点赞手势
2 dislike 踩/倒赞手势
3 call 打电话手势
4 fist 握拳
5 four 四指手势
6 mute 静音手势
7 ok OK手势
8 one 食指手势
9 palm 手掌张开
10 other 其他手势
11 three 三指手势
12 yeah 胜利/V字手势

Pipeline流程: 检测 → 裁剪 → 分类

OCR文字识别

功能特性:

  • 支持多行文本检测
  • 自动识别中英文混合文本
  • 返回文本内容和位置信息

技术参数:

参数 数值
置信度阈值 0.2
输入尺寸 256×256
支持语言 中文、英文
二维码识别

支持的码制:

  • QR Code
  • EAN-13(可配置关闭)
  • 其他ZBar支持的码制

配置参数:

g_qrScanner->set_config(ZBAR_QRCODE, ZBAR_CFG_X_DENSITY, 4);
g_qrScanner->set_config(ZBAR_QRCODE, ZBAR_CFG_Y_DENSITY, 4);

模型移植流程(以人脸识别为例)

本小节以人脸识别为例,详细介绍如何从零开始在Android Studio中集成算法模型。

项目结构创建

app/src/main/
├── cpp/
│   ├── CMakeLists.txt           # CMake配置文件
│   ├── edge_computing_market.cpp             # JNI接口实现
│   └── include/                 # 头文件目录
│       ├── faceRecogInterfaceEX.hpp
│       ├── SNPEClass.hpp
│       └── ...
├── java/com/quectel/edgecomputingmarket/
│   └── manager/
│       └── JniManager.java      # JNI管理类
├── jniLibs/arm64-v8a/           # Native库目录
│   ├── libSNPE.so
│   ├── libopencv_java4.so
│   └── ...
└── assets/models/
    └── face_model/
        └── sim.engine

CMake配置

创建 app/src/main/cpp/CMakeLists.txt:

cmake_minimum_required(VERSION 3.22.1)
project("edge_computing_market" LANGUAGES CXX)

# ========== 路径配置 ==========
set(THIRD_PARTY_SO_ROOT "${CMAKE_SOURCE_DIR}/../jniLibs")
set(INCLUDE_ROOT "${CMAKE_SOURCE_DIR}/include")
set(LOCAL_SRC_DIR "${CMAKE_SOURCE_DIR}")
set(SUPPORTED_ABI "arm64-v8a")
set(THIRD_PARTY_SO_DIR "${THIRD_PARTY_SO_ROOT}/${SUPPORTED_ABI}")

# ========== 导入预编译库 ==========

# OpenCV库
set(OPENCV_LIB_NAME "opencv_java4")
add_library(${OPENCV_LIB_NAME} SHARED IMPORTED)
set_target_properties(${OPENCV_LIB_NAME} PROPERTIES
    IMPORTED_LOCATION "${THIRD_PARTY_SO_DIR}/lib${OPENCV_LIB_NAME}.so"
)

# SNPE推理引擎
set(SNPE_LIB_NAME "SNPE")
add_library(${SNPE_LIB_NAME} SHARED IMPORTED)
set_target_properties(${SNPE_LIB_NAME} PROPERTIES
    IMPORTED_LOCATION "${THIRD_PARTY_SO_DIR}/lib${SNPE_LIB_NAME}.so"
)

# C++运行时
set(C_SHARED_LIB_NAME "c++_shared")
add_library(${C_SHARED_LIB_NAME} SHARED IMPORTED)
set_target_properties(${C_SHARED_LIB_NAME} PROPERTIES
    IMPORTED_LOCATION "${THIRD_PARTY_SO_DIR}/lib${C_SHARED_LIB_NAME}.so"
)

# 人脸识别库
set(FACE_LIB_NAME "facerecognition")
add_library(${FACE_LIB_NAME} SHARED IMPORTED)
set_target_properties(${FACE_LIB_NAME} PROPERTIES
    IMPORTED_LOCATION "${THIRD_PARTY_SO_DIR}/lib${FACE_LIB_NAME}.so"
)

# ========== 头文件配置 ==========
function(recursive_include dir)
    if(IS_DIRECTORY ${dir})
        include_directories(${dir})
        file(GLOB SUB_DIRS RELATIVE ${dir} "${dir}/*")
        foreach(sub_dir ${SUB_DIRS})
            set(full_sub_dir "${dir}/${sub_dir}")
            if(IS_DIRECTORY ${full_sub_dir})
                recursive_include(${full_sub_dir})
            endif()
        endforeach()
    endif()
endfunction()
recursive_include(${INCLUDE_ROOT})

# ========== 编译edge_computing_market.so ==========
file(GLOB LOCAL_SRC_FILES
    "${LOCAL_SRC_DIR}/edge_computing_market.cpp"
)

find_library(LOG_LIB log REQUIRED)
find_library(JNI_GRAPHICS_LIB jnigraphics REQUIRED)

add_library(edge_computing_market SHARED ${LOCAL_SRC_FILES})

target_compile_features(edge_computing_market PRIVATE cxx_std_17)
target_compile_options(edge_computing_market PRIVATE
    -frtti
    -fexceptions
    -Wno-error=format-security
    -fPIC
)

target_link_libraries(edge_computing_market PRIVATE
    ${C_SHARED_LIB_NAME}
    ${FACE_LIB_NAME}
    ${OPENCV_LIB_NAME}
    ${SNPE_LIB_NAME}
    ${JNI_GRAPHICS_LIB}
    ${LOG_LIB}
    c m dl atomic
)

JNI接口层实现

Java层接口定义

创建 JniManager.java:

package com.quectel.edgecomputingmarket.manager;

public class JniManager {
    private static final String TAG = "JniManager";
    private static final String SO_NAME = "edge_computing_market";
    private static volatile JniManager INSTANCE;
    private static boolean isSoLoaded = false;

    // 加载Native库
    static {
        try {
            System.loadLibrary(SO_NAME);
            isSoLoaded = true;
        } catch (UnsatisfiedLinkError e) {
            isSoLoaded = false;
        }
    }

    // 单例模式
    public static JniManager getInstance() {
        if (INSTANCE == null) {
            synchronized (JniManager.class) {
                if (INSTANCE == null) {
                    INSTANCE = new JniManager();
                }
            }
        }
        return INSTANCE;
    }

    // ========== 人脸识别接口 ==========

    /**
     * 设置SNPE环境变量
     * @param soPath Native库路径
     */
    public native void setAdspEnv(String soPath);

    /**
     * 初始化人脸识别模型
     * @param faceDetModelPath 人脸检测模型路径
     * @param faceLandmarkModelPath 关键点模型路径
     * @param faceRegModelPath 特征提取模型路径
     * @param faceDbPath 人脸库路径
     * @return 0-成功,负数-错误码
     */
    public native int initFaceRecognition(
        String faceDetModelPath,
        String faceLandmarkModelPath,
        String faceRegModelPath,
        String faceDbPath
    );

    /**
     * 从图片进行人脸识别
     * @param imagePath 输入图片路径
     * @param saveDir 结果图片保存目录
     * @return 结果图片路径
     */
    public native String faceRecognitionFromImage(String imagePath, String saveDir);

    /**
     * 从相机流进行实时人脸识别
     * @param mergedYuvData YUV数据
     * @param width 图像宽度
     * @param height 图像高度
     * @param rotation 旋转角度
     * @return 检测结果数组 [人脸数, x, y, w, h, id, score, 关键点数, 关键点坐标...]
     */
    public native float[] faceRecognitionFromCameraX(
        byte[] mergedYuvData,
        int width,
        int height,
        int rotation
    );

    /**
     * 释放人脸识别资源
     */
    public native void releaseCameraFaceRecog();
}
C++层JNI实现

创建 edge_computing_market.cpp(核心代码片段):

#include <jni.h>
#include <string>
#include <vector>
#include <android/log.h>
#include "faceRecogInterfaceEX.hpp"
#include "opencv2/opencv.hpp"

#define LOG_TAG "JNI_edge_computing_market"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)

// 全局人脸识别实例
static std::unique_ptr<FaceRecogInterFaceEX::FaceRecogIFEX> g_frIfEx = nullptr;
static std::vector<std::vector<float>> g_faceFeatures;
static int g_validFeatureDim = 0;

void SetAdspLibraryPath(const std::string &nativeLibPath) {
    std::stringstream spath;
    std::stringstream spath1;
    spath << nativeLibPath << ";/vendor/lib/rfsa/adsp;/vendor/dsp;/vendor/dsp/cdsp";
    spath1 << nativeLibPath;
    LOGD("nativeLibPath is %s", nativeLibPath.c_str());
    if (setenv("ADSP_LIBRARY_PATH", spath.str().c_str(), 1 /*override*/) == 0) {
        LOGD("SNPE环境配置成功");
    } else {
        LOGD("SNPE环境配置失败");
    }
    if (setenv("LD_LIBRARY_PATH", spath1.str().c_str(), 1 /*override*/) == 0) {
        LOGD("SNPE环境配置成功");
    } else {
        LOGD("SNPE环境配置失败");
    }
    LOGD("ADSP_LIBRARY_PATH is %s", getenv("ADSP_LIBRARY_PATH"));
    LOGD("LD_LIBRARY_PATH is %s", getenv("LD_LIBRARY_PATH"));
}

extern "C" JNIEXPORT void JNICALL
Java_com_quectel_edgecomputingmarket_manager_JniManager_setAdspEnv(
    JNIEnv *env,
    jobject thiz,
    jstring soLibraryPath
) {
    const char *ldPath = env->GetStringUTFChars(soLibraryPath, nullptr);

    if (ldPath == nullptr) {
        LOGE("环境变量路径为空!");
        goto end;
    }

    SetAdspLibraryPath(ldPath);

end:
    env->ReleaseStringUTFChars(soLibraryPath, ldPath);
}

/**
 * 初始化人脸识别模型
 */
extern "C"
JNIEXPORT jint JNICALL
Java_com_quectel_edgecomputingmarket_manager_JniManager_initFaceRecognition(
    JNIEnv *env, jobject thiz,
    jstring face_det_model_path,
    jstring face_landmark_model_path,
    jstring face_reg_model_path,
    jstring face_db_path) {

    try {
        if (g_frIfEx != nullptr) {
            LOGD("人脸识别已初始化");
            return 0;
        }

        // 获取模型路径
        char *detModel = const_cast<char *>(env->GetStringUTFChars(face_det_model_path, nullptr));
        char *landmarkModel = const_cast<char *>(env->GetStringUTFChars(face_landmark_model_path, nullptr));
        char *regModel = const_cast<char *>(env->GetStringUTFChars(face_reg_model_path, nullptr));
        char *dbPath = const_cast<char *>(env->GetStringUTFChars(face_db_path, nullptr));

        // 创建人脸识别实例
        g_frIfEx = std::make_unique<FaceRecogInterFaceEX::FaceRecogIFEX>(
            detModel, landmarkModel, regModel);

        int err = g_frIfEx->init();
        if (err != 0) {
            g_frIfEx.reset();
            return err;
        }

        // 加载人脸库
        std::vector<std::string> filePaths;
        glob(dbPath, filePaths);
        g_faceFeatures.clear();

        for (const auto &path: filePaths) {
            cv::Mat img = cv::imread(path);
            if (img.empty()) continue;

            std::vector<FaceRecogInterFaceEX::FaceResults_t> faceResults;
            err = g_frIfEx->detect_face(img, faceResults, 0.5, 0.5);

            if (err == 1 && !faceResults.empty() && !faceResults[0].feature.empty()) {
                g_faceFeatures.emplace_back(faceResults[0].feature);
                g_validFeatureDim = faceResults[0].feature.size();
            }
        }

        // 释放字符串资源
        env->ReleaseStringUTFChars(face_det_model_path, detModel);
        env->ReleaseStringUTFChars(face_landmark_model_path, landmarkModel);
        env->ReleaseStringUTFChars(face_reg_model_path, regModel);
        env->ReleaseStringUTFChars(face_db_path, dbPath);

        return g_faceFeatures.empty() ? -2 : 0;
    } catch (...) {
        return -5;
    }
}

/**
 * 相机流人脸识别
 */
extern "C"
JNIEXPORT jfloatArray JNICALL
Java_com_quectel_edgecomputingmarket_manager_JniManager_faceRecognitionFromCameraX(
    JNIEnv *env, jobject thiz,
    jbyteArray mergedYuvData_,
    jint width, jint height, jint rotation) {

    if (g_frIfEx == nullptr || g_faceFeatures.empty()) {
        jfloatArray emptyArray = env->NewFloatArray(1);
        float emptyVal = 0.0f;
        env->SetFloatArrayRegion(emptyArray, 0, 1, &emptyVal);
        return emptyArray;
    }

    // 获取YUV数据
    jbyte *mergedYuvData = env->GetByteArrayElements(mergedYuvData_, nullptr);

    // YUV转BGR
    cv::Mat yuvMat(height * 3 / 2, width, CV_8UC1, (unsigned char *) mergedYuvData);
    cv::Mat bgrMat;
    cv::cvtColor(yuvMat, bgrMat, cv::COLOR_YUV2BGR_I420);

    // 图像旋转
    cv::Mat rotatedMat;
    switch (rotation) {
        case 90: cv::rotate(bgrMat, rotatedMat, cv::ROTATE_90_CLOCKWISE); break;
        case 180: cv::rotate(bgrMat, rotatedMat, cv::ROTATE_180); break;
        case 270: cv::rotate(bgrMat, rotatedMat, cv::ROTATE_90_COUNTERCLOCKWISE); break;
        default: rotatedMat = bgrMat.clone(); break;
    }

    // 人脸检测
    std::vector<FaceRecogInterFaceEX::FaceResults_t> faceResults;
    g_frIfEx->detect_face(rotatedMat, faceResults, 0.5f, 0.5f);

    // 特征匹配
    for (auto &face: faceResults) {
        if (!face.feature.empty() && face.feature.size() == g_validFeatureDim) {
            std::pair<int, float> matchRes = g_frIfEx->cosine_similarity(
                g_faceFeatures, face.feature, 0.6f);
            face.code = matchRes.first;
        }
    }

    // 构建返回结果
    int resultSize = 1;
    for (const auto &face: faceResults) {
        resultSize += 7 + face.key_pts.size() * 2;
    }

    jfloatArray resultArray = env->NewFloatArray(resultSize);
    std::unique_ptr<float[]> resultData(new float[resultSize]);
    int idx = 0;
    resultData[idx++] = static_cast<float>(faceResults.size());

    for (const auto &face: faceResults) {
        resultData[idx++] = static_cast<float>(face.x);
        resultData[idx++] = static_cast<float>(face.y);
        resultData[idx++] = static_cast<float>(face.width);
        resultData[idx++] = static_cast<float>(face.height);
        resultData[idx++] = static_cast<float>(face.code);
        resultData[idx++] = 0.0f; // score
        resultData[idx++] = static_cast<float>(face.key_pts.size());
        for (const auto &pt: face.key_pts) {
            resultData[idx++] = static_cast<float>(pt.x);
            resultData[idx++] = static_cast<float>(pt.y);
        }
    }

    env->SetFloatArrayRegion(resultArray, 0, resultSize, resultData.get());
    env->ReleaseByteArrayElements(mergedYuvData_, mergedYuvData, JNI_ABORT);
    return resultArray;
}

/**
 * 释放资源
 */
extern "C"
JNIEXPORT void JNICALL
Java_com_quectel_edgecomputingmarket_manager_JniManager_releaseCameraFaceRecog(
    JNIEnv *env, jobject thiz) {
    g_frIfEx.reset();
    g_faceFeatures.clear();
    g_validFeatureDim = 0;
}

build.gradle配置

在 app/build.gradle 中添加:

android {
    // ... 其他配置 ...

    defaultConfig {
        // ... 其他配置 ...

        externalNativeBuild {
            cmake {
                cppFlags ""
                abiFilters 'arm64-v8a'
            }
        }
        ndk {
            abiFilters 'arm64-v8a'
        }
    }

    externalNativeBuild {
        cmake {
            path "src/main/cpp/CMakeLists.txt"
            version "3.22.1"
        }
    }
    ndkVersion '21.4.7075529'
}

使用示例

初始化模型
public class MainActivity extends AppCompatActivity {
    private JniManager jniManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // 获取JNI管理器实例
        jniManager = JniManager.getInstance();

        // 设置SNPE环境
        String nativeLibDir = getApplicationInfo().nativeLibraryDir;
        jniManager.setAdspEnv(nativeLibDir);

        // 初始化人脸识别
        String modelPath = getFilesDir() + "/model/face_model";
        String faceDbPath = getFilesDir() + "/model/facedb";

        int result = jniManager.initFaceRecognition(//参数传模型文件的路径);

        if (result == 0) {
            Log.d("EdgeComputingMarket", "人脸识别初始化成功");
        } else {
            Log.e("EdgeComputingMarket", "人脸识别初始化失败: " + result);
        }
    }
}
相机流人脸识别

以CameraX为例,需要导入相关依赖,也可自行选择别的相机实现方式:

// 使用CameraX获取预览帧
previewView.getPreviewStreamState().observe(this, state -> {
    // 相机准备就绪
});

// 在ImageAnalysis中处理帧数据
ImageAnalysis imageAnalysis = new ImageAnalysis.Builder()
    .setTargetResolution(new Size(640, 480))
    .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
    .build();

imageAnalysis.setAnalyzer(executor, image -> {
    // 获取YUV数据
    ImageProxy imageProxy = image;
    byte[] yuvData = yuv420ToNv21(imageProxy);

    // 调用人脸识别
    float[] results = jniManager.faceRecognitionFromCameraX(
        yuvData,
        imageProxy.getWidth(),
        imageProxy.getHeight(),
        getRotationDegrees()
    );

    // 解析结果
    if (results != null && results.length > 1) {
        int faceCount = (int) results[0];
        int idx = 1;
        for (int i = 0; i < faceCount; i++) {
            float x = results[idx++];
            float y = results[idx++];
            float w = results[idx++];
            float h = results[idx++];
            int id = (int) results[idx++];
            // 跳过score和关键点
            idx += 2 + (int) results[idx - 1] * 2;

            Log.d("FaceResult", "Face " + i + ": pos=(" + x + "," + y +
                "), id=" + id);
        }
    }

    image.close();
});
资源释放
@Override
protected void onDestroy() {
    super.onDestroy();
    if (jniManager != null) {
        jniManager.releaseCameraFaceRecog();
    }
}

Demo演示

左侧tab栏选择想要体验的功能,Local Picture是选择本地图片识别,Live Camera是调用设备摄像头识别,APM是查看设备性能变化以及运行日志。

选择Local Picture,点击Before区域的灰白区域即可上传图片。

选择Live Camera,点击开始拍照,即可开启摄像头识别。

常见问题排查

注意: 本项目需要使用到系统库,需确保targetSdk ≤ 30,在安装应用之前需要先adb连接设备执行 adb root 命令,然后执行 adb shell setenforce 0 命令,且需要在算法的so库初始化之前设置adsp环境变量,才能正常使用,参考上面的代码。

问题 可能原因 解决方案
算法时效性失效 算法初始化后会有一个小时的时效性,超时会失效 退出当前应用并清理后台重新启动
UnsatisfiedLinkError .so库未正确加载 检查jniLibs目录结构和架构是否匹配以及环境变量是否设置
模型初始化失败(-1) 模型文件不存在或路径错误 检查模型文件是否正确复制到设备
人脸库加载失败(-2) 人脸库图片格式不正确 确保使用标准JPEG格式图片
检测结果为空 图像格式或尺寸不正确,有些相机的角度不固定,导致拍摄出来的画面出现旋转镜像等问题 检查YUV数据格式和旋转角度,对传入的图片进行正确旋转
DSP不可用 设备不支持SNPE DSP 更换设备

更多算法,尽在AlgoStore算法超市

本文档当前仅提供部分算法的集成开发指南,如需获取更丰富、更全面的 AI 算法能力,欢迎前往 AlgoStore 算法超市,海量优质算法随心选用,满足多样化开发与落地需求。

AlgoStore算法超市

AlgoStore算法超市简介

AlgoStore算法超市是由移远通信(Quectel)开发的一站式AI算法服务平台。作为一站式AIoT算法平台,AlgoStore就像一个汇聚丰富前沿AI算法的"超级市场",凭借强大的算法适配能力、便捷的算法定制服务和完善的AIoT生态体系,让企业和开发者能够像"选购商品"般轻松获取合适的AI算法,助力其破解业务难题的同时,驱动产品加速创新。

AlgoStore算法超市入口

核心特性

  • 算法领先:汇聚了包括视觉、音频等多领域的先进AI算法,以上的核心算法经客户实际项目验证,并且不断迭代创新。
  • 硬件适配:算法全面覆盖Qualcomm、Unisoc、Rockchip、MediaTek等主流芯片,消除选型顾虑。
  • 深度定制:全球化布局与客户服务网络,针对差异化业务需求提供深度定制化服务,打造专属解决方案。

联系我们

若想要获取更多算法体验或者获取算法集成SDK,请前往官网联系我们!

AIDE开发工具链

AIDE开发工具链简介

移远通信一站式 AI 开发工具链 AIDE(AI Development Tools & Engines)是面向AI应用开发者和端侧设备厂商的一站式开发工具链平台,核心覆盖"模型转换 - 优化- 端侧推理"全链路,旨在解决多模型格式适配难多硬件平台兼容复杂端侧推理性能不足三大核心痛点,加速 AI 模型从训练完成到业务落地的周期。

AIDE开发工具链入口

核心模块

  • ModelForge AI模型转换与优化工具:支持多种主流模型格式上传与多芯片平台。通过通道量化、位宽量化、模型校准等多元量化优化技术,实现模型轻量化压缩与推理效率跃升,打造更适配各类智能终端场景的端侧部署模型,确保转换后模型精度与性能双达标。
  • ModelInfer AI推理引擎:深度适配CPU/GPU/NPU异构计算单元,支持多计算单元协同推理,显著降低延迟、提升硬件资源利用率,缩短从模型算法研发到应用落地的周期。

模型转换与部署

本节将通过示例演示将 mobilenet v3 的模型转化到端侧部署全过程。

模型转换

1. 模型上传

登录AI开放平台,选择AI开发工具链。在源模型界面选择合适的模型格式,然后点击同意隐私协议和使用条款。上传模型并等待完成。

2. 选择目标硬件

在目标硬件平台依次选择合适的目标硬件平台,芯片平台以及推理引擎。

3. 模型配置

在模型配置界面可以设置模型名称,输入尺寸会根据上传模型自动获取,也可根据实际需求设置。

模型量化可以减少模型大小并提升推理速度,但是会降低准确率。开启模型量化后,会出现如下量化参数界面。量化方法中可以选择是否启用通道优化,以进一步减小模型大小。接下来可以选择量化位宽,根据实际模型去设置输入的均值和方法。最后选择校准数据集,默认使用COCO数据集,也可以选择自定义数据集然后上传自己的校准数据集。

4. 确认配置

接下来将会展示模型转换的信息,确认无误就可以进行模型转换,如果有修改可以点击上一步进行修改。

5. 模型转换

模型转换成功后,可以点击按钮即可下载模型。

部署示例

1. cmake集成

由于ModelInfer SDK已经配置了集成的config.cmake,方便我们进行集成。我们只需要CMakeLists.txt文件中设置ModelInfer SDK的路径,通过find_package查找推理引擎SDK包,包含头文件,定义以及链接库文件即可以完成cmake集成,如下所示:

cmake_minimum_required(VERSION 3.1)
project(MODEL_INFER_TEST)
set(CMAKE_CXX_STANDARD 14)
set(OpenCV_DIR "${CMAKE_CURRENT_SOURCE_DIR}/third_party/OpenCV-android-sdk/sdk/native/jni")
find_package(OpenCV REQUIRED)
find_library(log-lib log )
set(ModelInfer_DIR ${CMAKE_CURRENT_SOURCE_DIR}/model_infer_sdk/0.0.1/Android_aarch64_Release_Q)
find_package(ModelInfer REQUIRED)
include_directories(${ModelInfer_INCLUDE_DIRS})
add_definitions(${ModelInfer_DEFINITIONS})
include_directories(${OpenCV_INCLUDE_DIRS})
add_executable(${PROJECT_NAME} main.cc)
target_link_libraries(${PROJECT_NAME}
    ${ModelInfer_LIBRARIES}
    ${OpenCV_LIBRARIES}
    log
)

2. SDK使用示例

本例以mobilenet分类算法部署的详细说明使用。

包含头文件:

#include "model_inference.hpp"

具体使用代码如下:

void test_qnn_sdk(const std::string& model_path, const std::string& image_path) {
#ifdef HAVE_INFERENCE_QNN
    std::unique_ptr qnnDelegate = std::make_unique();
    std::string auth_code = "your_auth_code"; // 替换为实际auth code
    int module = DEV_SG560D; // 替换为实际硬件,见:ql_slas.h,QuecSlasDev_e
    if (0 != qnnDelegate->Authenticate(module, auth_code)) {
        std::cout << "fail to authenticate\n";
        return;
    }
    if (0 != qnnDelegate->Init(model_path.c_str())) {
        std::cout << "fail to init\n";
        return;
    } else {
        std::cout << "init success\n";
    }
    std::vector input_arr(224*224*3); // 假设输入大小为3x224x224
    std::vector out_arr(1000);
    PreProcess(image_path, input_arr);
    std::vector input_buffers;
    input_buffers.emplace_back(input_arr.data());
    std::vector output_buffers;
    output_buffers.emplace_back(out_arr.data());
    qnnDelegate->Inference(input_buffers, output_buffers);
    PostProcess(out_arr);
#endif
}

首先需要声明推理引擎,本例中使用的是QnnEngine,通过认证函数Authenticate后方可以使用推理引擎,通过调用Init函数来进行初始化,需要注意的是通过模型转换平台下载的模型均已经加密,所以Init第二个参数需要设置true。

然后准备模型输入输出参数,ModelInfer引擎均支持多头输入输出。需要注意的是推理引擎并不会创建以及管理内存,所有的输入输出buffer的中内存的创建,管理以及释放需要由使用者进行操作。最后通过调用Inference函数既可以完成一次推理操作。PreProcess和PostProcess分别为mobilenet的前后处理过程。其具体实现如下:

void PreProcess(const std::string& image_path, std::vector& input_arr) {
    cv::Mat image = cv::imread(image_path, -1);
    cv::cvtColor(image, image, cv::COLOR_BGR2RGB);
    int h = image.rows;
    int w = image.cols;
    int target_size = 256; // 计算缩放比例
    float scale = target_size / (float)std::min(h, w);
    int new_h = static_cast(h * scale);
    int new_w = static_cast(w * scale);
    cv::Mat resized;
    cv::resize(image, resized, cv::Size(new_w, new_h), 0, 0, cv::INTER_LINEAR); // 中心裁剪到224x224
    int crop_size = 224;
    int start_y = (resized.rows - crop_size) / 2;
    int start_x = (resized.cols - crop_size) / 2;
    cv::Rect roi(start_x, start_y, crop_size, crop_size);
    resized = resized(roi).clone();
    cv::Mat float_img;
    resized.convertTo(float_img, CV_32FC3, 1.0 / 255.0);
    std::vector mean = {0.485, 0.456, 0.406}; // R, G, B
    std::vector std = {0.229, 0.224, 0.225};
    std::vector channels(3);
    cv::split(float_img, channels);
    for (int i = 0; i < 3; i++) {
        channels[i] = (channels[i] - mean[i]) / std[i];
    }
    cv::Mat normalized;
    cv::merge(channels, normalized);
    float *normalized_ptr = reinterpret_cast(normalized.data);
    std::copy(normalized_ptr, normalized_ptr + input_arr.size(), input_arr.begin());
}

void PostProcess(std::vector& out_arr) {
    auto probs = Softmax(out_arr.data(), out_arr.size());
    float sum = 0.0f;
    for (const auto& p : probs) {
        sum += p;
    }
    std::vector top5_idx = ArgSort(probs, 5);
    std::vector> results;
    for (size_t idx : top5_idx) {
        results.emplace_back(idx, probs[idx]);
    }
    std::vector class_names;
    std::ifstream ifs("./imagenet_classes.txt");
    if (!ifs.is_open()) {
        std::cerr << "Failed to open class names file.\n";
        return;
    }
    std::string line;
    while (std::getline(ifs, line)) {
        class_names.push_back(line);
    }
    std::cout << "Top-5 Predictions:\n";
    for (const auto& res : results) {
        std::cout << "Class ID: " << res.first << ", Probability: " << res.second << ", Class Name: " << class_names[res.first] << "\n";
    }
}

其中Softmax和ArgSort函数如下:

std::vector Softmax(const float* data, size_t size) {
    std::vector result(size);
    float max_val = *std::max_element(data, data + size);
    float sum = 0.0f; // For numerical stability
    for (size_t i = 0; i < size; i++) {
        result[i] = std::exp(data[i] - max_val);
        sum += result[i];
    } // Normalize
    for (size_t i = 0; i < size; i++) {
        result[i] /= sum;
    }
    return result;
}
std::vector ArgSort(const std::vector& probs, int k) {
    // Create index array
    std::vector indices(probs.size());
    std::iota(indices.begin(), indices.end(), 0); // Partial sort for top-k
    std::partial_sort(indices.begin(), indices.begin() + k, indices.end(), [&probs](size_t i1, size_t i2) {
        return probs[i1] > probs[i2];
    }); // Return only top-k indices
    return std::vector(indices.begin(), indices.begin() + k);
}

3. 编译运行

通过cmake编译出可以执行程序,push到终端设备上进行运行,可以获得结果。

adb shell "cd /data/local/tmp/model_infer_demo/ && chmod +x ./MODEL_INFER_TEST && export LD_LIBRARY_PATH=/data/local/tmp/model_infer_demo/:$LD_LIBRARY_PATH && ./MODEL_INFER_TEST ./mobilenetv3_ctx.bin ./20260204-101425.jpg"
init success
Top-5 Predictions:
Class ID: 340, Probability: 0.0700765, Class Name: zebra
Class ID: 396, Probability: 0.0140236, Class Name: lionfish
Class ID: 117, Probability: 0.0109065, Class Name: chambered nautilus
Class ID: 506, Probability: 0.0106358, Class Name: coil
Class ID: 56, Probability: 0.00643312, Class Name: king snake