更新到HyperLPR3版本

This commit is contained in:
tunmx
2023-02-27 15:47:55 +08:00
parent 7ae4d385e1
commit 0864e05f76
912 changed files with 8160 additions and 221461 deletions
+147
View File
@@ -0,0 +1,147 @@
//
// Created by tunm on 2023/1/25.
//
#include "hyper_lpr_sdk_internal.h"
#include "hyper_lpr_sdk.h"
#include "context_module/all.h"
P_HLPR_DataBuffer HLPR_CreateDataBufferEmpty() {
return new HLPR_DataBuffer();
}
HREESULT HLPR_DataBufferSetData(
P_HLPR_DataBuffer buffer, // [in] CameraStream handle - 相机流组件的句柄指针
const uint8_t *data, // [in] Raw data stream - 原始的数据流
int width, // [in] Image width - 图像宽度
int height // [in] Image height - 图像高度
) {
buffer->impl.SetDataBuffer(data, height, width);
return hyper::hRetOk;
}
HREESULT HLPR_DataBufferSetRotationMode(
P_HLPR_DataBuffer buffer, // [in] CameraStream handle - 相机流组件的句柄指针
HLPR_Rotation mode // [in] CameraRotation mode - 相机流组件旋转模式
) {
if (mode == 1) {
buffer->impl.SetRotationMode(hyper::ROTATION_90);
} else if (mode == 2) {
buffer->impl.SetRotationMode(hyper::ROTATION_180);
} else if (mode == 3) {
buffer->impl.SetRotationMode(hyper::ROTATION_270);
} else {
buffer->impl.SetRotationMode(hyper::ROTATION_0);
}
return hyper::hRetOk;
}
HREESULT HLPR_DataBufferSetStreamFormat(
P_HLPR_DataBuffer buffer, // [in] CameraStream handle - 相机流组件的句柄指针
HLPR_ImageFormat mode // [in] CameraRotation data format - 相机流组件数据格式
) {
// STREAM_RGB = 0,
// STREAM_BGR = 1,
// STREAM_RGBA = 2,
// STREAM_BGRA = 3,
// STREAM_YUV_NV12 = 4,
// STREAM_YUV_NV21 = 5,
if (mode == 0) {
buffer->impl.SetDataFormat(hyper::RGB);
} else if (mode == 1) {
buffer->impl.SetDataFormat(hyper::BGR);
} else if (mode == 2) {
buffer->impl.SetDataFormat(hyper::RGBA);
} else if (mode == 3) {
buffer->impl.SetDataFormat(hyper::BGRA);
} else if (mode == 4) {
buffer->impl.SetDataFormat(hyper::NV12);
} else if (mode == 5) {
buffer->impl.SetDataFormat(hyper::NV21);
}
return hyper::hRetOk;
}
P_HLPR_DataBuffer HLPR_CreateDataBuffer(P_HLPR_ImageData data) {
auto buffer = new HLPR_DataBuffer();
if (data->rotation == 1) {
buffer->impl.SetRotationMode(hyper::ROTATION_90);
} else if (data->rotation == 2) {
buffer->impl.SetRotationMode(hyper::ROTATION_180);
} else if (data->rotation == 3) {
buffer->impl.SetRotationMode(hyper::ROTATION_270);
} else {
buffer->impl.SetRotationMode(hyper::ROTATION_0);
}
if (data->format == 0) {
buffer->impl.SetDataFormat(hyper::RGB);
} else if (data->format == 1) {
buffer->impl.SetDataFormat(hyper::BGR);
} else if (data->format == 2) {
buffer->impl.SetDataFormat(hyper::RGBA);
} else if (data->format == 3) {
buffer->impl.SetDataFormat(hyper::BGRA);
} else if (data->format == 4) {
buffer->impl.SetDataFormat(hyper::NV12);
} else if (data->format == 5) {
buffer->impl.SetDataFormat(hyper::NV21);
}
buffer->impl.SetDataBuffer(data->data, data->height, data->width);
return buffer;
}
HREESULT HLPR_ReleaseDataBuffer(P_HLPR_DataBuffer buffer) {
delete buffer;
return hyper::hRetOk;
}
P_HLPR_Context HLPR_CreateContext(P_HLPR_ContextConfiguration configuration) {
auto ctx = new HLPR_Context();
ctx->impl.Initialize(
configuration->models_path,
configuration->max_num,
hyper::DetectLevel(configuration->det_level),
configuration->threads,
configuration->use_half,
configuration->box_conf_threshold,
configuration->nms_threshold,
configuration->rec_confidence_threshold);
return ctx;
}
HREESULT HLPR_ContextUpdateStream(P_HLPR_Context ctx, P_HLPR_DataBuffer buffer, P_HLPR_PlateResultList results) {
ctx->impl(buffer->impl);
auto &list = ctx->impl.getMObjectResults();
results->plate_size = list.size();
results->plates = (P_HLPR_PlateResult) list.data();
return hyper::hRetOk;
}
HREESULT HLPR_ContextQueryStatus(P_HLPR_Context ctx) {
return ctx->impl.getMInitStatus();
}
HREESULT HLPR_ReleaseContext(P_HLPR_Context ctx) {
delete ctx;
return hyper::hRetOk;
}
//HREESULT HLPR_DataBufferTest(P_HLPR_DataBuffer buffer, const char *save_path) {
// cv::Mat image = buffer->impl.GetScaledImage(1.0f, true);
// cv::imwrite(save_path, image);
//
// return 0;
//}
+255
View File
@@ -0,0 +1,255 @@
//
// Created by tunm on 2023/1/25.
//
#ifndef ZEPHYRLPR_HYPER_LPR_SDK_H
#define ZEPHYRLPR_HYPER_LPR_SDK_H
#include <stdint.h>
#if defined(_WIN32)
#ifdef HYPER_BUILD_SHARED_LIB
#define HYPER_CAPI_EXPORT __declspec(dllexport)
#else
#define HYPER_CAPI_EXPORT
#endif
#else
#define HYPER_CAPI_EXPORT __attribute__((visibility("default")))
#endif // _WIN32
#ifdef __cplusplus
extern "C" {
#endif
/**
* API Result - API调用结果
* */
typedef int HREESULT;
typedef enum HResultCode{
Ok = 0,
Err = -1,
} HResultCode;
/**
* camera stream format - 支持的相机流格式
* Contains several common camera stream formats on the market -
* 包含了几款市面上常见的相机流格式
*/
typedef enum HLPR_ImageFormat {
STREAM_RGB = 0, ///< Image in RGB format - RGB排列格式的图像
STREAM_BGR = 1, ///< Image in BGR format (Opencv Mat default) - BGR排列格式的图像(OpenCV的Mat默认)
STREAM_RGBA = 2, ///< Image in RGB with alpha channel format - 带alpha通道的RGB排列格式的图像
STREAM_BGRA = 3, ///< Image in BGR with alpha channel format - 带alpha通道的BGR排列格式的图像
STREAM_YUV_NV12 = 4, ///< Image in YUV NV12 format - YUV NV12排列的图像格式
STREAM_YUV_NV21 = 5, ///< Image in YUV NV21 format - YUV NV21排列的图像格式
} HLPR_ImageFormat;
/**
* Camera picture corner mode - 相机画面转角模式
* To cope with the rotation of some devices, four image rotation modes are provided here -
* 为应对某些设备的画面自带旋转,这里提供四种图像旋转模式
*/
typedef enum HLPR_Rotation {
CAMERA_ROTATION_0 = 0, ///< 0 degree - 0
CAMERA_ROTATION_90 = 1, ///< 90 degree - 90
CAMERA_ROTATION_180 = 2, ///< 180 degree - 180
CAMERA_ROTATION_270 = 3, ///< 270 degree - 270
} HLPR_Rotation;
/**
* Image Buffer Data struct - 图像数据流结构
* */
typedef struct HLPR_ImageData {
uint8_t *data; ///< Image data stream - 图像数据流
int width; ///< Width of the image - 宽
int height; ///< Height of the image - 高
HLPR_ImageFormat format; ///< Format of the image - 传入需要解析数据流格式
HLPR_Rotation rotation; ///< The rotation Angle of the image - 图像的画面旋转角角度
} HLPR_ImageData, *P_HLPR_ImageData;
/**
* Plate layers - 车牌层数
* */
typedef enum HLPR_PlateLayers {
PLATE_LAYERS_MONO = 0, ///< 单层车牌
PLATE_LAYERS_DOUBLE, ///< 双层车牌
} HLPR_Layers;
/**
* Detector Level - 检测器等级
* */
typedef enum HLPR_DetectLevel {
DETECT_LEVEL_LOW = 0, ///< 高开销检测模式 (推荐)
DETECT_LEVEL_HIGH, ///< 低开销检测模式
} HLPR_DetectLevel;
/**
* PlateType Type - 车牌类型(中国)
* */
typedef enum HLPR_PlateType {
PLATE_TYPE_UNKNOWN = -1, ///< 未知车牌
PLATE_TYPE_BLUE = 0, ///< 蓝牌
PLATE_TYPE_YELLOW_SINGLE = 1, ///< 黄牌单层
PLATE_TYPE_WHILE_SINGLE = 2, ///< 白牌单层
PLATE_TYPE_GREEN = 3, ///< 绿牌新能源
PLATE_TYPE_BLACK_HK_MACAO = 4, ///< 黑牌港澳
PLATE_TYPE_HK_SINGLE = 5, ///< 香港单层
PLATE_TYPE_HK_DOUBLE = 6, ///< 香港双层
PLATE_TYPE_MACAO_SINGLE = 7, ///< 澳门单层
PLATE_TYPE_MACAO_DOUBLE = 8, ///< 澳门双层
PLATE_TYPE_YELLOW_DOUBLE = 9, ///< 黄牌双层
} HLPR_PlateType;
/**
* Plate Result - 车牌检测结果
* */
typedef struct HLPR_PlateResult {
float x1; ///< 左上角点x坐标
float y1; ///< 左上角点y坐标
float x2; ///< 右下角点x坐标
float y2; ///< 右下角点y坐标
HLPR_PlateType type; ///< 车牌类型
float text_confidence; ///< 置信度
char code[128]; ///< 车牌号码字符串
} HLPR_PlateResult, *P_HLPR_PlateResult;
/**
* Plate Result List - 车牌检测结果列表
* */
typedef struct HLPR_PlateResultList {
unsigned long plate_size; ///< 车牌数量
P_HLPR_PlateResult plates; ///< 检测车牌结果列表
} HLPR_PlateResultList, *P_HLPR_PlateResultList;
/**
* HyperLPR Context Instantiating parameters - Context的实例化参数对象
* */
typedef struct HLPR_ContextConfiguration {
char *models_path; ///< 模型文件地址
int max_num; ///< 识别最大数量
int threads; ///< 线程数 (推荐1)
bool use_half; ///< 是否使用半精度推理模式
float box_conf_threshold; ///< 检测框阈值
float nms_threshold; ///< 非极大值抑制阈值
float rec_confidence_threshold; ///< 识别置信度阈值
HLPR_DetectLevel det_level; ///< 检测器等级(推荐low)
} HLPR_ContextConfiguration, *P_HLPR_ContextConfiguration;
/**
* Data Buffer - 数据缓冲流
* */
typedef struct HLPR_DataBuffer HLPR_DataBuffer, *P_HLPR_DataBuffer;
/**
* The runtime object after HyperLPR is instantiated - 实例化运行时的Context对象
* */
typedef struct HLPR_Context HLPR_Context, *P_HLPR_Context;
/************************************************************************
* Carry parameters to create a data buffer stream instantiation object.
* 携带创建数据缓冲流实例化对象.
* [out] return: Model instant handle - 返回实例化后的指针句柄
************************************************************************/
HYPER_CAPI_EXPORT extern P_HLPR_DataBuffer HLPR_CreateDataBuffer(
P_HLPR_ImageData data // [in] Image Buffer Data struct - 图像数据流结构
);
/************************************************************************
* Create a data buffer stream instantiation object.
* 创建数据缓冲流实例化对象.
* [out] return: Model instant handle - 返回实例化后的指针句柄
************************************************************************/
HYPER_CAPI_EXPORT extern P_HLPR_DataBuffer HLPR_CreateDataBufferEmpty();
/************************************************************************
* Set the DataBuffer rotation mode.
* 设置DataBuffer旋转模式.
* [out] Result Code - 返回结果码
************************************************************************/
HYPER_CAPI_EXPORT extern HREESULT HLPR_DataBufferSetData(
P_HLPR_DataBuffer buffer, // [in] DataBuffer handle - 相机流组件的句柄指针
const uint8_t *data, // [in] Raw data stream - 原始的数据流
int width, // [in] Image width - 图像宽度
int height // [in] Image height - 图像高度
);
/************************************************************************
* Set the DataBuffer data format.
* 设置DataBuffer数据格式.
* [out] Result Code - 返回结果码
************************************************************************/
HYPER_CAPI_EXPORT extern HREESULT HLPR_DataBufferSetRotationMode(
P_HLPR_DataBuffer buffer, // [in] DataBuffer handle - 数据流组件的句柄指针
HLPR_Rotation mode // [in] DataBuffer mode - 数据流组件旋转模式
);
/************************************************************************
* Set the DataBuffer rotation mode.
* 设置DataBuffer旋转模式.
* [out] Result Code - 返回结果码
************************************************************************/
HYPER_CAPI_EXPORT extern HREESULT HLPR_DataBufferSetStreamFormat(
P_HLPR_DataBuffer buffer, // [in] DataBuffer handle - 数据流组件的句柄指针
HLPR_ImageFormat mode // [in] DataBuffer data format - 数据流组件数据格式
);
/************************************************************************
* Releases the DataBuffer object that has been instantiated.
* 释放已经被实例化后的模型对象.
* [out] Result Code - 返回结果码
************************************************************************/
HYPER_CAPI_EXPORT extern HREESULT HLPR_ReleaseDataBuffer(
P_HLPR_DataBuffer buffer // [in] DataBuffer handle - 相机流组件的句柄指针
);
/************************************************************************
* Create a data Context instantiation object.
* 创建Context实例化对象.
* [out] Result Code - 返回结果码
************************************************************************/
HYPER_CAPI_EXPORT extern P_HLPR_Context HLPR_CreateContext(
P_HLPR_ContextConfiguration configuration // [in] Context configuration - 配置表
);
/************************************************************************
* Query the Context instantiation state.
* 查询实例化后的状态.
* [out] Result Code - 返回结果码
************************************************************************/
HYPER_CAPI_EXPORT extern HREESULT HLPR_ContextQueryStatus(
P_HLPR_Context ctx // [in] Context handle - Context的指针句柄
);
/************************************************************************
* Update Data Buffer Stream.
* 喂入数据流并更新进行车牌识别.
* [out] Result Code - 返回结果码
************************************************************************/
HYPER_CAPI_EXPORT extern HREESULT HLPR_ContextUpdateStream(
P_HLPR_Context ctx, // [in] Context handle - Context的指针句柄
P_HLPR_DataBuffer buffer, // [in] DataBuffer handle - 数据流组件的句柄指针
P_HLPR_PlateResultList results // [out] Results List - 返回结果的列表
);
/************************************************************************
* Release Context.
* 释放Context的实例化对象.
* [out] Result Code - 返回结果码
************************************************************************/
HYPER_CAPI_EXPORT extern HREESULT HLPR_ReleaseContext(
P_HLPR_Context ctx // [in] Context handle - Context的指针句柄
);
//HYPER_CAPI_EXPORT extern HREESULT HLPR_DataBufferTest(P_HLPR_DataBuffer buffer, const char *save_path);
#ifdef __cplusplus
}
#endif
#endif //ZEPHYRLPR_HYPER_LPR_SDK_H
+19
View File
@@ -0,0 +1,19 @@
//
// Created by tunm on 2023/1/25.
//
#ifndef ZEPHYRLPR_HYPER_LPR_SDK_INTERNAL_H
#define ZEPHYRLPR_HYPER_LPR_SDK_INTERNAL_H
#include "hyper_lpr_sdk.h"
#include "context_module/all.h"
typedef struct HLPR_Context {
hyper::HyperLPRContext impl;
} HyperLPR_Context;
typedef struct HLPR_DataBuffer {
hyper::CameraBuffer impl;
} HLPR_DataBuffer;
#endif //ZEPHYRLPR_HYPER_LPR_SDK_INTERNAL_H
@@ -0,0 +1,162 @@
//
// Created by tunm on 2023/1/27.
//
#include <jni.h>
#include <string>
#include <android/log.h>
#include <stdlib.h>
#include <android/bitmap.h>
//#include <solexcv/log.h>
#include <opencv2/opencv.hpp>
#include "hyper_lpr_sdk.h"
#include "jni/common/jni_utils.h"
#include "log.h"
using namespace hyper;
extern "C" {
static double cost_ = 0.0;
JNIEXPORT long JNICALL HYPERLPR_API(core_HyperLPRCore_CreateRecognizerContext) (
JNIEnv *env,
jobject thiz,
jobject parameterObj) {
// get object class
jclass parameterCls = env->GetObjectClass(parameterObj);
// get object method id
jmethodID getModelPathMethodId = env->GetMethodID(parameterCls, "getModelPath", "()Ljava/lang/String;");
jmethodID getThreadsMethodId = env->GetMethodID(parameterCls, "getThreads", "()I");
jmethodID isUseHalfMethodId = env->GetMethodID(parameterCls, "isUseHalf", "()Z");
jmethodID getBoxConfThresholdMethodId = env->GetMethodID(parameterCls, "getBoxConfThreshold", "()F");
jmethodID getNmsThresholdMethodId = env->GetMethodID(parameterCls, "getNmsThreshold", "()F");
jmethodID getRecConfidenceThresholdMethodId = env->GetMethodID(parameterCls, "getRecConfidenceThreshold", "()F");
jmethodID getDetLevelMethodId = env->GetMethodID(parameterCls, "getDetLevel", "()I");
jmethodID getMaxNumMethodId = env->GetMethodID(parameterCls, "getMaxNum", "()I");
// get model path
jstring modelPath = (jstring ) env->CallObjectMethod(parameterObj, getModelPathMethodId, NULL);
std::string path = jstring2str(env, modelPath);
// get number of threads
jint threads = env->CallIntMethod(parameterObj, getThreadsMethodId, NULL);
// get is use half
jboolean useHalf = env->CallBooleanMethod(parameterObj, isUseHalfMethodId, NULL);
// get box conf threshold
jfloat boxConfThreshold = env->CallFloatMethod(parameterObj, getBoxConfThresholdMethodId, NULL);
// get nms threshold
jfloat nmsThreshold = env->CallFloatMethod(parameterObj, getNmsThresholdMethodId, NULL);
// get rec confidence threshold
jfloat textThreshold = env->CallFloatMethod(parameterObj, getRecConfidenceThresholdMethodId, NULL);
// get detect level
jint detLevel = env->CallIntMethod(parameterObj, getDetLevelMethodId, NULL);
// get max num level
jint maxNum = env->CallIntMethod(parameterObj, getMaxNumMethodId, NULL);
LOGD("input path: %s", path.c_str());
LOGD("threads: %d", threads);
LOGD("use half: %d", useHalf);
LOGD("det conf threshold: %f", boxConfThreshold);
LOGD("nms threshold: %f", nmsThreshold);
LOGD("text threshold: %f", textThreshold);
LOGD("det level: %d", detLevel);
// create context
HLPR_ContextConfiguration configuration = {0};
configuration.models_path = const_cast<char*>(path.c_str());
configuration.det_level = HLPR_DetectLevel(detLevel);
configuration.use_half = useHalf;
configuration.nms_threshold = nmsThreshold;
configuration.rec_confidence_threshold = textThreshold;
configuration.box_conf_threshold = boxConfThreshold;
configuration.threads = threads;
configuration.max_num = maxNum;
P_HLPR_Context ctx = HLPR_CreateContext(&configuration);
env->DeleteLocalRef(parameterCls);
return (long )ctx;
}
JNIEXPORT int JNICALL HYPERLPR_API(core_HyperLPRCore_ReleaseRecognizerContext) (
JNIEnv *env,
jobject thiz,
jlong handle) {
P_HLPR_Context ctx = (P_HLPR_Context ) handle;
return HLPR_ReleaseContext(ctx);
}
JNIEXPORT jobjectArray JNICALL HYPERLPR_API(core_HyperLPRCore_PlateRecognitionFromBuffer) (
JNIEnv *env,
jobject thiz,
jlong handle,
jbyteArray buf,
jint height,
jint width,
jint rotation,
jint format) {
P_HLPR_Context ctx = (P_HLPR_Context ) handle;
uint8_t *pBuf = (uint8_t *) env->GetByteArrayElements(buf, 0);
// create ImageData
HLPR_ImageData data = {0};
data.data = pBuf;
data.width = width;
data.height = height;
data.format = HLPR_ImageFormat(format);
data.rotation = HLPR_Rotation(rotation);
// create DataBuffer
P_HLPR_DataBuffer buffer = HLPR_CreateDataBuffer(&data);
// exec plate recognition
HLPR_PlateResultList results = {0};
cost_ = (double)cv::getTickCount();
HLPR_ContextUpdateStream(ctx, buffer, &results);
cost_ = ((double)cv::getTickCount() - cost_) / cv::getTickFrequency();
LOGD("cost: %f", cost_);
jobjectArray jPlateArray = nullptr;
jclass jPlateCls = env->FindClass("com/hyperai/hyperlpr3/bean/Plate");
// get object method id
jmethodID plateClsInitId = env->GetMethodID(jPlateCls, "<init>", "()V");
jmethodID setX1MethodId = env->GetMethodID(jPlateCls, "setX1", "(F)V");
jmethodID setY1MethodId = env->GetMethodID(jPlateCls, "setY1", "(F)V");
jmethodID setX2MethodId = env->GetMethodID(jPlateCls, "setX2", "(F)V");
jmethodID setY2MethodId = env->GetMethodID(jPlateCls, "setY2", "(F)V");
// jmethodID setLayersMethodId = env->GetMethodID(jPlateCls, "setLayers", "(I)V");
jmethodID setTypeMethodId = env->GetMethodID(jPlateCls, "setType", "(I)V");
jmethodID setConfidenceMethodId = env->GetMethodID(jPlateCls, "setConfidence", "(F)V");
jmethodID setCodeMethodId = env->GetMethodID(jPlateCls, "setCode", "(Ljava/lang/String;)V");
int total = results.plate_size;
jPlateArray = env->NewObjectArray(total, jPlateCls, 0);
for (int i = 0; i < total; ++i) {
auto &plate = results.plates[i];
// set in location
jobject jPlate = env->NewObject(jPlateCls, plateClsInitId);
env->CallVoidMethod(jPlate, setX1MethodId, plate.x1);
env->CallVoidMethod(jPlate, setY1MethodId, plate.y1);
env->CallVoidMethod(jPlate, setX2MethodId, plate.x2);
env->CallVoidMethod(jPlate, setY2MethodId, plate.y2);
// set in types
// env->CallVoidMethod(jPlate, setLayersMethodId, plate.layers);
env->CallVoidMethod(jPlate, setTypeMethodId, plate.type);
// set in text_confidence
env->CallVoidMethod(jPlate, setConfidenceMethodId, plate.text_confidence);
// set in plate code
// env->CallObjectMethod(jPlate, setCodeMethodId, stringTojstring(env, plate.code));
env->CallObjectMethod(jPlate, setCodeMethodId, env->NewStringUTF(plate.code));
env->SetObjectArrayElement(jPlateArray, i, jPlate);
env->DeleteLocalRef(jPlate);
}
env->DeleteLocalRef(jPlateCls);
return jPlateArray;
}
} // extern
+63
View File
@@ -0,0 +1,63 @@
//
// Created by tunm on 2023/1/26.
//
#ifndef ZEPHYRLPR_JNI_UTILS_H
#define ZEPHYRLPR_JNI_UTILS_H
namespace hyper {
#define HYPERLPR_API(sig) Java_com_hyperai_hyperlpr3_##sig
inline jstring stringTojstring(JNIEnv *env, const char *pat) {
jclass strClass = (env)->FindClass("java/lang/String");
jmethodID ctorID = (env)->GetMethodID(strClass, "<init>", "([BLjava/lang/String;)V");
jbyteArray bytes = (env)->NewByteArray(strlen(pat));
(env)->SetByteArrayRegion(bytes, 0, strlen(pat), (jbyte *) pat);
jstring encoding = (env)->NewStringUTF("GBK");
return (jstring)(env)->NewObject(strClass, ctorID, bytes, encoding);
}
inline jstring JNIString_getString(JNIEnv *env, const char *c_str) {
//C 返回 java 字符串
jclass str_cls = (env)->FindClass("java/lang/String");
jmethodID jmid = (env)->GetMethodID(str_cls, "<init>", "([BLjava/lang/String;)V");
//jstring -> jbyteArray
jbyteArray bytes = (env)->NewByteArray(strlen(c_str));
// 将Char * 赋值到 bytes
(env)->SetByteArrayRegion(bytes, 0, strlen(c_str), (jbyte *) c_str);
jstring charsetName = (env)->NewStringUTF("GB2312");
return (jstring)(env)->NewObject(str_cls, jmid, bytes, charsetName);
}
inline std::string jstring2str(JNIEnv *env, jstring jstr) {
char *rtn = NULL;
jclass clsstring = env->FindClass("java/lang/String");
jstring strencode = env->NewStringUTF("GB2312");
jmethodID mid =
env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");
jbyteArray barr = (jbyteArray) env->CallObjectMethod(jstr, mid, strencode);
jsize alen = env->GetArrayLength(barr);
jbyte *ba = env->GetByteArrayElements(barr, JNI_FALSE);
if (alen > 0) {
rtn = (char *) malloc(alen + 1);
memcpy(rtn, ba, alen);
rtn[alen] = 0;
}
env->ReleaseByteArrayElements(barr, ba, 0);
std::string stemp(rtn);
free(rtn);
return stemp;
}
} // namespace
#endif //ZEPHYRLPR_JNI_UTILS_H
View File
+75
View File
@@ -0,0 +1,75 @@
//
// Created by tunm on 2023/1/26.
//
#include <iostream>
#include "hyper_lpr_sdk.h"
#include "opencv2/opencv.hpp"
static const std::vector<std::string> TYPES = {"蓝牌", "黄牌单层", "白牌单层", "绿牌新能源", "黑牌港澳", "香港单层", "香港双层", "澳门单层", "澳门双层", "黄牌双层"};
int main(int argc, char **argv) {
char *model_path = argv[1];
char *image_path = argv[2];
cv::Mat image = cv::imread(image_path);
// create ImageData
HLPR_ImageData data = {0};
data.data = image.ptr<uint8_t>(0);
data.width = image.cols;
data.height = image.rows;
data.format = STREAM_BGR;
data.rotation = CAMERA_ROTATION_0;
// create DataBuffer
P_HLPR_DataBuffer buffer = HLPR_CreateDataBuffer(&data);
// create context
HLPR_ContextConfiguration configuration = {0};
configuration.models_path = model_path;
configuration.max_num = 5;
configuration.det_level = DETECT_LEVEL_LOW;
configuration.use_half = false;
configuration.nms_threshold = 0.5f;
configuration.rec_confidence_threshold = 0.5f;
configuration.box_conf_threshold = 0.30f;
configuration.threads = 1;
P_HLPR_Context ctx = HLPR_CreateContext(&configuration);
HREESULT ret = HLPR_ContextQueryStatus(ctx);
if (ret != HResultCode::Ok) {
printf("create error.\n");
return -1;
}
// exec plate recognition
HLPR_PlateResultList results = {0};
double time;
time = (double)cv::getTickCount();
HLPR_ContextUpdateStream(ctx, buffer, &results);
time = ((double)cv::getTickCount() - time) / cv::getTickFrequency();
printf("cost: %f\n", time);
for (int i = 0; i < results.plate_size; ++i) {
std::string type;
if (results.plates[i].type == HLPR_PlateType::PLATE_TYPE_UNKNOWN) {
type = "未知";
} else {
type = TYPES[results.plates[i].type];
}
cv::rectangle(image, cv::Point2f(results.plates[i].x1, results.plates[i].y1), cv::Point2f(results.plates[i].x2, results.plates[i].y2),
cv::Scalar(100, 100, 200), 3);
printf("<%d> %s, %s, %f\n", i + 1, type.c_str(),
results.plates[i].code, results.plates[i].text_confidence);
}
// cv::imwrite("out.jpg", image);
cv::imshow("out", image);
cv::waitKey(0);
// release buffer
HLPR_ReleaseDataBuffer(buffer);
// release context
HLPR_ReleaseContext(ctx);
return 0;
}
+47
View File
@@ -0,0 +1,47 @@
//
// Created by tunm on 2023/1/22.
//
#include <iostream>
#include "opencv2/opencv.hpp"
#include "context_module/all.h"
#include "buffer_module/all.h"
using namespace hyper;
static const std::vector<std::string> TYPES = {"蓝牌", "黄牌单层", "白牌单层", "绿牌新能源", "黑牌港澳", "香港单层", "香港双层", "澳门单层", "澳门双层", "黄牌双层"};
int main(int argc, char **argv) {
char *model_path = argv[1];
char *image_path = argv[2];
cv::Mat image = cv::imread(image_path);
HyperLPRContext context;
auto ret = context.Initialize(model_path, 5, DetectLevel::DETECT_LEVEL_LOW);
if (ret != hRetOk) {
LOGE("Load error.");
return -1;
}
CameraBuffer buffer;
buffer.SetDataBuffer(image.data, image.rows, image.cols);
buffer.SetDataFormat(BGR);
buffer.SetRotationMode(ROTATION_0);
double time;
time = (double)cv::getTickCount();
context(buffer);
time = ((double)cv::getTickCount() - time) / cv::getTickFrequency();
LOGD("pipeline cost: %f", time);
auto &objs = context.getMObjectResults();
for (auto &obj: objs) {
cv::rectangle(image,
cv::Point2f(obj.x1, obj.y1),
cv::Point2f(obj.x2, obj.y2),
cv::Scalar(0, 0, 200),
2);
LOGD("[%s]%s", TYPES[obj.type].c_str(), obj.code);
LOGD("文本均值置信度: %f", obj.text_confidence);
}
cv::imshow("w", image);
cv::waitKey(0);
return 0;
}
+113
View File
@@ -0,0 +1,113 @@
//
// Created by Tunm-Air13 on 2023/2/8.
//
#include <iostream>
#include "opencv2/opencv.hpp"
//#include "loader_module/all.h"
#include "nn_implementation_module/all.h"
#include "configuration.h"
using namespace hyper;
int main(int argc, char **argv) {
char *model_path = argv[1];
char *image_path = argv[2];
int input_size = 320;
std::string backbone_path = std::string(model_path) + "/" + hyper::DETECT_LOW_BACKBONE_FILENAME;
std::string head_path = std::string(model_path) + "/" + hyper::DETECT_LOW_HEAD_FILENAME;
// std::string backbone_path = std::string(model_path) + "/" + hyper::DETECT_HIGH_BACKBONE_FILENAME;
// std::string head_path = std::string(model_path) + "/" + hyper::DETECT_HIGH_HEAD_FILENAME;
// int input_size = 640;
cv::Mat image = cv::imread(image_path);
// DetBackbone backbone;
// backbone.Initialize(backbone_model);
//
// DetHeader header;
// header.Initialize(header_model);
//
//
// backbone.Inference(image);
//
// header.Inference(backbone.getMOutputTensorInfoList()[0].GetDataAsFloat(),
// backbone.getMOutputTensorInfoList()[1].GetDataAsFloat(),
// backbone.getMOutputTensorInfoList()[1].GetDataAsFloat());
// for (int i = 0; i < 20; ++i) {
// std::cout << header.getMOutputTensorInfoList()[0].GetDataAsFloat()[i] << std::endl;
// }
// float *h = header.getMOutputTensorInfoList()[0].GetDataAsFloat();
// for (int i = 0; i < 6300 * 15; ++i) {
//// std::cout << backbone.m_output_feature_map_40p_.get()[i] << std::endl;
// FILE *fp = NULL;
// fp = fopen("head.txt", "a");
// fprintf(fp, "%f\n", h[i]);
// fclose(fp);
// }
//
// float *p40 = backbone.getMOutputTensorInfoList()[0].GetDataAsFloat();
// for (int i = 0; i < 45 * 40 *40; ++i) {
//// std::cout << backbone.m_output_feature_map_40p_.get()[i] << std::endl;
// FILE *fp = NULL;
// fp = fopen("40.txt", "a");
// fprintf(fp, "%f\n", p40[i]);
// fclose(fp);
// }
//
// std::cout << std::endl;
// float *p20 = backbone.getMOutputTensorInfoList()[1].GetDataAsFloat();
// for (int i = 0; i < 45 * 20 * 20; ++i) {
//// std::cout << backbone.m_output_feature_map_20p_.get()[i] << std::endl;
// FILE *fp = NULL;
// fp = fopen("20.txt", "a");
// fprintf(fp, "%f\n", p20[i]);
// fclose(fp);
// }
// std::cout << std::endl;
// float *p10 = backbone.getMOutputTensorInfoList()[2].GetDataAsFloat();
// for (int i = 0; i < 45 * 10 * 10; ++i) {
//// std::cout << backbone.m_output_feature_map_10p_.get()[i] << std::endl;
// FILE *fp = NULL;
// fp = fopen("10.txt", "a");
// fprintf(fp, "%f\n", p10[i]);
// fclose(fp);
// }
DetArch arch;
arch.Initialize(backbone_path, head_path, input_size);
double time;
time = (double)cv::getTickCount();
arch.Detection(image, true);
time = ((double)cv::getTickCount() - time) / cv::getTickFrequency();
auto &results = arch.m_results_;
for (auto &plate : results) {
std::cout << plate.x1 << ", " << plate.y1 << std::endl;
cv::rectangle(image, cv::Point2f(plate.x1, plate.y1), cv::Point2f(plate.x2, plate.y2),
cv::Scalar(100, 100, 200), 1);
for (int i = 0; i < 4; ++i) {
cv::line(image, cv::Point2f(plate.kps[i * 2 + 0], plate.kps[i * 2 + 1]),
cv::Point2f(plate.kps[i * 2 + 0], plate.kps[i * 2 + 1]), cv::Scalar(100, 220, 20), 1);
}
}
#ifdef BUILD_LINUX_ARM7
cv::imwrite("out.jpg", image);
#else
cv::imshow("w", image);
cv::waitKey(0);
#endif
return 0;
}
+17
View File
@@ -0,0 +1,17 @@
#pragma once
#ifndef ZEPHYRLPR_BASIC_TYPES_H
#define ZEPHYRLPR_BASIC_TYPES_H
#include "opencv2/opencv.hpp"
#include "log.h"
namespace hyper {
typedef std::vector<size_t> IndexList;
typedef cv::Size InputSize;
}
#endif // ZEPHYRLPR_BASIC_TYPES_H
+10
View File
@@ -0,0 +1,10 @@
//
// Created by tunm on 2023/1/25.
//
#ifndef ZEPHYRLPR_CAMERA_BUFFER_ALL_H
#define ZEPHYRLPR_CAMERA_BUFFER_ALL_H
#include "camera_buffer.h"
#endif //ZEPHYRLPR_CAMERA_BUFFER_ALL_H
+236
View File
@@ -0,0 +1,236 @@
//
// Created by Tunm-Air13 on 2022/4/24.
//
#include "camera_buffer.h"
namespace hyper {
CameraBuffer::CameraBuffer() {
config_.sourceFormat = MNN::CV::YUV_NV21;
config_.destFormat = MNN::CV::BGR;
config_.filterType = MNN::CV::NEAREST;
config_.wrap = MNN::CV::ZERO;
rotation_mode_ = ROTATION_0;
}
void CameraBuffer::SetDataBuffer(const uint8_t *data_buffer, int height, int width) {
this->buffer_ = data_buffer;
this->height_ = height;
this->width_ = width;
}
void CameraBuffer::SetRotationMode(ROTATION_MODE mode) {
rotation_mode_ = mode;
}
void CameraBuffer::SetDataFormat(DATA_FORMAT data_format) {
if (data_format == NV21) {
config_.sourceFormat = MNN::CV::YUV_NV21;
}
if (data_format == NV12) {
config_.sourceFormat = MNN::CV::YUV_NV12;
}
if (data_format == RGBA) {
config_.sourceFormat = MNN::CV::RGBA;
}
if (data_format == RGB) {
config_.sourceFormat = MNN::CV::RGB;
}
if (data_format == BGR) {
config_.sourceFormat = MNN::CV::BGR;
}
if (data_format == BGRA) {
config_.sourceFormat = MNN::CV::BGRA;
}
if (data_format == YCrCb) {
config_.sourceFormat = MNN::CV::YCrCb;
}
}
cv::Mat CameraBuffer::GetAffineRGBImage(const cv::Mat &affine_matrix, const int width_out, const int height_out) const {
int sw = width_;
int sh = height_;
int rot_sw = sw;
int rot_sh = sh;
MNN::CV::Matrix tr;
assert(affine_matrix.rows == 2);
assert(affine_matrix.cols == 3);
assert(affine_matrix.type() == CV_64F);
cv::Mat trans_matrix;
affine_matrix.convertTo(trans_matrix, CV_32F);
std::vector<float> tr_cv({1, 0, 0, 0, 1, 0, 0, 0, 1});
memcpy(tr_cv.data(), trans_matrix.data, sizeof(float) * 6);
tr.set9(tr_cv.data());
MNN::CV::Matrix tr_inv;
tr.invert(&tr_inv);
std::shared_ptr<MNN::CV::ImageProcess> process(
MNN::CV::ImageProcess::create(config_));
process->setMatrix(tr_inv);
cv::Mat img_out(height_out, width_out, CV_8UC3);
std::shared_ptr<MNN::Tensor> tensor(MNN::Tensor::create<uint8_t>(
std::vector<int>{1, height_out, width_out, 3}, img_out.data));
process->convert(buffer_, sw, sh, 0, tensor.get());
return img_out;
}
cv::Mat CameraBuffer::GetScaledImage(const float scale, bool with_rotation) {
int sw = width_;
int sh = height_;
int rot_sw = sw;
int rot_sh = sh;
// MNN::CV::Matrix tr;
std::shared_ptr<MNN::CV::ImageProcess> process(
MNN::CV::ImageProcess::create(config_));
if (rotation_mode_ == ROTATION_270 && with_rotation) {
float srcPoints[] = {
0.0f,
0.0f,
0.0f,
(float)(height_ - 1),
(float)(width_ - 1),
0.0f,
(float)(width_ - 1),
(float)(height_ - 1),
};
float dstPoints[] = {(float)(height_ * scale - 1),
0.0f,
0.0f,
0.0f,
(float)(height_ * scale - 1),
(float)(width_ * scale - 1),
0.0f,
(float)(width_ * scale - 1)};
tr_.setPolyToPoly((MNN::CV::Point *)dstPoints,
(MNN::CV::Point *)srcPoints, 4);
process->setMatrix(tr_);
int scaled_height = static_cast<int>(width_ * scale);
int scaled_width = static_cast<int>(height_ * scale);
cv::Mat img_out(scaled_height, scaled_width, CV_8UC3);
std::shared_ptr<MNN::Tensor> tensor(MNN::Tensor::create<uint8_t>(
std::vector<int>{1, scaled_height, scaled_width, 3}, img_out.data));
process->convert(buffer_, sw, sh, 0, tensor.get());
return img_out;
} else if (rotation_mode_ == ROTATION_90 && with_rotation) {
float srcPoints[] = {
0.0f,
0.0f,
0.0f,
(float)(height_ - 1),
(float)(width_ - 1),
0.0f,
(float)(width_ - 1),
(float)(height_ - 1),
};
float dstPoints[] = {
0.0f,
(float)(width_ * scale - 1),
(float)(height_ * scale - 1),
(float)(width_ * scale - 1),
0.0f,
0.0f,
(float)(height_ * scale - 1),
0.0f,
};
tr_.setPolyToPoly((MNN::CV::Point *)dstPoints,
(MNN::CV::Point *)srcPoints, 4);
process->setMatrix(tr_);
int scaled_height = static_cast<int>(width_ * scale);
int scaled_width = static_cast<int>(height_ * scale);
cv::Mat img_out(scaled_height, scaled_width, CV_8UC3);
std::shared_ptr<MNN::Tensor> tensor(MNN::Tensor::create<uint8_t>(
std::vector<int>{1, scaled_height, scaled_width, 3}, img_out.data));
process->convert(buffer_, sw, sh, 0, tensor.get());
return img_out;
} else if (rotation_mode_ == ROTATION_180 && with_rotation) {
float srcPoints[] = {
0.0f,
0.0f,
0.0f,
(float)(height_ - 1),
(float)(width_ - 1),
0.0f,
(float)(width_ - 1),
(float)(height_ - 1),
};
float dstPoints[] = {
(float)(width_ * scale - 1),
(float)(height_ * scale - 1),
(float)(width_ * scale - 1),
0.0f,
0.0f,
(float)(height_ * scale - 1),
0.0f,
0.0f,
};
tr_.setPolyToPoly((MNN::CV::Point *)dstPoints,
(MNN::CV::Point *)srcPoints, 4);
process->setMatrix(tr_);
int scaled_height = static_cast<int>(height_ * scale);
int scaled_width = static_cast<int>(width_ * scale);
cv::Mat img_out(scaled_height, scaled_width, CV_8UC3);
std::shared_ptr<MNN::Tensor> tensor(MNN::Tensor::create<uint8_t>(
std::vector<int>{1, scaled_height, scaled_width, 3}, img_out.data));
process->convert(buffer_, sw, sh, 0, tensor.get());
return img_out;
} else {
float srcPoints[] = {
0.0f,
0.0f,
0.0f,
(float)(height_ - 1),
(float)(width_ - 1),
0.0f,
(float)(width_ - 1),
(float)(height_ - 1),
};
float dstPoints[] = {
0.0f,
0.0f,
0.0f,
(float)(height_ * scale - 1),
(float)(width_ * scale - 1),
0.0f,
(float)(width_ * scale - 1),
(float)(height_ * scale - 1),
};
tr_.setPolyToPoly((MNN::CV::Point *)dstPoints,
(MNN::CV::Point *)srcPoints, 4);
process->setMatrix(tr_);
int scaled_height = static_cast<int>(height_ * scale);
int scaled_width = static_cast<int>(width_ * scale);
cv::Mat img_out(scaled_height, scaled_width, CV_8UC3);
std::shared_ptr<MNN::Tensor> tensor(MNN::Tensor::create<uint8_t>(
std::vector<int>{1, scaled_height, scaled_width, 3}, img_out.data));
process->convert(buffer_, sw, sh, 0, tensor.get());
return img_out;
}
}
cv::Mat CameraBuffer::GetAffineMatrix() const {
cv::Mat affine_matrix(3, 3, CV_32F);
tr_.get9((float *)affine_matrix.data);
cv::Mat affine = affine_matrix.rowRange(0, 2);
cv::Mat affine_64;
affine.convertTo(affine_64, CV_64F);
assert(affine_64.rows == 2);
assert(affine_64.cols == 3);
assert(affine_64.type() == CV_64F);
return affine_64;
}
int CameraBuffer::GetHeight() const {
return height_;
}
int CameraBuffer::GetWidth() const {
return width_;
}
int CameraBuffer::GetRotationMode() const{
return rotation_mode_;
}
}
+68
View File
@@ -0,0 +1,68 @@
//
// Created by Tunm-Air13 on 2022/4/24.
//
#ifndef SOLEXCV_CAMERA_BUFFER_H
#define SOLEXCV_CAMERA_BUFFER_H
#include <opencv2/opencv.hpp>
#include <MNN/ImageProcess.hpp>
#include <memory>
namespace hyper {
enum ROTATION_MODE {
ROTATION_0 = 0,
ROTATION_90 = 1,
ROTATION_180 = 2,
ROTATION_270 = 3
};
enum DATA_FORMAT {
NV21 = 0, NV12 = 1, RGBA = 2, RGB = 3, BGR = 4, BGRA = 5, YCrCb = 6
};
class CameraBuffer {
public:
CameraBuffer();
void SetDataBuffer(const uint8_t *data_buffer, int height, int width);
void SetRotationMode(ROTATION_MODE mode);
void SetDataFormat(DATA_FORMAT data_format);
cv::Mat GetAffineRGBImage(const cv::Mat &affine_matrix, const int width_out,
const int height_out) const;
cv::Mat GetScaledImage(const float scale, bool with_rotation);
cv::Mat GetAffineMatrix() const;
int GetHeight() const;
int GetWidth() const;
int GetRotationMode() const;
private:
const uint8_t *buffer_;
int buffer_size_;
std::vector<float> rotation_matrix;
int height_;
int width_;
MNN::CV::Matrix tr_;
ROTATION_MODE rotation_mode_;
MNN::CV::ImageProcess::Config config_;
std::shared_ptr <MNN::CV::ImageProcess> process_;
};
}
#endif //SOLEXCV_CAMERA_BUFFER_H
+29
View File
@@ -0,0 +1,29 @@
//
// Created by Tunm-Air13 on 2023/2/21.
//
#pragma once
#ifndef ZEPHYRLPR_CONFIGURATION_H
#define ZEPHYRLPR_CONFIGURATION_H
#include "basic_types.h"
#include <iostream>
#include "log.h"
namespace hyper {
const std::string DETECT_LOW_BACKBONE_FILENAME = "b320_backbone_h.mnn";
const std::string DETECT_LOW_HEAD_FILENAME = "b320_header_h.mnn";
const std::string DETECT_HIGH_BACKBONE_FILENAME = "b640x_backbone_h.mnn";
const std::string DETECT_HIGH_HEAD_FILENAME = "b640x_head_h.mnn";
const std::string CLS_MODEL_FILENAME = "litemodel_cls_96xh.mnn";
const std::string REC_MODEL_FILENAME = "rpv3_mdict_160_r3.mnn";
const InputSize REC_INPUT_SIZE = {160, 48};
const InputSize CLS_INPUT_SIZE = {96, 96};
const int REC_MAX_CHAR_NUM = 20;
const int REC_CHAR_CLASS_NUM = 78;
}
#endif //ZEPHYRLPR_CONFIGURATION_H
+11
View File
@@ -0,0 +1,11 @@
//
// Created by tunm on 2023/1/25.
//
#ifndef ZEPHYRLPR_HYPER_LPR_CONTEXT_ALL_H
#define ZEPHYRLPR_HYPER_LPR_CONTEXT_ALL_H
#include "hyper_lpr_context.h"
#include "hyper_lpr_common.h"
#endif //ZEPHYRLPR_HYPER_LPR_CONTEXT_ALL_H
+54
View File
@@ -0,0 +1,54 @@
//
// Created by tunm on 2023/1/25.
//
#pragma once
#ifndef ZEPHYRLPR_HYPER_LPR_COMMON_H
#define ZEPHYRLPR_HYPER_LPR_COMMON_H
#include "nn_implementation_module/all.h"
namespace hyper {
enum PlateType {
UNKNOWN = -1, ///< 未知车牌
BLUE = 0, ///< 蓝牌
YELLOW_SINGLE = 1, ///< 黄牌单层
WHILE_SINGLE = 2, ///< 白牌单层
GREEN = 3, ///< 绿牌新能源
BLACK_HK_MACAO = 4, ///< 黑牌港澳
HK_SINGLE = 5, ///< 香港单层
HK_DOUBLE = 6, ///< 香港双层
MACAO_SINGLE = 7, ///< 澳门单层
MACAO_DOUBLE = 8, ///< 澳门双层
YELLOW_DOUBLE = 9, ///< 黄牌双层
};
enum DetectLevel {
DETECT_LEVEL_LOW = 0,
DETECT_LEVEL_HIGH,
};
typedef struct PlateObject {
PlateLocation location{};
TextLine line{};
PlateColor type{};
} PlateObject;
typedef struct PlateResult {
float x1;
float y1;
float x2;
float y2;
PlateType type;
float text_confidence;
char code[128];
} PlateResult;
typedef std::vector<PlateObject> PlateObjectList;
typedef std::vector<PlateResult> PlateResultList;
}
#endif //ZEPHYRLPR_HYPER_LPR_COMMON_H
@@ -0,0 +1,223 @@
//
// Created by tunm on 2023/1/25.
//
#pragma once
#include <string>
#include "hyper_lpr_context.h"
#include "configuration.h"
#include "utils.h"
#include "log.h"
namespace hyper {
HyperLPRContext::HyperLPRContext() = default;
void HyperLPRContext::operator()(CameraBuffer &buffer) {
cv::Mat process_image;
process_image = buffer.GetScaledImage(1.0f, true);
m_plate_detector_->Detection(process_image, true, 1.0f);
PlateResultList().swap(m_object_results_);
auto &detect_results = m_plate_detector_->m_results_;
// cv::imwrite("/storage/emulated/0/Android/data/com.hyperai.hyperlpr_sdk_demo/files/bug.jpg", process_image);
// std::sort(detect_results.begin(), detect_results.end(),
// [](PlateLocation a, PlateLocation b) { return xyxyArea(a.x1, a.y1, a.x2, a.y2) > xyxyArea(b.x1, b.y1, b.x2, b.y2); });
//
auto iteration_num = detect_results.size() > m_rec_max_num_ ? m_rec_max_num_: detect_results.size();
for (size_t i = 0; i < iteration_num; ++i) {
auto &loc = detect_results[i];
PlateResult obj;
obj.x1 = loc.x1;
obj.y1 = loc.y1;
obj.x2 = loc.x2;
obj.y2 = loc.y2;
cv::Mat align_image;
getRotateCropAndAlignPad(process_image, align_image, loc.kps);
// std::cout << align_image_pad.size << std::endl;
// cv::imshow("align_image_pad", align_image_pad);
// cv::imshow("align_image", align_image);
// cv::waitKey(0);
TextLine text_line;
if (loc.layers == LayersNum::DOUBLE) {
int line = (int )((float )align_image.rows * 0.4f);
int bottom_h = align_image.rows - line;
cv::Rect_<int> top_rect(0, 0, align_image.cols, line);
cv::Rect_<int> bottom_rect(0, line, align_image.cols, bottom_h);
cv::Mat top_crop = align_image(top_rect);
cv::Mat bottom_crop = align_image(bottom_rect);
std::vector<cv::Mat> candidate = {top_crop, bottom_crop};
text_line.code = "";
text_line.average_score = 0.0f;
for (int j = 0; j < candidate.size(); ++j) {
cv::Mat &align = candidate[j];
cv::Mat align_pad;
float wh_ratio = (float) align.cols / align.rows;
imagePadding(align, align_pad, wh_ratio, m_plate_recognition_->getMInputImageSize());
TextLine candidate_text;
// cv::imshow("align_pad", align_pad);
// cv::waitKey(0);
// if (j == 0)
// cv::imwrite("a.jpg", align_pad);
m_plate_recognition_->Inference(align_pad, candidate_text);
text_line.code += candidate_text.code;
text_line.average_score += candidate_text.average_score;
}
text_line.average_score /= candidate.size();
// cv::imshow("top_crop", top_crop);
// cv::imshow("bottom_crop", bottom_crop);
// cv::waitKey(0);
} else {
cv::Mat align_image_pad;
float wh_ratio = (float) align_image.cols / align_image.rows;
imagePadding(align_image, align_image_pad, wh_ratio, m_plate_recognition_->getMInputImageSize());
m_plate_recognition_->Inference(align_image_pad, text_line);
}
cv::resize(align_image, align_image, m_plate_classification_->getMInputImageSize());
// SLOG_CRITICAL("cfg: {}", text_line.average_score);
// SLOG_CRITICAL("code: {}", text_line.code);
if (text_line.average_score < m_plate_recognition_->getMConfidenceThreshold()) {
continue;
}
// cv::imshow("resize", align_image_pad);
// cv::imshow("align_image", align_image);
// cv::waitKey(0);
// obj.color_classify = PlateColor::GREEN;
// obj.layers = loc.layers;
// LOGD("size %d", text_line.code.size());
if (text_line.code.size() >= 7) {
obj.text_confidence = text_line.average_score;
auto type = PreGetPlateType(text_line.code);
if (type == PlateType::UNKNOWN) {
m_plate_classification_->Inference(align_image);
auto color_type = m_plate_classification_->getMOutputColor();
// obj.color_classify = color_type;
// obj.layers = loc.layers;
if (color_type == PlateColor::YELLOW) {
// 黄牌
if (loc.layers == LayersNum::DOUBLE) {
// 双层黄牌
type = PlateType::YELLOW_DOUBLE;
} else {
// 单层黄牌
type = PlateType::YELLOW_SINGLE;
}
} else if (color_type == PlateColor::BLUE) {
type = PlateType::BLUE;
} else if (color_type == PlateColor::GREEN) {
type = PlateType::GREEN;
}
}
obj.type = type;
strcpy(obj.code, text_line.code.c_str());
// obj.code = text_line.code;
// cv::imshow("align_image_pad", align_image_pad);
// cv::waitKey(0);
m_object_results_.push_back(obj);
}
}
}
int32_t HyperLPRContext::Initialize(const std::string& models_folder_path, int max_num, DetectLevel detect_level,
int threads, bool use_half, float box_conf_threshold, float nms_threshold, float rec_confidence_threshold) {
int32_t ret;
std::string det_backbone_model = models_folder_path + "/" + hyper::DETECT_LOW_BACKBONE_FILENAME;
if (!exists(det_backbone_model)) {
LOGE("file: %s does not exist.", hyper::DETECT_LOW_BACKBONE_FILENAME.c_str());
return hRetErr;
}
std::string det_header_model = models_folder_path + "/" + hyper::DETECT_LOW_HEAD_FILENAME;
if (!exists(det_header_model)) {
LOGE("file: %s does not exist.", hyper::DETECT_LOW_HEAD_FILENAME.c_str());
return hRetErr;
}
if (detect_level == DETECT_LEVEL_HIGH) {
det_backbone_model = models_folder_path + "/" + hyper::DETECT_HIGH_BACKBONE_FILENAME;
if (!exists(det_backbone_model)) {
LOGE("file: %s does not exist.", hyper::DETECT_HIGH_BACKBONE_FILENAME.c_str());
return hRetErr;
}
det_header_model = models_folder_path + "/" + hyper::DETECT_HIGH_HEAD_FILENAME;
if (!exists(det_header_model)) {
LOGE("file: %s does not exist.", hyper::DETECT_HIGH_HEAD_FILENAME.c_str());
return hRetErr;
}
m_pre_image_size_ = 640;
}
m_plate_detector_ = std::make_shared<DetArch>();
ret = m_plate_detector_->Initialize(det_backbone_model, det_header_model, m_pre_image_size_, threads, box_conf_threshold, nms_threshold,
use_half);
if (ret != hRetOk) {
LOGE("Detect model loading errors.");
return hRetErr;
}
// init classification
std::string classification_model = models_folder_path + "/" + hyper::CLS_MODEL_FILENAME;
if (!exists(classification_model)) {
LOGE("file: %s does not exist.", hyper::CLS_MODEL_FILENAME.c_str());
return hRetErr;
}
m_plate_classification_ = std::make_shared<ClassificationEngine>();
ret = m_plate_classification_->Initialize(classification_model, CLS_INPUT_SIZE, threads, use_half);
if (ret != hRetOk) {
LOGE("Cls model loading errors.");
return hRetErr;
}
// init recognition
std::string recognition_model = models_folder_path + "/" + hyper::REC_MODEL_FILENAME;
if (!exists(recognition_model)) {
LOGE("file: %s does not exist.", hyper::REC_MODEL_FILENAME.c_str());
return hRetErr;
}
m_plate_recognition_ = std::make_shared<RecognitionEngine>();
ret = m_plate_recognition_->Initialize(recognition_model, REC_INPUT_SIZE, threads, rec_confidence_threshold,
use_half);
if (ret != hRetOk) {
LOGE("Rec model loading errors.");
return hRetErr;
}
m_rec_max_num_ = std::max(1, max_num); // Can't be less than 1
m_init_status_ = hRetOk;
return hRetOk;
}
PlateResultList &HyperLPRContext::getMObjectResults() {
return m_object_results_;
}
PlateType HyperLPRContext::PreGetPlateType(std::string& code) {
PlateType type = PlateType::UNKNOWN;
if (code[0] == 'W' && code[1] == 'J'){
type = PlateType::WHILE_SINGLE;
} else if (code.size() == 10) {
type = PlateType::GREEN;
} else if (code.find("") != -1) {
type = PlateType::BLUE;
} else if (code.find("") != -1) {
type = PlateType::BLACK_HK_MACAO;
} else if (code.find("") != -1) {
type = PlateType::BLACK_HK_MACAO;
} else if (code.find("") != -1) {
type = PlateType::WHILE_SINGLE;
} else if (code.find("粤Z") != -1) {
type = PlateType::BLACK_HK_MACAO;
}
return type;
}
int32_t HyperLPRContext::getMInitStatus() const {
return m_init_status_;
}
}
@@ -0,0 +1,76 @@
//
// Created by tunm on 2023/1/25.
//
#ifndef ZEPHYRLPR_HYPER_LPR_CONTEXT_H
#define ZEPHYRLPR_HYPER_LPR_CONTEXT_H
#include <iostream>
#include <opencv2/opencv.hpp>
#include "nn_implementation_module/all.h"
#include "buffer_module/all.h"
#include "hyper_lpr_common.h"
namespace hyper {
enum {
hRetOk = InferenceHelper::kRetOk, ///< 成功
hRetErr = InferenceHelper::kRetErr, ///< 失败
};
class HyperLPRContext {
public:
HyperLPRContext(const HyperLPRContext &) = delete;
HyperLPRContext &operator=(const HyperLPRContext &) = delete;
explicit HyperLPRContext();
void operator()(CameraBuffer &buffer);
/**
* 手动初始化并实例化内部模型对象
* @param models_folder_path 存放模型文件夹的路径地址
* @param max_num 最大识别车牌数量
* @param detect_level 检测器等级 low速度快,high速度慢检出率略高于low
* @param threads 推理线程数量 (暂时无效)
* @param use_half 是否开启半精度推理 (暂时无效)
* @param box_conf_threshold 检测框置信度阈值
* @param nms_threshold 非极大值抑制阈值
* @param rec_confidence_threshold 车牌字符识别置信度阈值
* @return 初始化状态
*/
int32_t Initialize(const std::string& models_folder_path, int max_num = 1, DetectLevel detect_level = DETECT_LEVEL_LOW, int threads = 1, bool use_half = false, float box_conf_threshold = 0.3f,
float nms_threshold = 0.5f,
float rec_confidence_threshold = 0.75f);
PlateResultList &getMObjectResults();
static PlateType PreGetPlateType(std::string& code);
int32_t getMInitStatus() const;
private:
std::shared_ptr<DetArch> m_plate_detector_;
std::shared_ptr<ClassificationEngine> m_plate_classification_;
std::shared_ptr<RecognitionEngine> m_plate_recognition_;
int m_pre_image_size_ = 320;
PlateResultList m_object_results_;
int m_rec_max_num_ = 1;
int32_t m_init_status_ = hRetErr;
};
} // namespace
#endif //ZEPHYRLPR_HYPER_LPR_CONTEXT_H
+8
View File
@@ -0,0 +1,8 @@
//
// Created by Tunm-Air13 on 2022/12/29.
//
#ifndef ZEPHYRLPR_DOC_H
#define ZEPHYRLPR_DOC_H
#endif //ZEPHYRLPR_DOC_H
@@ -0,0 +1,430 @@
/* Copyright 2021 iwatake2222
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
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.
==============================================================================*/
/*** Include ***/
/* for general */
#include <cstdint>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <string>
#include <vector>
#include <array>
#include <algorithm>
#include <chrono>
/* for My modules */
#include "inference_helper_log.h"
#include "inference_helper.h"
#ifdef INFERENCE_HELPER_ENABLE_OPENCV
#include "inference_helper_opencv.h"
#endif
#if defined(INFERENCE_HELPER_ENABLE_TFLITE) || defined(INFERENCE_HELPER_ENABLE_TFLITE_DELEGATE_XNNPACK) || defined(INFERENCE_HELPER_ENABLE_TFLITE_DELEGATE_GPU) || defined(INFERENCE_HELPER_ENABLE_TFLITE_DELEGATE_EDGETPU)
#include "inference_helper_tensorflow_lite.h"
#endif
#ifdef INFERENCE_HELPER_ENABLE_TENSORRT
#include "inference_helper_tensorrt.h"
#endif
#ifdef INFERENCE_HELPER_ENABLE_NCNN
#include "inference_helper_ncnn.h"
#endif
#ifdef INFERENCE_HELPER_ENABLE_MNN
#include "inference_helper_mnn.h"
#endif
#ifdef INFERENCE_HELPER_ENABLE_SNPE
#include "inference_helper_snpe.h"
#endif
#ifdef INFERENCE_HELPER_ENABLE_ARMNN
#include "inference_helper_armnn.h"
#endif
#if defined(INFERENCE_HELPER_ENABLE_NNABLA) || defined(INFERENCE_HELPER_ENABLE_NNABLA_CUDA)
#include "inference_helper_nnabla.h"
#endif
#if defined(INFERENCE_HELPER_ENABLE_ONNX_RUNTIME) || defined(INFERENCE_HELPER_ENABLE_ONNX_RUNTIME_CUDA)
#include "inference_helper_onnx_runtime.h"
#endif
#if defined(INFERENCE_HELPER_ENABLE_LIBTORCH) || defined(INFERENCE_HELPER_ENABLE_LIBTORCH_CUDA)
#include "inference_helper_libtorch.h"
#endif
#if defined(INFERENCE_HELPER_ENABLE_TENSORFLOW) || defined(INFERENCE_HELPER_ENABLE_TENSORFLOW_GPU)
#include "inference_helper_tensorflow.h"
#endif
#ifdef INFERENCE_HELPER_ENABLE_SAMPLE
#include "inference_helper_sample.h"
#endif
#ifdef INFERENCE_HELPER_ENABLE_RKNN
#include "inference_helper_rknn.h"
#endif
/*** Macro ***/
#define TAG "InferenceHelper"
#define PRINT(...) INFERENCE_HELPER_LOG_PRINT(TAG, __VA_ARGS__)
#define PRINT_E(...) INFERENCE_HELPER_LOG_PRINT_E(TAG, __VA_ARGS__)
InferenceHelper* InferenceHelper::Create(const InferenceHelper::HelperType helper_type)
{
InferenceHelper* p = nullptr;
switch (helper_type) {
#ifdef INFERENCE_HELPER_ENABLE_OPENCV
case kOpencv:
case kOpencvGpu:
PRINT("Use OpenCV \n");
p = new InferenceHelperOpenCV();
break;
#endif
#ifdef INFERENCE_HELPER_ENABLE_TFLITE
case kTensorflowLite:
PRINT("Use TensorflowLite\n");
p = new InferenceHelperTensorflowLite();
break;
#endif
#ifdef INFERENCE_HELPER_ENABLE_TFLITE_DELEGATE_XNNPACK
case kTensorflowLiteXnnpack:
PRINT("Use TensorflowLite XNNPACK Delegate\n");
p = new InferenceHelperTensorflowLite();
break;
#endif
#ifdef INFERENCE_HELPER_ENABLE_TFLITE_DELEGATE_GPU
case kTensorflowLiteGpu:
PRINT("Use TensorflowLite GPU Delegate\n");
p = new InferenceHelperTensorflowLite();
break;
#endif
#ifdef INFERENCE_HELPER_ENABLE_TFLITE_DELEGATE_EDGETPU
case kTensorflowLiteEdgetpu:
PRINT("Use TensorflowLite EdgeTPU Delegate\n");
p = new InferenceHelperTensorflowLite();
break;
#endif
#ifdef INFERENCE_HELPER_ENABLE_TFLITE_DELEGATE_NNAPI
case kTensorflowLiteNnapi:
PRINT("Use TensorflowLite NNAPI Delegate\n");
p = new InferenceHelperTensorflowLite();
break;
#endif
#ifdef INFERENCE_HELPER_ENABLE_TENSORRT
case kTensorrt:
PRINT("Use TensorRT \n");
p = new InferenceHelperTensorRt();
break;
#endif
#ifdef INFERENCE_HELPER_ENABLE_NCNN
case kNcnn:
case kNcnnVulkan:
PRINT("Use NCNN\n");
p = new InferenceHelperNcnn();
break;
#endif
#ifdef INFERENCE_HELPER_ENABLE_MNN
case kMnn:
PRINT("Use MNN\n");
p = new InferenceHelperMnn();
break;
#endif
#ifdef INFERENCE_HELPER_ENABLE_SNPE
case kSnpe:
PRINT("Use SNPE\n");
p = new InferenceHelperSnpe();
break;
#endif
#ifdef INFERENCE_HELPER_ENABLE_ARMNN
case kArmnn:
PRINT("Use ARMNN\n");
p = new InferenceHelperArmnn();
break;
#endif
#ifdef INFERENCE_HELPER_ENABLE_NNABLA
case kNnabla:
PRINT("Use NNabla\n");
p = new InferenceHelperNnabla();
break;
#endif
#ifdef INFERENCE_HELPER_ENABLE_NNABLA_CUDA
case kNnablaCuda:
PRINT("Use NNabla_CUDA\n");
p = new InferenceHelperNnabla();
break;
#endif
#ifdef INFERENCE_HELPER_ENABLE_ONNX_RUNTIME
case kOnnxRuntime:
PRINT("Use ONNX Runtime\n");
p = new InferenceHelperOnnxRuntime();
break;
#endif
#ifdef INFERENCE_HELPER_ENABLE_ONNX_RUNTIME_CUDA
case kOnnxRuntimeCuda:
PRINT("Use ONNX Runtime_CUDA\n");
p = new InferenceHelperOnnxRuntime();
break;
#endif
#ifdef INFERENCE_HELPER_ENABLE_LIBTORCH
case kLibtorch:
PRINT("Use LibTorch\n");
p = new InferenceHelperLibtorch();
break;
#endif
#ifdef INFERENCE_HELPER_ENABLE_LIBTORCH_CUDA
case kLibtorchCuda:
PRINT("Use LibTorch CUDA\n");
p = new InferenceHelperLibtorch();
break;
#endif
#ifdef INFERENCE_HELPER_ENABLE_TENSORFLOW
case kTensorflow:
PRINT("Use TensorFlow\n");
p = new InferenceHelperTensorflow();
break;
#endif
#ifdef INFERENCE_HELPER_ENABLE_TENSORFLOW_GPU
case kTensorflowGpu:
PRINT("Use TensorFlow GPU\n");
p = new InferenceHelperTensorflow();
break;
#endif
#ifdef INFERENCE_HELPER_ENABLE_SAMPLE
case kSample:
PRINT("Do not use this. this is just a reference code\n");
p = new InferenceHelperSample();
break;
#endif
#ifdef INFERENCE_HELPER_ENABLE_RKNN
case kRknn:
PRINT("Use Rknn\n")
p = new InferenceHelperRKNN();
break;
#endif
default:
PRINT_E("Unsupported inference helper type (%d)\n", helper_type);
break;
}
if (p == nullptr) {
PRINT_E("Failed to create inference helper\n");
} else {
p->helper_type_ = helper_type;
}
return p;
}
#ifdef INFERENCE_HELPER_ENABLE_PRE_PROCESS_BY_OPENCV
#include <opencv2/opencv.hpp>
void InferenceHelper::PreProcessByOpenCV(const InputTensorInfo& input_tensor_info, bool is_nchw, cv::Mat& img_blob)
{
/* Generate mat from original data */
cv::Mat img_src = cv::Mat(cv::Size(input_tensor_info.image_info.width, input_tensor_info.image_info.height), (input_tensor_info.image_info.channel == 3) ? CV_8UC3 : CV_8UC1, input_tensor_info.data);
/* Crop image */
if (input_tensor_info.image_info.width == input_tensor_info.image_info.crop_width && input_tensor_info.image_info.height == input_tensor_info.image_info.crop_height) {
/* do nothing */
} else {
img_src = img_src(cv::Rect(input_tensor_info.image_info.crop_x, input_tensor_info.image_info.crop_y, input_tensor_info.image_info.crop_width, input_tensor_info.image_info.crop_height));
}
/* Resize image */
if (input_tensor_info.image_info.crop_width == input_tensor_info.GetWidth() && input_tensor_info.image_info.crop_height == input_tensor_info.GetHeight()) {
/* do nothing */
} else {
cv::resize(img_src, img_src, cv::Size(input_tensor_info.GetWidth(), input_tensor_info.GetHeight()));
}
/* Convert color type */
if (input_tensor_info.image_info.channel == input_tensor_info.GetChannel()) {
if (input_tensor_info.image_info.channel == 3 && input_tensor_info.image_info.swap_color) {
cv::cvtColor(img_src, img_src, cv::COLOR_BGR2RGB);
}
} else if (input_tensor_info.image_info.channel == 3 && input_tensor_info.GetChannel() == 1) {
cv::cvtColor(img_src, img_src, (input_tensor_info.image_info.is_bgr) ? cv::COLOR_BGR2GRAY : cv::COLOR_RGB2GRAY);
} else if (input_tensor_info.image_info.channel == 1 && input_tensor_info.GetChannel() == 3) {
cv::cvtColor(img_src, img_src, cv::COLOR_GRAY2BGR);
}
if (input_tensor_info.tensor_type == TensorInfo::kTensorTypeFp32) {
/* Normalize image */
if (input_tensor_info.GetChannel() == 3) {
#if 1
img_src.convertTo(img_src, CV_32FC3);
cv::subtract(img_src, cv::Scalar(cv::Vec<float, 3>(input_tensor_info.normalize.mean)), img_src);
cv::multiply(img_src, cv::Scalar(cv::Vec<float, 3>(input_tensor_info.normalize.norm)), img_src);
#else
img_src.convertTo(img_src, CV_32FC3, 1.0 / 255);
cv::subtract(img_src, cv::Scalar(cv::Vec<float, 3>(input_tensor_info.normalize.mean)), img_src);
cv::divide(img_src, cv::Scalar(cv::Vec<float, 3>(input_tensor_info.normalize.norm)), img_src);
#endif
} else {
#if 1
img_src.convertTo(img_src, CV_32FC1);
cv::subtract(img_src, cv::Scalar(cv::Vec<float, 1>(input_tensor_info.normalize.mean)), img_src);
cv::multiply(img_src, cv::Scalar(cv::Vec<float, 1>(input_tensor_info.normalize.norm)), img_src);
#else
img_src.convertTo(img_src, CV_32FC1, 1.0 / 255);
cv::subtract(img_src, cv::Scalar(cv::Vec<float, 1>(input_tensor_info.normalize.mean)), img_src);
cv::divide(img_src, cv::Scalar(cv::Vec<float, 1>(input_tensor_info.normalize.norm)), img_src);
#endif
}
} else {
/* do nothing */
}
if (is_nchw) {
/* Convert to 4-dimensional Mat in NCHW */
img_src = cv::dnn::blobFromImage(img_src);
}
img_blob = img_src;
//memcpy(blobData, img_src.data, img_src.cols * img_src.rows * img_src.channels());
}
#else
/* For the environment where OpenCV is not supported */
void InferenceHelper::PreProcessByOpenCV(const InputTensorInfo& input_tensor_info, bool is_nchw, cv::Mat& img_blob)
{
PRINT_E("[PreProcessByOpenCV] Unsupported function called\n");
exit(-1);
}
#endif
void InferenceHelper::ConvertNormalizeParameters(InputTensorInfo& tensor_info)
{
if (tensor_info.data_type != InputTensorInfo::kDataTypeImage) return;
#if 0
/* Convert to speeden up normalization: ((src / 255) - mean) / norm = src * 1 / (255 * norm) - (mean / norm) */
for (int32_t i = 0; i < 3; i++) {
tensor_info.normalize.mean[i] /= tensor_info.normalize.norm[i];
tensor_info.normalize.norm[i] *= 255.0f;
tensor_info.normalize.norm[i] = 1.0f / tensor_info.normalize.norm[i];
}
#endif
#if 1
/* Convert to speeden up normalization: ((src / 255) - mean) / norm = (src - (mean * 255)) * (1 / (255 * norm)) */
for (int32_t i = 0; i < 3; i++) {
tensor_info.normalize.mean[i] *= 255.0f;
tensor_info.normalize.norm[i] *= 255.0f;
tensor_info.normalize.norm[i] = 1.0f / tensor_info.normalize.norm[i];
}
#endif
}
void InferenceHelper::PreProcessImage(int32_t num_thread, const InputTensorInfo& input_tensor_info, float* dst)
{
const int32_t img_width = input_tensor_info.GetWidth();
const int32_t img_height = input_tensor_info.GetHeight();
const int32_t img_channel = input_tensor_info.GetChannel();
uint8_t* src = (uint8_t*)(input_tensor_info.data);
if (input_tensor_info.is_nchw == true) {
/* convert NHWC to NCHW */
#pragma omp parallel for num_threads(num_thread)
for (int32_t c = 0; c < img_channel; c++) {
for (int32_t i = 0; i < img_width * img_height; i++) {
dst[c * img_width * img_height + i] = (src[i * img_channel + c] - input_tensor_info.normalize.mean[c]) * input_tensor_info.normalize.norm[c];
}
}
} else {
/* convert NHWC to NHWC */
#pragma omp parallel for num_threads(num_thread)
for (int32_t i = 0; i < img_width * img_height; i++) {
for (int32_t c = 0; c < img_channel; c++) {
#if 1
dst[i * img_channel + c] = (src[i * img_channel + c] - input_tensor_info.normalize.mean[c]) * input_tensor_info.normalize.norm[c];
#else
dst[i * img_channel + c] = (src[i * img_channel + c] / 255.0f - input_tensor_info.normalize.mean[c]) / input_tensor_info.normalize.norm[c];
#endif
}
}
}
}
void InferenceHelper::PreProcessImage(int32_t num_thread, const InputTensorInfo& input_tensor_info, uint8_t* dst)
{
const int32_t img_width = input_tensor_info.GetWidth();
const int32_t img_height = input_tensor_info.GetHeight();
const int32_t img_channel = input_tensor_info.GetChannel();
uint8_t* src = (uint8_t*)(input_tensor_info.data);
if (input_tensor_info.is_nchw == true) {
/* convert NHWC to NCHW */
#pragma omp parallel for num_threads(num_thread)
for (int32_t c = 0; c < img_channel; c++) {
for (int32_t i = 0; i < img_width * img_height; i++) {
dst[c * img_width * img_height + i] = src[i * img_channel + c];
}
}
} else {
/* convert NHWC to NHWC */
std::copy(src, src + input_tensor_info.GetElementNum(), dst);
}
}
void InferenceHelper::PreProcessImage(int32_t num_thread, const InputTensorInfo& input_tensor_info, int8_t* dst)
{
const int32_t img_width = input_tensor_info.GetWidth();
const int32_t img_height = input_tensor_info.GetHeight();
const int32_t img_channel = input_tensor_info.GetChannel();
uint8_t* src = (uint8_t*)(input_tensor_info.data);
if (input_tensor_info.is_nchw == true) {
/* convert NHWC to NCHW */
#pragma omp parallel for num_threads(num_thread)
for (int32_t c = 0; c < img_channel; c++) {
for (int32_t i = 0; i < img_width * img_height; i++) {
dst[c * img_width * img_height + i] = src[i * img_channel + c] - 128;
}
}
} else {
#pragma omp parallel for num_threads(num_thread)
for (int32_t i = 0; i < img_width * img_height; i++) {
for (int32_t c = 0; c < img_channel; c++) {
dst[i * img_channel + c] = src[i * img_channel + c] - 128;
}
}
}
}
template<typename T>
void InferenceHelper::PreProcessBlob(int32_t num_thread, const InputTensorInfo& input_tensor_info, T* dst)
{
const int32_t img_width = input_tensor_info.GetWidth();
const int32_t img_height = input_tensor_info.GetHeight();
const int32_t img_channel = input_tensor_info.GetChannel();
T* src = static_cast<T*>(input_tensor_info.data);
if ((input_tensor_info.data_type == InputTensorInfo::kDataTypeBlobNchw && input_tensor_info.is_nchw) || (input_tensor_info.data_type == InputTensorInfo::kDataTypeBlobNhwc && !input_tensor_info.is_nchw)) {
std::copy(src, src + input_tensor_info.GetElementNum(), dst);
} else if (input_tensor_info.data_type == InputTensorInfo::kDataTypeBlobNchw) {
/* NCHW -> NHWC */
#pragma omp parallel for num_threads(num_thread)
for (int32_t i = 0; i < img_width * img_height; i++) {
for (int32_t c = 0; c < img_channel; c++) {
dst[i * img_channel + c] = src[c * (img_width * img_height) + i];
}
}
} else if (input_tensor_info.data_type == InputTensorInfo::kDataTypeBlobNhwc) {
/* NHWC -> NCHW */
#pragma omp parallel for num_threads(num_thread)
for (int32_t i = 0; i < img_width * img_height; i++) {
for (int32_t c = 0; c < img_channel; c++) {
dst[c * (img_width * img_height) + i] = src[i * img_channel + c];
}
}
}
}
template void InferenceHelper::PreProcessBlob<float>(int32_t num_thread, const InputTensorInfo& input_tensor_info, float* dst);
template void InferenceHelper::PreProcessBlob<int32_t>(int32_t num_thread, const InputTensorInfo& input_tensor_info, int32_t* dst);
template void InferenceHelper::PreProcessBlob<int64_t>(int32_t num_thread, const InputTensorInfo& input_tensor_info, int64_t* dst);
template void InferenceHelper::PreProcessBlob<uint8_t>(int32_t num_thread, const InputTensorInfo& input_tensor_info, uint8_t* dst);
template void InferenceHelper::PreProcessBlob<int8_t>(int32_t num_thread, const InputTensorInfo& input_tensor_info, int8_t* dst);
@@ -0,0 +1,280 @@
/* Copyright 2021 iwatake2222
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
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.
==============================================================================*/
#ifndef INFERENCE_HELPER_
#define INFERENCE_HELPER_
/* for general */
#include <cstdint>
#include <cmath>
#include <string>
#include <vector>
#include <array>
#include <memory>
class TensorInfo {
public:
enum {
kTensorTypeNone,
kTensorTypeUint8,
kTensorTypeInt8,
kTensorTypeFp32,
kTensorTypeInt32,
kTensorTypeInt64,
};
public:
TensorInfo()
: name("")
, id(-1)
, tensor_type(kTensorTypeNone)
, is_nchw(true)
{}
~TensorInfo() {}
int32_t GetElementNum() const
{
int32_t element_num = 1;
for (const auto& dim : tensor_dims) {
element_num *= dim;
}
return element_num;
}
int32_t GetBatch() const
{
if (tensor_dims.size() <= 0) return -1;
return tensor_dims[0];
}
int32_t GetChannel() const
{
if (is_nchw) {
if (tensor_dims.size() <= 1) return -1;
return tensor_dims[1];
} else {
if (tensor_dims.size() <= 3) return -1;
return tensor_dims[3];
}
}
int32_t GetHeight() const
{
if (is_nchw) {
if (tensor_dims.size() <= 2) return -1;
return tensor_dims[2];
} else {
if (tensor_dims.size() <= 1) return -1;
return tensor_dims[1];
}
}
int32_t GetWidth() const
{
if (is_nchw) {
if (tensor_dims.size() <= 3) return -1;
return tensor_dims[3];
} else {
if (tensor_dims.size() <= 2) return -1;
return tensor_dims[2];
}
}
public:
std::string name; // [In] Set the name_ of tensor
int32_t id; // [Out] Do not modify (Used in InferenceHelper)
int32_t tensor_type; // [In] The type of tensor (e.g. kTensorTypeFp32)
std::vector<int32_t> tensor_dims; // InputTensorInfo: [In] The dimentions of tensor. (If empty at initialize, the size is updated from model info.)
// OutputTensorInfo: [Out] The dimentions of tensor is set from model information
bool is_nchw; // [IN] NCHW or NHWC
};
class InputTensorInfo : public TensorInfo {
public:
enum {
kDataTypeImage,
kDataTypeBlobNhwc, // data_ which already finished preprocess(color conversion, resize, normalize_, etc.)
kDataTypeBlobNchw,
};
public:
InputTensorInfo()
: data(nullptr)
, data_type(kDataTypeImage)
, image_info({ -1, -1, -1, -1, -1, -1, -1, true, false })
, normalize({ 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f })
{}
InputTensorInfo(std::string name_, int32_t tensor_type_, bool is_nchw_ = true)
: InputTensorInfo()
{
name = name_;
tensor_type = tensor_type_;
is_nchw = is_nchw_;
}
~InputTensorInfo() {}
public:
void* data; // [In] Set the pointer to image/blob
int32_t data_type; // [In] Set the type of data_ (e.g. kDataTypeImage)
struct {
int32_t width;
int32_t height;
int32_t channel;
int32_t crop_x;
int32_t crop_y;
int32_t crop_width;
int32_t crop_height;
bool is_bgr; // used when channel == 3 (true: BGR, false: RGB)
bool swap_color;
} image_info; // [In] used when data_type_ == kDataTypeImage
struct {
float mean[3];
float norm[3];
} normalize; // [In] used when data_type_ == kDataTypeImage
};
class OutputTensorInfo : public TensorInfo {
public:
OutputTensorInfo()
: data(nullptr)
, quant({ 1.0f, 0 })
, data_fp32_(nullptr)
{}
OutputTensorInfo(std::string name_, int32_t tensor_type_, bool is_nchw_ = true)
: OutputTensorInfo()
{
name = name_;
tensor_type = tensor_type_;
is_nchw = is_nchw_;
}
~OutputTensorInfo() {
if (data_fp32_ != nullptr) {
delete[] data_fp32_;
}
}
float* GetDataAsFloat() { /* Returned pointer should be with const, but returning pointer without const is convenient to create cv::Mat */
if (tensor_type == kTensorTypeUint8 || tensor_type == kTensorTypeInt8) {
if (data_fp32_ == nullptr) {
data_fp32_ = new float[GetElementNum()];
}
if (tensor_type == kTensorTypeUint8) {
#pragma omp parallel
for (int32_t i = 0; i < GetElementNum(); i++) {
const uint8_t* val_uint8 = static_cast<const uint8_t*>(data);
float val_float = (val_uint8[i] - quant.zero_point) * quant.scale;
data_fp32_[i] = val_float;
}
} else {
#pragma omp parallel
for (int32_t i = 0; i < GetElementNum(); i++) {
const int8_t* val_int8 = static_cast<const int8_t*>(data);
float val_float = (val_int8[i] - quant.zero_point) * quant.scale;
data_fp32_[i] = val_float;
}
}
return data_fp32_;
} else if (tensor_type == kTensorTypeFp32) {
return static_cast<float*>(data);
} else {
return nullptr;
}
}
public:
void* data; // [Out] Pointer to the output data_
struct {
float scale;
int32_t zero_point;
} quant; // [Out] Parameters for dequantization (convert uint8 to float)
private:
float* data_fp32_;
};
namespace cv {
class Mat;
};
class InferenceHelper {
public:
enum {
kRetOk = 0,
kRetErr = -1,
};
typedef enum {
kOpencv,
kOpencvGpu,
kTensorflowLite,
kTensorflowLiteXnnpack,
kTensorflowLiteGpu,
kTensorflowLiteEdgetpu,
kTensorflowLiteNnapi,
kTensorrt,
kNcnn,
kNcnnVulkan,
kMnn,
kSnpe,
kArmnn,
kNnabla,
kNnablaCuda,
kOnnxRuntime,
kOnnxRuntimeCuda,
kLibtorch,
kLibtorchCuda,
kTensorflow,
kTensorflowGpu,
kSample,
kRknn,
} HelperType;
public:
static InferenceHelper* Create(const HelperType helper_type);
static void PreProcessByOpenCV(const InputTensorInfo& input_tensor_info, bool is_nchw, cv::Mat& img_blob); // use this if the selected inference engine doesn't support pre-process
public:
virtual ~InferenceHelper() {}
virtual int32_t SetNumThreads(const int32_t num_threads) = 0;
virtual int32_t SetCustomOps(const std::vector<std::pair<const char*, const void*>>& custom_ops) = 0;
virtual int32_t Initialize(const std::string& model_filename, std::vector<InputTensorInfo>& input_tensor_info_list, std::vector<OutputTensorInfo>& output_tensor_info_list) = 0;
virtual int32_t Initialize(char* model_buffer, int model_size, std::vector<InputTensorInfo>& input_tensor_info_list, std::vector<OutputTensorInfo>& output_tensor_info_list) = 0;
virtual int32_t Finalize(void) = 0;
virtual int32_t PreProcess(const std::vector<InputTensorInfo>& input_tensor_info_list) = 0;
virtual int32_t Process(std::vector<OutputTensorInfo>& output_tensor_info_list) = 0;
virtual int32_t ParameterInitialization(std::vector<InputTensorInfo>& input_tensor_info_list, std::vector<OutputTensorInfo>& output_tensor_info_list) = 0;
protected:
void ConvertNormalizeParameters(InputTensorInfo& tensor_info);
void PreProcessImage(int32_t num_thread, const InputTensorInfo& input_tensor_info, float* dst);
void PreProcessImage(int32_t num_thread, const InputTensorInfo& input_tensor_info, uint8_t* dst);
void PreProcessImage(int32_t num_thread, const InputTensorInfo& input_tensor_info, int8_t* dst);
template<typename T>
void PreProcessBlob(int32_t num_thread, const InputTensorInfo& input_tensor_info, T *dst);
protected:
HelperType helper_type_;
};
#endif
@@ -0,0 +1,45 @@
/* Copyright 2021 iwatake2222
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
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.
==============================================================================*/
#ifndef INFERENCE_HELPER_LOG_
#define INFERENCE_HELPER_LOG_
/* for general */
#include <cstdint>
#include <cmath>
#include <string>
#include <vector>
#include <array>
#if defined(ANDROID) || defined(__ANDROID__)
#define CV_COLOR_IS_RGB
#include <android/log.h>
#define INFERENCE_HELPER_LOG_NDK_TAG "HyperLPR3-Native-Inference"
#define INFERENCE_HELPER_LOG_PRINT_(...) __android_log_print(ANDROID_LOG_INFO, INFERENCE_HELPER_LOG_NDK_TAG, __VA_ARGS__)
#else
#define INFERENCE_HELPER_LOG_PRINT_(...) printf(__VA_ARGS__)
#endif
#define INFERENCE_HELPER_LOG_PRINT(INFERENCE_HELPER_LOG_PRINT_TAG, ...) do { \
INFERENCE_HELPER_LOG_PRINT_("[" INFERENCE_HELPER_LOG_PRINT_TAG "][%d] ", __LINE__); \
INFERENCE_HELPER_LOG_PRINT_(__VA_ARGS__); \
} while(0);
#define INFERENCE_HELPER_LOG_PRINT_E(INFERENCE_HELPER_LOG_PRINT_TAG, ...) do { \
INFERENCE_HELPER_LOG_PRINT_("[ERR: " INFERENCE_HELPER_LOG_PRINT_TAG "][%d] ", __LINE__); \
INFERENCE_HELPER_LOG_PRINT_(__VA_ARGS__); \
} while(0);
#endif
@@ -0,0 +1,317 @@
/* Copyright 2021 iwatake2222
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
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.
==============================================================================*/
/*** Include ***/
/* for general */
#include <cstdint>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <string>
#include <vector>
#include <array>
#include <algorithm>
#include <chrono>
/* for MNN */
#include <MNN/ImageProcess.hpp>
#include <MNN/Interpreter.hpp>
#include <MNN/AutoTime.hpp>
/* for My modules */
#include "inference_helper_log.h"
#include "inference_helper_mnn.h"
/*** Macro ***/
#define TAG "InferenceHelperMnn"
#define PRINT(...) INFERENCE_HELPER_LOG_PRINT(TAG, __VA_ARGS__)
#define PRINT_E(...) INFERENCE_HELPER_LOG_PRINT_E(TAG, __VA_ARGS__)
/*** Function ***/
InferenceHelperMnn::InferenceHelperMnn()
{
num_threads_ = 1;
}
InferenceHelperMnn::~InferenceHelperMnn()
{
}
int32_t InferenceHelperMnn::SetNumThreads(const int32_t num_threads)
{
num_threads_ = num_threads;
return kRetOk;
}
int32_t InferenceHelperMnn::SetCustomOps(const std::vector<std::pair<const char*, const void*>>& custom_ops)
{
PRINT("[WARNING] This method is not supported\n");
return kRetOk;
}
int32_t InferenceHelperMnn::ParameterInitialization(std::vector<InputTensorInfo>& input_tensor_info_list, std::vector<OutputTensorInfo>& output_tensor_info_list) {
/* Check tensor info fits the info from model */
for (auto& input_tensor_info : input_tensor_info_list) {
auto input_tensor = net_->getSessionInput(session_, input_tensor_info.name.c_str());
if (input_tensor == nullptr) {
PRINT_E("Invalid input name (%s)\n", input_tensor_info.name.c_str());
return kRetErr;
}
if ((input_tensor->getType().code == halide_type_float) && (input_tensor_info.tensor_type == TensorInfo::kTensorTypeFp32)) {
/* OK */
} else if ((input_tensor->getType().code == halide_type_uint) && (input_tensor_info.tensor_type == TensorInfo::kTensorTypeUint8)) {
/* OK */
} else {
PRINT_E("Incorrect input tensor type (%d, %d)\n", input_tensor->getType().code, input_tensor_info.tensor_type);
return kRetErr;
}
if ((input_tensor->channel() != -1) && (input_tensor->height() != -1) && (input_tensor->width() != -1)) {
if (input_tensor_info.GetChannel() != -1) {
if ((input_tensor->channel() == input_tensor_info.GetChannel()) && (input_tensor->height() == input_tensor_info.GetHeight()) && (input_tensor->width() == input_tensor_info.GetWidth())) {
/* OK */
} else {
PRINT_E("W: %d != %d\n", input_tensor->width() , input_tensor_info.GetWidth());
PRINT_E("H: %d != %d\n", input_tensor->height() , input_tensor_info.GetHeight());
PRINT_E("C: %d != %d\n", input_tensor->channel() , input_tensor_info.GetChannel());
PRINT_E("Incorrect input tensor size\n");
return kRetErr;
}
} else {
PRINT("Input tensor size is set from the model\n");
input_tensor_info.tensor_dims.clear();
for (int32_t dim = 0; dim < input_tensor->dimensions(); dim++) {
input_tensor_info.tensor_dims.push_back(input_tensor->length(dim));
}
}
} else {
if (input_tensor_info.GetChannel() != -1) {
PRINT("Input tensor size is resized\n");
/* In case the input size is not fixed */
net_->resizeTensor(input_tensor, { 1, input_tensor_info.GetChannel(), input_tensor_info.GetHeight(), input_tensor_info.GetWidth() });
net_->resizeSession(session_);
} else {
PRINT_E("Model input size is not set\n");
return kRetErr;
}
}
}
for (const auto& output_tensor_info : output_tensor_info_list) {
auto output_tensor = net_->getSessionOutput(session_, output_tensor_info.name.c_str());
if (output_tensor == nullptr) {
PRINT_E("Invalid output name (%s)\n", output_tensor_info.name.c_str());
return kRetErr;
}
/* Output size is set when run inference later */
}
/* Convert normalize parameter to speed up */
for (auto& input_tensor_info : input_tensor_info_list) {
ConvertNormalizeParameters(input_tensor_info);
}
/* Check if tensor info is set */
for (const auto& input_tensor_info : input_tensor_info_list) {
for (const auto& dim : input_tensor_info.tensor_dims) {
if (dim <= 0) {
PRINT_E("Invalid tensor size\n");
return kRetErr;
}
}
}
//for (const auto& output_tensor_info : output_tensor_info_list) {
// for (const auto& dim : output_tensor_info.tensor_dims) {
// if (dim <= 0) {
// PRINT_E("Invalid tensor size\n");
// return kRetErr;
// }
// }
//}
return kRetOk;
}
int32_t InferenceHelperMnn::Initialize(char* model_buffer, int model_size, std::vector<InputTensorInfo>& input_tensor_info_list, std::vector<OutputTensorInfo>& output_tensor_info_list) {
/*** Create network ***/
net_.reset(MNN::Interpreter::createFromBuffer(model_buffer, model_size));
if (!net_) {
PRINT_E("Failed to load model model buffer\n");
return kRetErr;
}
MNN::ScheduleConfig scheduleConfig;
scheduleConfig.type = MNN_FORWARD_CPU;
scheduleConfig.numThread = num_threads_; // it seems, setting 1 has better performance on Android
// MNN::BackendConfig bnconfig;
// bnconfig.power = MNN::BackendConfig::Power_High;
// bnconfig.precision = MNN::BackendConfig::Precision_Low;
// scheduleConfig.backendConfig = &bnconfig;
session_ = net_->createSession(scheduleConfig);
if (!session_) {
PRINT_E("Failed to create session\n");
return kRetErr;
}
return ParameterInitialization(input_tensor_info_list, output_tensor_info_list);
}
int32_t InferenceHelperMnn::Initialize(const std::string& model_filename, std::vector<InputTensorInfo>& input_tensor_info_list, std::vector<OutputTensorInfo>& output_tensor_info_list)
{
/*** Create network ***/
net_.reset(MNN::Interpreter::createFromFile(model_filename.c_str()));
if (!net_) {
PRINT_E("Failed to load model file (%s)\n", model_filename.c_str());
return kRetErr;
}
MNN::ScheduleConfig scheduleConfig;
scheduleConfig.type = MNN_FORWARD_AUTO;
scheduleConfig.numThread = num_threads_; // it seems, setting 1 has better performance on Android
// MNN::BackendConfig bnconfig;
// bnconfig.power = MNN::BackendConfig::Power_High;
// bnconfig.precision = MNN::BackendConfig::Precision_Low;
// scheduleConfig.backendConfig = &bnconfig;
session_ = net_->createSession(scheduleConfig);
if (!session_) {
PRINT_E("Failed to create session\n");
return kRetErr;
}
return ParameterInitialization(input_tensor_info_list, output_tensor_info_list);
};
int32_t InferenceHelperMnn::Finalize(void)
{
net_->releaseSession(session_);
net_->releaseModel();
net_.reset();
out_mat_list_.clear();
return kRetOk;
}
int32_t InferenceHelperMnn::PreProcess(const std::vector<InputTensorInfo>& input_tensor_info_list)
{
for (const auto& input_tensor_info : input_tensor_info_list) {
auto input_tensor = net_->getSessionInput(session_, input_tensor_info.name.c_str());
if (input_tensor == nullptr) {
PRINT_E("Invalid input name (%s)\n", input_tensor_info.name.c_str());
return kRetErr;
}
if (input_tensor_info.data_type == InputTensorInfo::kDataTypeImage) {
/* Crop */
if ((input_tensor_info.image_info.width != input_tensor_info.image_info.crop_width) || (input_tensor_info.image_info.height != input_tensor_info.image_info.crop_height)) {
PRINT_E("Crop is not supported\n");
return kRetErr;
}
MNN::CV::ImageProcess::Config image_processconfig;
/* Convert color type */
if ((input_tensor_info.image_info.channel == 3) && (input_tensor_info.GetChannel() == 3)) {
image_processconfig.sourceFormat = (input_tensor_info.image_info.is_bgr) ? MNN::CV::BGR : MNN::CV::RGB;
if (input_tensor_info.image_info.swap_color) {
image_processconfig.destFormat = (input_tensor_info.image_info.is_bgr) ? MNN::CV::RGB : MNN::CV::BGR;
} else {
image_processconfig.destFormat = (input_tensor_info.image_info.is_bgr) ? MNN::CV::BGR : MNN::CV::RGB;
}
} else if ((input_tensor_info.image_info.channel == 1) && (input_tensor_info.GetChannel() == 1)) {
image_processconfig.sourceFormat = MNN::CV::GRAY;
image_processconfig.destFormat = MNN::CV::GRAY;
} else if ((input_tensor_info.image_info.channel == 3) && (input_tensor_info.GetChannel() == 1)) {
image_processconfig.sourceFormat = (input_tensor_info.image_info.is_bgr) ? MNN::CV::BGR : MNN::CV::RGB;
image_processconfig.destFormat = MNN::CV::GRAY;
} else if ((input_tensor_info.image_info.channel == 1) && (input_tensor_info.GetChannel() == 3)) {
image_processconfig.sourceFormat = MNN::CV::GRAY;
image_processconfig.destFormat = MNN::CV::BGR;
} else {
PRINT_E("Unsupported color conversion (%d, %d)\n", input_tensor_info.image_info.channel, input_tensor_info.GetChannel());
return kRetErr;
}
/* Normalize image */
std::memcpy(image_processconfig.mean, input_tensor_info.normalize.mean, sizeof(image_processconfig.mean));
std::memcpy(image_processconfig.normal, input_tensor_info.normalize.norm, sizeof(image_processconfig.normal));
/* Resize image */
image_processconfig.filterType = MNN::CV::BILINEAR;
MNN::CV::Matrix trans;
trans.setScale(static_cast<float>(input_tensor_info.image_info.crop_width) / input_tensor_info.GetWidth(), static_cast<float>(input_tensor_info.image_info.crop_height) / input_tensor_info.GetHeight());
/* Do pre-process */
std::shared_ptr<MNN::CV::ImageProcess> pretreat(MNN::CV::ImageProcess::create(image_processconfig));
pretreat->setMatrix(trans);
pretreat->convert(static_cast<uint8_t*>(input_tensor_info.data), input_tensor_info.image_info.crop_width, input_tensor_info.image_info.crop_height, 0, input_tensor);
} else if ( (input_tensor_info.data_type == InputTensorInfo::kDataTypeBlobNhwc) || (input_tensor_info.data_type == InputTensorInfo::kDataTypeBlobNchw) ) {
std::unique_ptr<MNN::Tensor> tensor;
if (input_tensor_info.data_type == InputTensorInfo::kDataTypeBlobNhwc) {
tensor.reset(new MNN::Tensor(input_tensor, MNN::Tensor::TENSORFLOW));
} else {
tensor.reset(new MNN::Tensor(input_tensor, MNN::Tensor::CAFFE));
}
if (tensor->getType().code == halide_type_float) {
for (int32_t i = 0; i < input_tensor_info.GetWidth() * input_tensor_info.GetHeight() * input_tensor_info.GetChannel(); i++) {
tensor->host<float>()[i] = static_cast<float*>(input_tensor_info.data)[i];
}
} else {
for (int32_t i = 0; i < input_tensor_info.GetWidth() * input_tensor_info.GetHeight() * input_tensor_info.GetChannel(); i++) {
tensor->host<uint8_t>()[i] = static_cast<uint8_t*>(input_tensor_info.data)[i];
}
}
input_tensor->copyFromHostTensor(tensor.get());
} else {
PRINT_E("Unsupported data type (%d)\n", input_tensor_info.data_type);
return kRetErr;
}
}
return kRetOk;
}
int32_t InferenceHelperMnn::Process(std::vector<OutputTensorInfo>& output_tensor_info_list)
{
net_->runSession(session_);
out_mat_list_.clear();
for (auto& output_tensor_info : output_tensor_info_list) {
auto output_tensor = net_->getSessionOutput(session_, output_tensor_info.name.c_str());
if (output_tensor == nullptr) {
PRINT_E("Invalid output name (%s)\n", output_tensor_info.name.c_str());
return kRetErr;
}
auto dimType = output_tensor->getDimensionType();
std::unique_ptr<MNN::Tensor> outputUser(new MNN::Tensor(output_tensor, dimType));
output_tensor->copyToHostTensor(outputUser.get());
auto type = outputUser->getType();
if (type.code == halide_type_float) {
output_tensor_info.tensor_type = TensorInfo::kTensorTypeFp32;
output_tensor_info.data = outputUser->host<float>();
} else if (type.code == halide_type_uint && type.bytes() == 1) {
output_tensor_info.tensor_type = TensorInfo::kTensorTypeUint8;
output_tensor_info.data = outputUser->host<uint8_t>();
} else {
PRINT_E("Unexpected data type\n");
return kRetErr;
}
output_tensor_info.tensor_dims.clear();
for (int32_t dim = 0; dim < outputUser->dimensions(); dim++) {
output_tensor_info.tensor_dims.push_back(outputUser->length(dim));
}
out_mat_list_.push_back(std::move(outputUser)); // store data in member variable so that data keep exist
}
return kRetOk;
}
@@ -0,0 +1,53 @@
/* Copyright 2021 iwatake2222
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
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.
==============================================================================*/
#ifndef INFERENCE_HELPER_MNN_
#define INFERENCE_HELPER_MNN_
/* for general */
#include <cstdint>
#include <cmath>
#include <string>
#include <vector>
#include <array>
#include <memory>
/* for MNN */
#include <MNN/ImageProcess.hpp>
#include <MNN/Interpreter.hpp>
#include <MNN/AutoTime.hpp>
/* for My modules */
#include "inference_helper.h"
class InferenceHelperMnn : public InferenceHelper {
public:
InferenceHelperMnn();
~InferenceHelperMnn() override;
int32_t SetNumThreads(const int32_t num_threads) override;
int32_t SetCustomOps(const std::vector<std::pair<const char*, const void*>>& custom_ops) override;
int32_t Initialize(const std::string& model_filename, std::vector<InputTensorInfo>& input_tensor_info_list, std::vector<OutputTensorInfo>& output_tensor_info_list) override;
int32_t Initialize(char* model_buffer, int model_size, std::vector<InputTensorInfo>& input_tensor_info_list, std::vector<OutputTensorInfo>& output_tensor_info_list) override;
int32_t Finalize(void) override;
int32_t PreProcess(const std::vector<InputTensorInfo>& input_tensor_info_list) override;
int32_t Process(std::vector<OutputTensorInfo>& output_tensor_info_list) override;
int32_t ParameterInitialization(std::vector<InputTensorInfo>& input_tensor_info_list, std::vector<OutputTensorInfo>& output_tensor_info_list) override;
private:
std::unique_ptr<MNN::Interpreter> net_;
MNN::Session* session_;
std::vector<std::unique_ptr<MNN::Tensor>> out_mat_list_;
int32_t num_threads_;
};
#endif
Executable
+50
View File
@@ -0,0 +1,50 @@
#ifndef _LOG_UTILS_H_
#define _LOG_UTILS_H_
#include <stdio.h>
#include <string.h>
//#define DEBUG
#define __FILENAME__ (strrchr(__FILE__, '/') + 1)
//#ifdef DEBUG
#ifdef ANDROID
#include <android/log.h>
#define TAG "HyperLPR3-Native" // 这个是自定义的LOG的标识
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__) // 定义LOGD类型
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__) // 定义LOGI类型
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__) // 定义LOGW类型
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__) // 定义LOGE类型
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,TAG ,__VA_ARGS__) // 定义LOGF类型
#else
// color setting
#define ANSI_COLOR_RED "\x1b[31m"
#define ANSI_COLOR_GREEN "\x1b[32m"
#define ANSI_COLOR_YELLOW "\x1b[33m"
#define ANSI_COLOR_BLUE "\x1b[34m"
#define ANSI_COLOR_MAGENTA "\x1b[35m"
#define ANSI_COLOR_CYAN "\x1b[36m"
#define ANSI_COLOR_WHITE "\x1b[37m"
#define ANSI_COLOR_RESET "\x1b[0m"
#define LOGD(format, ...) \
printf("[%s][%s][%d]: " format "\n", __FILENAME__, __FUNCTION__, __LINE__, \
##__VA_ARGS__)
#define LOGE(format, ...) \
printf("%s[%s][%s][%d]: " format "\n%s", ANSI_COLOR_RED, __FILENAME__, __FUNCTION__, __LINE__, \
##__VA_ARGS__, ANSI_COLOR_RESET)
#endif //ANDROID
//#else
//#define LOGD(format, ...)
//#endif
#endif // _LOG_UTILS_H_
+12
View File
@@ -0,0 +1,12 @@
//
// Created by tunm on 2023/1/25.
//
#ifndef ZEPHYRLPR_NN_IMPLEMENTATION_ALL_H
#define ZEPHYRLPR_NN_IMPLEMENTATION_ALL_H
#include "recognition/all.h"
#include "detect/all.h"
#include "classification/all.h"
#endif //ZEPHYRLPR_NN_IMPLEMENTATION_ALL_H
@@ -0,0 +1,12 @@
//
// Created by tunm on 2023/1/22.
//
#pragma once
#ifndef ZEPHYRLPR_CLASSIFICATION_ALL_H
#define ZEPHYRLPR_CLASSIFICATION_ALL_H
#include "plate_classification.h"
#include "plate_cls_common.h"
#include "classification_engine.h"
#endif //ZEPHYRLPR_CLASSIFICATION_ALL_H
@@ -0,0 +1,107 @@
//
// Created by Tunm-Air13 on 2023/2/9.
//
#include "classification_engine.h"
#include "utils.h"
namespace hyper {
ClassificationEngine::ClassificationEngine() {
}
ClassificationEngine::~ClassificationEngine() {
}
int32_t ClassificationEngine::Initialize(const std::string& model_filename, cv::Size input_size, int threads, bool use_half) {
m_input_image_size_ = std::move(input_size);
/* Set output tensor info */
m_output_tensor_info_list_.clear();
m_output_tensor_info_list_.push_back(OutputTensorInfo("output", TensorInfo::kTensorTypeFp32));
m_nn_infer_.reset(InferenceHelper::Create(InferenceHelper::kMnn));
if (m_nn_infer_->SetNumThreads(threads) != InferenceHelper::kRetOk) {
m_nn_infer_.reset();
return InferenceHelper::kRetErr;
}
if (m_nn_infer_->Initialize(model_filename, m_input_tensor_info_list_, m_output_tensor_info_list_) != InferenceHelper::kRetOk) {
m_nn_infer_.reset();
return InferenceHelper::kRetErr;
}
m_input_tensor_info_list_.clear();
InputTensorInfo input_tensor_info("data", TensorInfo::kTensorTypeFp32, true);
input_tensor_info.tensor_dims = { 1, 3, m_input_image_size_.height, m_input_image_size_.width };
input_tensor_info.data_type = InputTensorInfo::kDataTypeImage;
input_tensor_info.normalize.mean[0] = 0.0f;
input_tensor_info.normalize.mean[1] = 0.0f;
input_tensor_info.normalize.mean[2] = 0.0f;
input_tensor_info.normalize.norm[0] = 0.003921568627f;
input_tensor_info.normalize.norm[1] = 0.003921568627f;
input_tensor_info.normalize.norm[2] = 0.003921568627f;
m_input_tensor_info_list_.push_back(input_tensor_info);
return InferenceHelper::kRetOk;
}
int32_t ClassificationEngine::Inference(const cv::Mat &bgr_pad) {
InputTensorInfo& input_tensor_info = m_input_tensor_info_list_[0];
input_tensor_info.data = bgr_pad.data;
input_tensor_info.data_type = InputTensorInfo::kDataTypeImage;
input_tensor_info.image_info.width = bgr_pad.cols;
input_tensor_info.image_info.height = bgr_pad.rows;
input_tensor_info.image_info.channel = bgr_pad.channels();
input_tensor_info.image_info.crop_x = 0;
input_tensor_info.image_info.crop_y = 0;
input_tensor_info.image_info.crop_width = bgr_pad.cols;
input_tensor_info.image_info.crop_height = bgr_pad.rows;
input_tensor_info.image_info.is_bgr = true;
input_tensor_info.image_info.swap_color = false;
if (m_nn_infer_->PreProcess(m_input_tensor_info_list_) != InferenceHelper::kRetOk) {
return InferenceHelper::kRetErr;
}
if (m_nn_infer_->Process(m_output_tensor_info_list_) != InferenceHelper::kRetOk) {
return InferenceHelper::kRetErr;
}
std::vector<float> output_score_raw_list(m_output_tensor_info_list_[0].GetDataAsFloat(),
m_output_tensor_info_list_[0].GetDataAsFloat() +
m_output_tensor_info_list_[0].GetElementNum());
auto index = argmax(output_score_raw_list.begin(), output_score_raw_list.end());
m_output_max_confidence_ = output_score_raw_list[index];
m_output_color_ = PlateColor(index);
return InferenceHelper::kRetOk;
}
PlateColor ClassificationEngine::getMOutputColor() const {
return m_output_color_;
}
const cv::Size &ClassificationEngine::getMInputImageSize() const {
return m_input_image_size_;
}
float ClassificationEngine::getMOutputMaxConfidence() const {
return m_output_max_confidence_;
}
} // namespace
@@ -0,0 +1,54 @@
//
// Created by Tunm-Air13 on 2023/2/9.
//
#pragma once
#ifndef ZEPHYRLPR_CLASSIFICATION_ENGINE_H
#define ZEPHYRLPR_CLASSIFICATION_ENGINE_H
#include "inference_helper_module/inference_helper.h"
#include "configuration.h"
#include "plate_cls_common.h"
#include "basic_types.h"
#include "opencv2/opencv.hpp"
namespace hyper {
class ClassificationEngine {
public:
ClassificationEngine(const ClassificationEngine &) = delete;
ClassificationEngine &operator=(const ClassificationEngine &) = delete;
explicit ClassificationEngine();
~ClassificationEngine();
int32_t Initialize(const std::string& model_filename, cv::Size input_size = CLS_INPUT_SIZE, int threads = 1, bool use_half = false);
int32_t Inference(const cv::Mat &bgr_pad);
PlateColor getMOutputColor() const;
const cv::Size &getMInputImageSize() const;
float getMOutputMaxConfidence() const;
private:
cv::Size m_input_image_size_{}; // 输入图像宽高
std::unique_ptr<InferenceHelper> m_nn_infer_; // 推理模块
std::vector<InputTensorInfo> m_input_tensor_info_list_;
std::vector<OutputTensorInfo> m_output_tensor_info_list_;
PlateColor m_output_color_;
float m_output_max_confidence_;
};
} // namespace
#endif //ZEPHYRLPR_CLASSIFICATION_ENGINE_H
@@ -0,0 +1,34 @@
//
// Created by tunm on 2023/1/22.
//
#include "plate_classification.h"
#include "utils.h"
namespace hyper {
PlateClassification::PlateClassification() = default;
int PlateClassification::Initialize(const std::string& model_filename, cv::Size input_size, int threads, bool use_half) {
m_input_image_size_ = std::move(input_size);
float mean[] = {0.0f, 0.0f, 0.0f};
float normal[] = {0.003921568627f, 0.003921568627f, 0.003921568627f};
m_nn_adapter_ = std::make_shared<MNNAdapterInference>(model_filename, threads, mean, normal, use_half);
m_nn_adapter_->Initialization("data", "output", m_input_image_size_.width, m_input_image_size_.height);
return 0;
}
PlateColor PlateClassification::Inference(const cv::Mat &bgr_pad) {
std::vector<float> output = m_nn_adapter_->Invoking(bgr_pad);
// for (int i = 0; i < output.size(); ++i) {
// std::cout << output[i] << std::endl;
// }
auto index = argmax(output.begin(), output.end());
return PlateColor(index);
}
const cv::Size &PlateClassification::getMInputImageSize() const {
return m_input_image_size_;
}
} // namespace
@@ -0,0 +1,39 @@
//
// Created by tunm on 2023/1/22.
//
#pragma once
#ifndef ZEPHYRLPR_PLATE_CLASSIFICATION_H
#define ZEPHYRLPR_PLATE_CLASSIFICATION_H
#include "nn_module/mnn_adapter.h"
#include "basic_types.h"
#include "plate_cls_common.h"
namespace hyper {
class PlateClassification {
public:
PlateClassification(const PlateClassification &) = delete;
PlateClassification &operator=(const PlateClassification &) = delete;
explicit PlateClassification();
int Initialize(const std::string& model_filename, cv::Size input_size = cv::Size(96, 96), int threads = 1, bool use_half = false);
PlateColor Inference(const cv::Mat &bgr_pad);
const cv::Size &getMInputImageSize() const;
private:
cv::Size m_input_image_size_{};
std::shared_ptr<MNNAdapterInference> m_nn_adapter_;
};
} // namespace
#endif //ZEPHYRLPR_PLATE_CLASSIFICATION_H
@@ -0,0 +1,14 @@
//
// Created by tunm on 2023/1/22.
//
#ifndef ZEPHYRLPR_PLATE_CLS_COMMON_H
#define ZEPHYRLPR_PLATE_CLS_COMMON_H
typedef enum {
BLUE, ///< 蓝牌
GREEN, ///< 绿牌
YELLOW, ///< 黄牌
} PlateColor;
#endif //ZEPHYRLPR_PLATE_CLS_COMMON_H
@@ -0,0 +1,16 @@
//
// Created by tunm on 2022/12/29.
//
#pragma once
#ifndef ZEPHYRLPR_DETECT_ALL_H
#define ZEPHYRLPR_DETECT_ALL_H
#include "plate_det_common.h"
#include "plate_detector.h"
#include "det_backbone.h"
#include "det_header.h"
#include "det_arch.h"
#endif //ZEPHYRLPR_DETECT_ALL_H
@@ -0,0 +1,143 @@
//
// Created by tunm on 2023/2/8.
//
#include "det_arch.h"
#include "log.h"
namespace hyper {
DetArch::DetArch() = default;
int32_t DetArch::Initialize(const std::string& backbone_path, const std::string& head_path, int input_size, int threads,
float box_conf_threshold, float nms_threshold, bool use_half) {
int32_t ret;
m_input_size_ = input_size;
m_box_conf_threshold_ = box_conf_threshold;
m_nms_threshold_ = nms_threshold;
m_backbone_net_ = std::make_shared<DetBackbone>();
m_header_net_ = std::make_shared<DetHeader>();
ret = m_backbone_net_->Initialize(backbone_path, cv::Size(input_size, input_size), use_half);
if (ret != InferenceHelper::kRetOk) {
LOGE("Backbone model error.");
return InferenceHelper::kRetErr;
}
ret = m_header_net_->Initialize(head_path, input_size, use_half);
if (ret != InferenceHelper::kRetOk) {
LOGE("Head model error.");
return InferenceHelper::kRetErr;
}
if (input_size == 320) {
m_grid_num_ = 6300;
} else if (input_size == 640) {
m_grid_num_ = 25200;
} else {
return InferenceHelper::kRetErr;
}
return InferenceHelper::kRetOk;
}
template<class T>
void DetArch::Decode(T features, std::vector<PlateLocation> &outputs, float scale, float conf_threshold,
float nms_threshold) {
int item_num = 15;
for (int index = 0; index < m_grid_num_; ++index) {
auto det_score = features[index * item_num + 4];
if (det_score > conf_threshold) {
// std::cout << det_score << std::endl;
PlateLocation plate;
plate.det_confidence = det_score;
auto class_score_1 = features[index * item_num + 13] * det_score;
auto class_score_2 = features[index * item_num + 14] * det_score;
LayersNum type = class_score_1 > class_score_2 ? MONO : DOUBLE;
plate.layers = type;
auto x = features[index * item_num + 0];
auto y = features[index * item_num + 1];
auto w = features[index * item_num + 2];
auto h = features[index * item_num + 3];
std::vector<float> xyxy = xywh2xyxy(x, y, w, h);
plate.x1 = xyxy[0];
plate.y1 = xyxy[1];
plate.x2 = xyxy[2];
plate.y2 = xyxy[3];
for (int i = 0; i < 8; ++i) {
plate.kps[i] = features[index * item_num + i + 5];
}
outputs.push_back(plate);
}
}
nms(outputs, nms_threshold);
for (auto &plate : outputs) {
plate.x1 = plate.x1 / scale;
plate.y1 = plate.y1 / scale;
plate.x2 = plate.x2 / scale;
plate.y2 = plate.y2 / scale;
for (int i = 0; i < 4; ++i) {
plate.kps[i * 2 + 0] = plate.kps[i * 2 + 0] / scale;
plate.kps[i * 2 + 1] = plate.kps[i * 2 + 1] / scale;
}
}
}
void DetArch::Detection(const cv::Mat &bgr, bool is_resize, float scale) {
cv::Mat pad;
if (is_resize) {
int ori_w = bgr.cols;
int ori_h = bgr.rows;
int w, h;
if (ori_w > ori_h) {
scale = (float) m_input_size_ / ori_w;
w = m_input_size_;
h = ori_h * scale;
} else {
scale = (float) m_input_size_ / ori_h;
h = m_input_size_;
w = ori_w * scale;
}
int wpad = m_input_size_ - w;
int hpad = m_input_size_ - h;
cv::Mat resized_img;
cv::resize(bgr, resized_img, cv::Size(w, h));
cv::copyMakeBorder(resized_img, pad, 0, hpad, 0, wpad, cv::BORDER_CONSTANT, cv::Scalar(127.5, 127.5, 127.5));
// cv::imwrite("i.jpg", pad);
// cv::imshow("pad_border", pad);
// cv::waitKey(0);
} else {
pad = bgr;
}
double time;
time = (double)cv::getTickCount();
m_backbone_net_->Inference(pad);
time = ((double)cv::getTickCount() - time) / cv::getTickFrequency();
time = (double)cv::getTickCount();
m_header_net_->Inference(m_backbone_net_->getMOutputTensorInfoList()[0].GetDataAsFloat(),
m_backbone_net_->getMOutputTensorInfoList()[1].GetDataAsFloat(),
m_backbone_net_->getMOutputTensorInfoList()[2].GetDataAsFloat());
time = ((double)cv::getTickCount() - time) / cv::getTickFrequency();
// std::vector<float> tensor(m_header_net_->getMOutputTensorInfoList()[0].GetDataAsFloat(),
// m_header_net_->getMOutputTensorInfoList()[0].GetDataAsFloat() +
// m_header_net_->getMOutputTensorInfoList()[0].GetElementNum());
std::vector<PlateLocation>().swap(m_results_);
Decode(m_header_net_->getMOutputTensorInfoList()[0].GetDataAsFloat(), m_results_, scale, m_box_conf_threshold_, m_nms_threshold_);
}
} // namespace
@@ -0,0 +1,54 @@
//
// Created by tunm on 2023/2/8.
//
#pragma once
#ifndef ZEPHYRLPR_DET_ARCH_H
#define ZEPHYRLPR_DET_ARCH_H
#include "det_backbone.h"
#include "det_header.h"
#include "plate_det_common.h"
namespace hyper {
class DetArch {
public:
DetArch(const DetArch &) = delete;
DetArch &operator=(const DetArch &) = delete;
explicit DetArch();
int32_t Initialize(const std::string& backbone_path, const std::string& head_path, int input_size = 320, int threads=1, float box_conf_threshold = 0.3f, float nms_threshold = 0.6f, bool use_half = false);
void Detection(const cv::Mat& bgr, bool is_resize = false, float scale = 1.0f);
public:
std::vector<PlateLocation> m_results_; // 存放检测结果
private:
template<class T>
void Decode(T features, std::vector<PlateLocation> &outputs, float scale,
float conf_threshold = 0.5, float nms_threshold = 0.5);
private:
int m_input_size_{};
float m_box_conf_threshold_{};
float m_nms_threshold_{};
std::shared_ptr<DetBackbone> m_backbone_net_;
std::shared_ptr<DetHeader> m_header_net_;
int m_grid_num_ = 6300;
};
} // namespace
#endif //ZEPHYRLPR_DET_ARCH_H
@@ -0,0 +1,96 @@
//
// Created by Tunm-Air13 on 2023/2/8.
//
#include <iostream>
#include "det_backbone.h"
#include "log.h"
#include <utility>
namespace hyper {
DetBackbone::~DetBackbone() {
if (!m_nn_infer_) {
LOGD("[DetBackbone]Inference helper is not created\n");
return;
}
m_nn_infer_->Finalize();
m_nn_infer_.reset();
}
int32_t DetBackbone::Initialize(const std::string& model_filename, cv::Size input_size, bool use_half) {
m_input_image_size_ = std::move(input_size);
m_nn_infer_.reset(InferenceHelper::Create(InferenceHelper::kMnn));
if (m_nn_infer_->SetNumThreads(1) != InferenceHelper::kRetOk) {
m_nn_infer_.reset();
return InferenceHelper::kRetErr;
}
/* Set output tensor info */
m_output_tensor_info_list_.clear();
m_output_tensor_info_list_ = {OutputTensorInfo("948", TensorInfo::kTensorTypeFp32),
OutputTensorInfo("1061", TensorInfo::kTensorTypeFp32),
OutputTensorInfo("1174", TensorInfo::kTensorTypeFp32)};
if (m_nn_infer_->Initialize(model_filename, m_input_tensor_info_list_, m_output_tensor_info_list_) != InferenceHelper::kRetOk) {
m_nn_infer_.reset();
return InferenceHelper::kRetErr;
}
m_input_tensor_info_list_.clear();
InputTensorInfo input_tensor_info("input", TensorInfo::kTensorTypeFp32, true);
input_tensor_info.tensor_dims = { 1, 3, m_input_image_size_.height, m_input_image_size_.width };
input_tensor_info.data_type = InputTensorInfo::kDataTypeImage;
input_tensor_info.normalize.mean[0] = 0.0f;
input_tensor_info.normalize.mean[1] = 0.0f;
input_tensor_info.normalize.mean[2] = 0.0f;
input_tensor_info.normalize.norm[0] = 0.003921568627f;
input_tensor_info.normalize.norm[1] = 0.003921568627f;
input_tensor_info.normalize.norm[2] = 0.003921568627f;
m_input_tensor_info_list_.push_back(input_tensor_info);
return InferenceHelper::kRetOk;
}
int32_t DetBackbone::Inference(const cv::Mat &bgr_pad) {
InputTensorInfo& input_tensor_info = m_input_tensor_info_list_[0];
input_tensor_info.data = bgr_pad.data;
input_tensor_info.data_type = InputTensorInfo::kDataTypeImage;
input_tensor_info.image_info.width = bgr_pad.cols;
input_tensor_info.image_info.height = bgr_pad.rows;
input_tensor_info.image_info.channel = bgr_pad.channels();
input_tensor_info.image_info.crop_x = 0;
input_tensor_info.image_info.crop_y = 0;
input_tensor_info.image_info.crop_width = bgr_pad.cols;
input_tensor_info.image_info.crop_height = bgr_pad.rows;
input_tensor_info.image_info.is_bgr = true;
input_tensor_info.image_info.swap_color = false;
if (m_nn_infer_->PreProcess(m_input_tensor_info_list_) != InferenceHelper::kRetOk) {
return InferenceHelper::kRetErr;
}
if (m_nn_infer_->Process(m_output_tensor_info_list_) != InferenceHelper::kRetOk) {
return InferenceHelper::kRetErr;
}
return InferenceHelper::kRetOk;
}
DetBackbone::DetBackbone() {
}
std::vector<OutputTensorInfo> &DetBackbone::getMOutputTensorInfoList() {
return m_output_tensor_info_list_;
}
} // namespace;
@@ -0,0 +1,43 @@
//
// Created by Tunm-Air13 on 2023/2/8.
//
#pragma once
#ifndef ZEPHYRLPR_DET_BACKBONE_H
#define ZEPHYRLPR_DET_BACKBONE_H
#include "inference_helper_module/inference_helper.h"
#include "plate_det_common.h"
#include "opencv2/opencv.hpp"
namespace hyper {
class DetBackbone {
public:
DetBackbone();
~DetBackbone();
int32_t Initialize(const std::string& model_filename, cv::Size input_size = cv::Size(320, 320), bool use_half = false);
int32_t Inference(const cv::Mat &bgr_pad);
public:
std::vector<OutputTensorInfo> &getMOutputTensorInfoList();
private:
cv::Size m_input_image_size_{}; // 输入图像宽高
std::shared_ptr<InferenceHelper> m_nn_infer_; // 推理模块
std::vector<InputTensorInfo> m_input_tensor_info_list_;
std::vector<OutputTensorInfo> m_output_tensor_info_list_;
};
} // namespace;
#endif //ZEPHYRLPR_DET_BACKBONE_H
@@ -0,0 +1,100 @@
//
// Created by Tunm-Air13 on 2023/2/8.
//
#include "det_header.h"
#include "log.h"
namespace hyper {
#define INPUTS_NUM 3
#define OUTPUT "output"
DetHeader::DetHeader() = default;
DetHeader::~DetHeader() {
if (!m_nn_infer_) {
LOGD("Inference helper is not created\n");
return;
}
m_nn_infer_->Finalize();
m_nn_infer_.reset();
}
int32_t DetHeader::Initialize(const std::string& model_filename, int input_size, bool use_half) {
m_nn_infer_.reset(InferenceHelper::Create(InferenceHelper::kMnn));
if (m_nn_infer_->SetNumThreads(1) != InferenceHelper::kRetOk) {
m_nn_infer_.reset();
return InferenceHelper::kRetErr;
}
if (input_size == 320) {
m_header_list_ = {
{"948", { 1, 45, 40, 40 }},
{"1061", { 1, 45, 20, 20 }},
{"1174", { 1, 45, 10, 10 }},};
} else {
m_header_list_ = {
{"948", { 1, 45, 80, 80 }},
{"1061", { 1, 45, 40, 40 }},
{"1174", { 1, 45, 20, 20 }},};
}
m_output_tensor_info_list_.clear();
m_output_tensor_info_list_.emplace_back(OUTPUT, TensorInfo::kTensorTypeFp32);
if (m_nn_infer_->Initialize(model_filename, m_input_tensor_info_list_, m_output_tensor_info_list_) != InferenceHelper::kRetOk) {
m_nn_infer_.reset();
return InferenceHelper::kRetErr;
}
m_input_tensor_info_list_.clear();
/* Set input tensor info */
for (int i = 0; i < INPUTS_NUM; ++i) {
auto &head = m_header_list_[i];
InputTensorInfo input_tensor_info(head.input_name, TensorInfo::kTensorTypeFp32, false);
input_tensor_info.tensor_dims = head.dims;
input_tensor_info.data_type = InputTensorInfo::kTensorTypeFp32;
input_tensor_info.normalize.mean[0] = 0.0f;
input_tensor_info.normalize.mean[1] = 0.0f;
input_tensor_info.normalize.mean[2] = 0.0f;
input_tensor_info.normalize.norm[0] = 1.0f;
input_tensor_info.normalize.norm[1] = 1.0f;
input_tensor_info.normalize.norm[2] = 1.0f;
m_input_tensor_info_list_.push_back(input_tensor_info);
}
return InferenceHelper::kRetOk;
}
int32_t DetHeader::Inference(float* ptr3, float* ptr2, float* ptr1) {
m_input_tensor_info_list_[0].data_type = InputTensorInfo::kDataTypeBlobNchw;
m_input_tensor_info_list_[0].data = ptr3;
m_input_tensor_info_list_[1].data_type = InputTensorInfo::kDataTypeBlobNchw;
m_input_tensor_info_list_[1].data = ptr2;
m_input_tensor_info_list_[2].data_type = InputTensorInfo::kDataTypeBlobNchw;
m_input_tensor_info_list_[2].data = ptr1;
if (m_nn_infer_->PreProcess(m_input_tensor_info_list_) != InferenceHelper::kRetOk) {
return InferenceHelper::kRetErr;
}
if (m_nn_infer_->Process(m_output_tensor_info_list_) != InferenceHelper::kRetOk) {
return InferenceHelper::kRetErr;
}
return InferenceHelper::kRetOk;
}
std::vector<OutputTensorInfo> &DetHeader::getMOutputTensorInfoList() {
return m_output_tensor_info_list_;
}
void DetHeader::setMOutputTensorInfoList(const std::vector<OutputTensorInfo> &mOutputTensorInfoList) {
m_output_tensor_info_list_ = mOutputTensorInfoList;
}
} // namespace
@@ -0,0 +1,53 @@
//
// Created by Tunm-Air13 on 2023/2/8.
//
#ifndef ZEPHYRLPR_DET_HEADER_H
#define ZEPHYRLPR_DET_HEADER_H
#include "inference_helper_module/inference_helper.h"
#include "plate_det_common.h"
#include "opencv2/opencv.hpp"
namespace hyper {
typedef struct {
std::string input_name;
std::vector<int> dims;
} Header;
typedef std::vector<Header> HeaderList;
class DetHeader {
public:
DetHeader(const DetHeader &) = delete;
DetHeader &operator=(const DetHeader &) = delete;
explicit DetHeader();
~DetHeader();
int32_t Initialize(const std::string& model_filename, int input_size = 320, bool use_half = false);
int32_t Inference(float* ptr3, float* ptr2, float* ptr1);
std::vector<OutputTensorInfo> &getMOutputTensorInfoList();
void setMOutputTensorInfoList(const std::vector<OutputTensorInfo> &mOutputTensorInfoList);
private:
HeaderList m_header_list_;
std::unique_ptr<InferenceHelper> m_nn_infer_; // 推理模块
std::vector<InputTensorInfo> m_input_tensor_info_list_;
std::vector<OutputTensorInfo> m_output_tensor_info_list_;
};
} // namespace
#endif //ZEPHYRLPR_DET_HEADER_H
@@ -0,0 +1,67 @@
//
// Created by tunm on 2022/12/29.
//
#pragma once
#ifndef ZEPHYRLPR_PLATE_DET_COMMON_H
#define ZEPHYRLPR_PLATE_DET_COMMON_H
#include <algorithm>
namespace hyper {
enum LayersNum {
MONO, ///< 单层车牌
DOUBLE, ///< 双层车牌
};
typedef struct PlateLocation {
float x1; ///< 左上角点x坐标
float y1; ///< 左上角点y坐标
float x2; ///< 右下角点x坐标
float y2; ///< 右下角点y坐标
float det_confidence; ///< 目标检测的置信度
float kps[8]; ///< 四个角的角点信息
LayersNum layers; ///< 车牌层级类型
};
inline void nms(std::vector<PlateLocation> &input_plates, float nms_threshold) {
std::sort(input_plates.begin(), input_plates.end(),
[](PlateLocation a, PlateLocation b) { return a.det_confidence > b.det_confidence; });
std::vector<float> area(input_plates.size());
for (int i = 0; i < int(input_plates.size()); ++i) {
area[i] =
(input_plates.at(i).x2 - input_plates.at(i).x1 + 1) *
(input_plates.at(i).y2 - input_plates.at(i).y1 + 1);
}
for (int i = 0; i < int(input_plates.size()); ++i) {
for (int j = i + 1; j < int(input_plates.size());) {
float xx1 = (std::max)(input_plates[i].x1, input_plates[j].x1);
float yy1 = (std::max)(input_plates[i].y1, input_plates[j].y1);
float xx2 = (std::min)(input_plates[i].x2, input_plates[j].x2);
float yy2 = (std::min)(input_plates[i].y2, input_plates[j].y2);
float w = (std::max)(float(0), xx2 - xx1 + 1);
float h = (std::max)(float(0), yy2 - yy1 + 1);
float inter = w * h;
float ovr = inter / (area[i] + area[j] - inter);
if (ovr >= nms_threshold) {
input_plates.erase(input_plates.begin() + j);
area.erase(area.begin() + j);
} else {
j++;
}
}
}
}
inline std::vector<float> xywh2xyxy(float x, float y, float w, float h) {
float x1 = x - w / 2;
float y1 = y - h / 2;
float x2 = x + w / 2;
float y2 = y + h / 2;
return {x1, y1, x2, y2};
}
}
#endif //ZEPHYRLPR_PLATE_DET_COMMON_H
@@ -0,0 +1,107 @@
//
// Created by tunm on 2022/12/29.
//
#include "plate_detector.h"
namespace hyper {
PlateDetector::PlateDetector() {
}
int PlateDetector::Initialize(const char *model_path,
int input_size,
int threads,
float box_conf_threshold,
float nms_threshold,
bool use_half){
m_input_size_ = input_size;
m_box_conf_threshold_ = box_conf_threshold;
m_nms_threshold_ = nms_threshold;
float mean[] = {0.0f, 0.0f, 0.0f};
float normal[] = {0.003921568627f, 0.003921568627f, 0.003921568627f};
m_nn_adapter_ = std::make_shared<MNNAdapterInference>(model_path, threads, mean, normal, use_half);
m_nn_adapter_->Initialization("input", "output", m_input_size_, m_input_size_);
return 0;
}
void PlateDetector::Decode(const std::vector<float> &tensor, std::vector<PlateLocation> &outputs, float scale,
float conf_threshold, float nms_threshold) {
int grid_num = 6300;
int item_num = 15;
for (int index = 0; index < grid_num; ++index) {
auto det_score = tensor[index * item_num + 4];
if (det_score > conf_threshold) {
PlateLocation plate;
plate.det_confidence = det_score;
auto class_score_1 = tensor[index * item_num + 13] * det_score;
auto class_score_2 = tensor[index * item_num + 14] * det_score;
LayersNum type = class_score_1 > class_score_2 ? MONO : DOUBLE;
plate.layers = type;
auto x = tensor[index * item_num + 0];
auto y = tensor[index * item_num + 1];
auto w = tensor[index * item_num + 2];
auto h = tensor[index * item_num + 3];
std::vector<float> xyxy = xywh2xyxy(x, y, w, h);
plate.x1 = xyxy[0];
plate.y1 = xyxy[1];
plate.x2 = xyxy[2];
plate.y2 = xyxy[3];
for (int i = 0; i < 8; ++i) {
plate.kps[i] = tensor[index * item_num + i + 5];
}
outputs.push_back(plate);
}
}
nms(outputs, nms_threshold);
for (auto &plate : outputs) {
plate.x1 = plate.x1 / scale;
plate.y1 = plate.y1 / scale;
plate.x2 = plate.x2 / scale;
plate.y2 = plate.y2 / scale;
for (int i = 0; i < 4; ++i) {
plate.kps[i * 2 + 0] = plate.kps[i * 2 + 0] / scale;
plate.kps[i * 2 + 1] = plate.kps[i * 2 + 1] / scale;
}
}
}
void PlateDetector::Detection(const cv::Mat &bgr, bool is_resize, float scale) {
cv::Mat pad;
if (is_resize) {
int ori_w = bgr.cols;
int ori_h = bgr.rows;
int w, h;
if (ori_w > ori_h) {
scale = (float) m_input_size_ / ori_w;
w = m_input_size_;
h = ori_h * scale;
} else {
scale = (float) m_input_size_ / ori_h;
h = m_input_size_;
w = ori_w * scale;
}
int wpad = m_input_size_ - w;
int hpad = m_input_size_ - h;
cv::Mat resized_img;
cv::resize(bgr, resized_img, cv::Size(w, h));
cv::copyMakeBorder(resized_img, pad, 0, hpad, 0, wpad, cv::BORDER_CONSTANT, 0.0f);
} else {
pad = bgr;
}
// cv::imshow("a", pad);
auto output = m_nn_adapter_->Invoking(pad);
std::vector<PlateLocation>().swap(m_results_);
Decode(output, m_results_, scale, m_box_conf_threshold_, m_nms_threshold_);
}
}
@@ -0,0 +1,46 @@
//
// Created by tunm on 2022/12/29.
//
#pragma once
#ifndef ZEPHYRLPR_PLATE_DETECTOR_H
#define ZEPHYRLPR_PLATE_DETECTOR_H
#include "nn_module/mnn_adapter.h"
#include "plate_det_common.h"
namespace hyper {
class PlateDetector {
public:
PlateDetector(const PlateDetector &) = delete;
PlateDetector &operator=(const PlateDetector &) = delete;
explicit PlateDetector();
int Initialize(const char *model_path, int input_size = 320, int threads=1, float box_conf_threshold = 0.3, float nms_threshold = 0.6, bool use_half = false);
void Detection(const cv::Mat& bgr, bool is_resize = false, float scale = 1.0f);
private:
void Decode(const std::vector<float> &tensor, std::vector<PlateLocation> &outputs, float scale,
float conf_threshold = 0.5, float nms_threshold = 0.5);
public:
std::vector<PlateLocation> m_results_;
private:
int m_input_size_{};
float m_box_conf_threshold_{};
float m_nms_threshold_{};
std::shared_ptr<MNNAdapterInference> m_nn_adapter_;
};
}
#endif //ZEPHYRLPR_PLATE_DETECTOR_H
@@ -0,0 +1,12 @@
//
// Created by Tunm-Air13 on 2022/12/30.
//
#ifndef ZEPHYRLPR_RECOGNITION_ALL_H
#define ZEPHYRLPR_RECOGNITION_ALL_H
#include "plate_recognition.h"
#include "recognition_commom.h"
#include "recognition_engine.h"
#endif //ZEPHYRLPR_RECOGNITION_ALL_H
@@ -0,0 +1,82 @@
//
// Created by Tunm-Air13 on 2022/12/30.
//
#include "plate_recognition.h"
#include <utility>
#include "utils.h"
#include "plate_recognition_tokenize.h"
namespace hyper {
PlateRecognition::PlateRecognition() = default;
int PlateRecognition::Initialize(const std::string& model_filename, cv::Size input_size, int threads, float confidence_threshold,
bool use_half) {
m_input_image_size_ = std::move(input_size);
m_confidence_threshold_ = confidence_threshold;
float mean[] = {127.5f, 127.5f, 127.5f};
float normal[] = {1.0 / 127.5f, 1.0 / 127.5f, 1.0 / 127.5f};
m_nn_adapter_ = std::make_shared<MNNAdapterInference>(model_filename, threads, mean, normal, use_half);
m_nn_adapter_->Initialization("data", "output", m_input_image_size_.width, m_input_image_size_.height);
return 0;
}
TextLine PlateRecognition::Inference(const cv::Mat &bgr_pad) {
std::vector<float> output = m_nn_adapter_->Invoking(bgr_pad);
// SLOG_INFO("out: {}", output.size());
auto line = decode(output);
return line;
}
TextLine PlateRecognition::decode(const std::vector<float>& tensor) {
int text_total_num = 24;
int classify_num = 75;
IndexList index_list;
std::vector<float> max_list;
for (int index = 0; index < text_total_num; ++index) {
std::vector<float> mapping;
mapping.reserve(classify_num);
for (int i = 0; i < classify_num; ++i) {
mapping.push_back(tensor[index * classify_num + i]);
}
auto max_index = argmax(mapping.begin(), mapping.end());
float max_value = mapping[max_index];
index_list.push_back(max_index);
max_list.push_back(max_value);
}
TextLine line;
line.code = "";
float total_ = 0.0f;
for (int i = 0; i < index_list.size(); ++i) {
auto &idx = index_list[i];
IndexList::const_iterator result = std::find(m_ignored_tokens_.begin(), m_ignored_tokens_.end(), idx);
if (result == m_ignored_tokens_.end()) {
if ((i > 0) && index_list[i - 1] == idx) {
continue; // remove_duplicate
}
// std::cout << "b " << idx << std::endl;
line.char_index.push_back(idx);
line.char_scores.push_back(max_list[i]);
line.code += TOKENIZE[idx];
// SLOG_INFO("{}", TOKENIZE[idx]);
total_ += max_list[i];
}
}
line.average_score = total_ / line.char_scores.size();
return line;
}
const cv::Size &PlateRecognition::getMInputImageSize() const {
return m_input_image_size_;
}
float PlateRecognition::getMConfidenceThreshold() const {
return m_confidence_threshold_;
}
}
@@ -0,0 +1,51 @@
//
// Created by Tunm-Air13 on 2022/12/30.
//
#pragma once
#ifndef ZEPHYRLPR_PLATE_RECOGNITION_H
#define ZEPHYRLPR_PLATE_RECOGNITION_H
#include "nn_module/mnn_adapter.h"
#include "configuration.h"
#include "recognition_commom.h"
#include "basic_types.h"
namespace hyper {
class PlateRecognition {
public:
PlateRecognition(const PlateRecognition &) = delete;
PlateRecognition &operator=(const PlateRecognition &) = delete;
explicit PlateRecognition();
int Initialize(const std::string& model_filename, cv::Size input_size = REC_INPUT_SIZE, int threads = 1,
float confidence_threshold = 0.5, bool use_half = false);
TextLine Inference(const cv::Mat &bgr_pad);
const cv::Size &getMInputImageSize() const;
float getMConfidenceThreshold() const;
private:
TextLine decode(const std::vector<float>& tensor);
private:
cv::Size m_input_image_size_{}; // 输入图像宽高
float m_confidence_threshold_{}; // 置信度阈值
std::shared_ptr<MNNAdapterInference> m_nn_adapter_; // 推理模块
IndexList m_ignored_tokens_ = {0, }; // 需要被忽略的索引
};
}
#endif //ZEPHYRLPR_PLATE_RECOGNITION_H
@@ -0,0 +1,17 @@
//
// Created by tunm on 2023/1/25.
//
#pragma once
#ifndef ZEPHYRLPR_PLATE_RECOGNITION_TOKENIZE_H
#define ZEPHYRLPR_PLATE_RECOGNITION_TOKENIZE_H
#include <iostream>
static const std::vector<std::string> TOKENIZE = {
"blank", "'", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "J",
"K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "使", ""};
#endif //ZEPHYRLPR_PLATE_RECOGNITION_TOKENIZE_H
@@ -0,0 +1,23 @@
//
// Created by Tunm-Air13 on 2022/12/30.
//
#ifndef ZEPHYRLPR_RECOGNITION_COMMOM_H
#define ZEPHYRLPR_RECOGNITION_COMMOM_H
#include "basic_types.h"
namespace hyper {
typedef struct TextLine {
IndexList char_index; ///< 索引列表
std::vector<float> char_scores; ///< 置信度列表
float average_score; ///< 平均置信度
std::string code; ///< 车牌字符串
};
typedef std::vector<TextLine> TextLines; ///< 多行文本表达
}
#endif //ZEPHYRLPR_RECOGNITION_COMMOM_H
@@ -0,0 +1,151 @@
//
// Created by tunm on 2023/2/6.
//
#include "recognition_engine.h"
#include <utility>
#include "utils.h"
#include "plate_recognition_tokenize.h"
#define INPUT_NAME "data"
namespace hyper {
RecognitionEngine::RecognitionEngine() = default;
int32_t RecognitionEngine::Initialize(const std::string& model_filename, cv::Size input_size, int threads, float confidence_threshold,
bool use_half) {
m_input_image_size_ = input_size;
m_confidence_threshold_ = confidence_threshold;
/* Set output tensor info */
m_output_tensor_info_list_.clear();
m_output_tensor_info_list_.push_back(OutputTensorInfo("output", TensorInfo::kTensorTypeFp32));
m_nn_infer_.reset(InferenceHelper::Create(InferenceHelper::kMnn));
if (m_nn_infer_->SetNumThreads(threads) != InferenceHelper::kRetOk) {
m_nn_infer_.reset();
return InferenceHelper::kRetErr;
}
if (m_nn_infer_->Initialize(model_filename, m_input_tensor_info_list_, m_output_tensor_info_list_) != InferenceHelper::kRetOk) {
m_nn_infer_.reset();
return InferenceHelper::kRetErr;
}
m_input_tensor_info_list_.clear();
InputTensorInfo input_tensor_info(INPUT_NAME, TensorInfo::kTensorTypeFp32, true);
input_tensor_info.tensor_dims = { 1, 3, m_input_image_size_.height, m_input_image_size_.width };
input_tensor_info.data_type = InputTensorInfo::kDataTypeImage;
input_tensor_info.normalize.mean[0] = 127.5f;
input_tensor_info.normalize.mean[1] = 127.5f;
input_tensor_info.normalize.mean[2] = 127.5f;
input_tensor_info.normalize.norm[0] = 0.007843137255f;
input_tensor_info.normalize.norm[1] = 0.007843137255f;
input_tensor_info.normalize.norm[2] = 0.007843137255f;
m_input_tensor_info_list_.push_back(input_tensor_info);
return InferenceHelper::kRetOk;
}
int32_t RecognitionEngine::Inference(const cv::Mat &bgr_pad, TextLine &line) {
InputTensorInfo& input_tensor_info = m_input_tensor_info_list_[0];
input_tensor_info.data = bgr_pad.data;
input_tensor_info.data_type = InputTensorInfo::kDataTypeImage;
input_tensor_info.image_info.width = bgr_pad.cols;
input_tensor_info.image_info.height = bgr_pad.rows;
input_tensor_info.image_info.channel = bgr_pad.channels();
input_tensor_info.image_info.crop_x = 0;
input_tensor_info.image_info.crop_y = 0;
input_tensor_info.image_info.crop_width = bgr_pad.cols;
input_tensor_info.image_info.crop_height = bgr_pad.rows;
input_tensor_info.image_info.is_bgr = true;
input_tensor_info.image_info.swap_color = false;
if (m_nn_infer_->PreProcess(m_input_tensor_info_list_) != InferenceHelper::kRetOk) {
return InferenceHelper::kRetErr;
}
if (m_nn_infer_->Process(m_output_tensor_info_list_) != InferenceHelper::kRetOk) {
return InferenceHelper::kRetErr;
}
std::vector<float> output_score_raw_list(m_output_tensor_info_list_[0].GetDataAsFloat(),
m_output_tensor_info_list_[0].GetDataAsFloat() +
m_output_tensor_info_list_[0].GetElementNum());
decode(output_score_raw_list, line);
return InferenceHelper::kRetOk;
}
const cv::Size &RecognitionEngine::getMInputImageSize() const {
return m_input_image_size_;
}
float RecognitionEngine::getMConfidenceThreshold() const {
return m_confidence_threshold_;
}
int32_t RecognitionEngine::decode(const std::vector<float> &tensor, TextLine &line) {
int text_total_num = REC_MAX_CHAR_NUM;
int classify_num = REC_CHAR_CLASS_NUM;
IndexList index_list;
std::vector<float> max_list;
for (int index = 0; index < text_total_num; ++index) {
std::vector<float> mapping;
mapping.reserve(classify_num);
for (int i = 0; i < classify_num; ++i) {
mapping.push_back(tensor[index * classify_num + i]);
}
auto max_index = argmax(mapping.begin(), mapping.end());
float max_value = mapping[max_index];
index_list.push_back(max_index);
max_list.push_back(max_value);
}
line.code = "";
float total_ = 0.0f;
for (int i = 0; i < index_list.size(); ++i) {
auto &idx = index_list[i];
IndexList::const_iterator result = std::find(m_ignored_tokens_.begin(), m_ignored_tokens_.end(), idx);
if (result == m_ignored_tokens_.end()) {
if ((i > 0) && index_list[i - 1] == idx) {
continue; // remove_duplicate
}
// std::cout << "b " << idx << std::endl;
line.char_index.push_back(idx);
line.char_scores.push_back(max_list[i]);
line.code += TOKENIZE[idx];
// SLOG_INFO("{}", TOKENIZE[idx]);
total_ += max_list[i];
}
}
line.average_score = total_ / line.char_scores.size();
return InferenceHelper::kRetOk;
}
RecognitionEngine::~RecognitionEngine() {
if (!m_nn_infer_) {
LOGD("Inference helper is not created\n");
return;
}
m_nn_infer_->Finalize();
m_nn_infer_.reset();
}
} // namespace
@@ -0,0 +1,58 @@
//
// Created by tunm on 2023/2/6.
//
#pragma once
#ifndef ZEPHYRLPR_RECOGNITION_ENGINE_H
#define ZEPHYRLPR_RECOGNITION_ENGINE_H
#include "inference_helper_module/inference_helper.h"
#include "configuration.h"
#include "recognition_commom.h"
#include "basic_types.h"
#include "opencv2/opencv.hpp"
namespace hyper {
class RecognitionEngine {
public:
RecognitionEngine(const RecognitionEngine &) = delete;
RecognitionEngine &operator=(const RecognitionEngine &) = delete;
explicit RecognitionEngine();
~RecognitionEngine();
int32_t Initialize(const std::string& model_filename, cv::Size input_size = REC_INPUT_SIZE, int threads = 1,
float confidence_threshold = 0.5, bool use_half = false);
int32_t Inference(const cv::Mat &bgr_pad, TextLine &line);
const cv::Size &getMInputImageSize() const;
float getMConfidenceThreshold() const;
private:
int32_t decode(const std::vector<float>& tensor, TextLine &line);
private:
cv::Size m_input_image_size_{}; // 输入图像宽高
float m_confidence_threshold_{}; // 置信度阈值
std::unique_ptr<InferenceHelper> m_nn_infer_; // 推理模块
IndexList m_ignored_tokens_ = {0, }; // 需要被忽略的索引
std::vector<InputTensorInfo> m_input_tensor_info_list_;
std::vector<OutputTensorInfo> m_output_tensor_info_list_;
};
} // namespace
#endif //ZEPHYRLPR_RECOGNITION_ENGINE_H
+10
View File
@@ -0,0 +1,10 @@
//
// Created by tunm on 2023/1/25.
//
#ifndef ZEPHYRLPR_MNN_ADAPTER_ALL_H
#define ZEPHYRLPR_MNN_ADAPTER_ALL_H
#include "mnn_adapter.h"
#endif //ZEPHYRLPR_MNN_ADAPTER_ALL_H
+107
View File
@@ -0,0 +1,107 @@
//
// Created by tunm on 2022/4/24.
//
#include "mnn_adapter.h"
namespace hyper {
MNNAdapterInference::MNNAdapterInference(const std::string &model, int thread, const float *mean,
const float *normal, bool is_use_half,
bool use_model_bin, bool is_use_cuda) {
if (is_use_cuda) {
backend_ = MNN_FORWARD_CUDA;
} else {
backend_ = MNN_FORWARD_CPU;
}
if (use_model_bin) {
raw_model_ = std::shared_ptr<MNN::Interpreter>(
MNN::Interpreter::createFromBuffer(model.c_str(), model.size()));
} else {
raw_model_ = std::shared_ptr<MNN::Interpreter>(
MNN::Interpreter::createFromFile(model.c_str()));
}
_config.type = backend_;
//_config.layers = MNN_FORWARD_CUDA;
_config.numThread = 2;
//_config.numThread = thread;
MNN::BackendConfig backendConfig;
if (is_use_half) {
backendConfig.precision = MNN::BackendConfig::Precision_Low;
} else {
backendConfig.precision = MNN::BackendConfig::Precision_High;
}
backendConfig.power = MNN::BackendConfig::Power_High;
_config.backendConfig = &backendConfig;
for (int i = 0; i < 3; i++) {
this->mean[i] = mean[i];
this->normal[i] = normal[i];
}
}
MNNAdapterInference::~MNNAdapterInference() {
raw_model_->releaseModel();
raw_model_->releaseSession(sess);
}
void
MNNAdapterInference::Initialization(const std::string &input, const std::string &output, int width, int height) {
sess = raw_model_->createSession(_config);
tensor_shape_.resize(4);
tensor_shape_ = {1, 3, height, width};
input_ = raw_model_->getSessionInput(sess, input.c_str());
output_ = raw_model_->getSessionOutput(sess, output.c_str());
width_ = width;
height_ = height;
}
std::vector<float> MNNAdapterInference::Invoking(const cv::Mat &mat) {
assert(mat.rows == height_);
assert(mat.cols == width_);
MNN::CV::ImageProcess::Config config;
config.destFormat = image_format_dst_;
config.sourceFormat = image_format_src_;
for (int i = 0; i < 3; i++) {
config.mean[i] = mean[i];
config.normal[i] = normal[i];
}
std::unique_ptr<MNN::CV::ImageProcess> process(
MNN::CV::ImageProcess::create(config));
process->convert(mat.data, mat.cols, mat.rows, (int) mat.step1(), input_);
raw_model_->runSession(sess);
auto dimType = input_->getDimensionType();
if (output_->getType().code != halide_type_float) {
dimType = MNN::Tensor::TENSORFLOW;
}
std::shared_ptr<MNN::Tensor> outputUser(new MNN::Tensor(output_, dimType));
output_->copyToHostTensor(outputUser.get());
auto type = outputUser->getType();
auto size = outputUser->elementSize();
std::vector<float> tempValues(size);
if (type.code == halide_type_float) {
auto values = outputUser->host<float>();
for (int i = 0; i < size; ++i) {
tempValues[i] = values[i];
}
}
return tempValues;
}
void MNNAdapterInference::setImageFormatSrc(MNN::CV::ImageFormat imageFormatSrc) {
image_format_src_ = imageFormatSrc;
}
void MNNAdapterInference::setImageFormatDst(MNN::CV::ImageFormat imageFormatDst) {
image_format_dst_ = imageFormatDst;
}
}
+54
View File
@@ -0,0 +1,54 @@
//
// Created by tunm on 2022/4/24.
//
#ifndef ZEPHYRLPR_MNN_ADAPTER_H
#define ZEPHYRLPR_MNN_ADAPTER_H
#include <opencv2/opencv.hpp>
#include <MNN/ImageProcess.hpp>
#include <MNN/Interpreter.hpp>
#include <MNN/MNNDefine.h>
#include <MNN/Tensor.hpp>
#include <MNN/MNNForwardType.h>
namespace hyper {
class MNNAdapterInference {
public:
MNNAdapterInference(const std::string &model, int thread, const float mean[], const float normal[], bool is_use_half = false,
bool use_model_bin = false, bool is_use_cuda = false);
~MNNAdapterInference();
void Initialization(const std::string &input, const std::string &output, int width, int height);
std::vector<float> Invoking(const cv::Mat &mat);
void setImageFormatSrc(MNN::CV::ImageFormat imageFormatSrc);
void setImageFormatDst(MNN::CV::ImageFormat imageFormatDst);
private:
float mean[3]{};
float normal[3]{};
std::shared_ptr<MNN::Interpreter> raw_model_;
MNN::Tensor *input_{};
MNN::Tensor *output_{};
MNN::Session *sess{};
std::vector<int> tensor_shape_;
MNN::ScheduleConfig _config;
MNNForwardType backend_;
int width_{};
int height_{};
MNN::CV::ImageFormat image_format_src_{MNN::CV::ImageFormat::BGR};
MNN::CV::ImageFormat image_format_dst_{MNN::CV::ImageFormat::BGR};
};
}
#endif //ZEPHYRLPR_MNN_ADAPTER_H
+148
View File
@@ -0,0 +1,148 @@
//
// Created by Tunm-Air13 on 2022/12/30.
//
#pragma once
#ifndef ZEPHYRLPR_UTILS_H
#define ZEPHYRLPR_UTILS_H
#include <unistd.h>
inline bool exists(const std::string &file_path) {
return (access(file_path.c_str(), F_OK) != -1);
}
template<class ForwardIterator>
inline size_t argmin(ForwardIterator first, ForwardIterator last) {
return std::distance(first, std::min_element(first, last));
}
template<class ForwardIterator>
inline size_t argmax(ForwardIterator first, ForwardIterator last) {
return std::distance(first, std::max_element(first, last));
}
inline double L2NormFor2Points(const cv::Point2f &p1, const cv::Point2f &p2) {
auto r = pow((p1.x - p2.x), 2) + pow((p1.y - p2.y), 2);
return sqrt(r);
}
inline cv::Mat getRotateCropAndAlignMatrix(int img_crop_width, int img_crop_height,
float *points2d, int point_num = 4) {
assert(point_num == 4);
float dst_array[] = {0, 0,
(float) img_crop_width, 0,
(float) img_crop_width, (float) img_crop_height,
0, (float) img_crop_height};
cv::Mat src(4, 2, CV_32F, points2d);
cv::Mat dst(4, 2, CV_32F);
dst.data = (uchar *) dst_array;
cv::Mat transform_matrix = cv::getPerspectiveTransform(src, dst);
return transform_matrix;
}
inline void getRotateCropAndAlignPad(const cv::Mat &image, cv::Mat &out, float *points2d, int point_num = 4) {
assert(point_num == 4);
std::vector<cv::Point2f> vertex;
for (int i = 0; i < point_num; ++i) {
auto point = cv::Point2f(points2d[i * 2 + 0], points2d[i * 2 + 1]);
vertex.push_back(point);
}
int img_crop_width = (int) std::max(L2NormFor2Points(vertex[0], vertex[1]), L2NormFor2Points(vertex[2], vertex[3]));
int img_crop_height = (int) std::max(L2NormFor2Points(vertex[0], vertex[3]),
L2NormFor2Points(vertex[1], vertex[2]));
cv::Mat transform_matrix = getRotateCropAndAlignMatrix(img_crop_width, img_crop_height, points2d, point_num);
cv::warpPerspective(image, out, transform_matrix, cv::Size(img_crop_width, img_crop_height), cv::INTER_CUBIC,
cv::BORDER_REPLICATE);
if ((float) img_crop_height / (float) img_crop_width >= 1.5) {
cv::rotate(out, out, cv::ROTATE_90_CLOCKWISE);
}
}
inline void
imagePadding(const cv::Mat &image, cv::Mat &out, cv::Size target_size) {
int ori_w = image.cols;
int ori_h = image.rows;
int w, h;
float scale;
if (ori_w > ori_h) {
scale = (float) target_size.width / ori_w;
w = target_size.width;
h = ori_h * scale;
} else {
scale = (float) target_size.height / ori_h;
h = target_size.height;
w = ori_w * scale;
}
int wpad = std::max(target_size.width - w, 0);
int hpad = std::max(target_size.height - h, 0);
cv::Mat resized_img;
cv::resize(image, resized_img, cv::Size(w, h));
cv::copyMakeBorder(resized_img, out, 0, hpad, 0, wpad, cv::BORDER_CONSTANT, cv::Scalar(127.5f, 127.5f, 127.5f));
}
inline void
imagePadding(const cv::Mat &image, cv::Mat &out, float max_wh_ratio, cv::Size target_size,
int limited_max_width = 160,
int limited_min_width = 48) {
// cv::imshow("w", image);
// cv::waitKey(0);
// std::cout << image.size << std::endl;
int target_h = target_size.height;
int target_w = target_size.width;
max_wh_ratio = std::max(max_wh_ratio, (float) target_w / (float) target_h);
target_w = (int) (max_wh_ratio * target_h);
target_w = std::max(std::min(target_w, limited_max_width), limited_min_width);
auto image_h = image.rows;
auto image_w = image.cols;
auto ratio = (float) image_w / image_h;
int ratio_img_h = ceil(target_h * ratio);
ratio_img_h = std::max(ratio_img_h, limited_min_width);
int resized_w;
if (ratio_img_h > target_w) {
resized_w = target_w;
} else {
resized_w = int(ratio_img_h);
}
cv::Mat resized_image;
cv::resize(image, resized_image, cv::Size(resized_w, target_h));
int wpad = std::max(target_size.width - resized_w, 0);
int hpad = std::max(target_size.height - target_h, 0);
// std::cout << resized_w << "," << target_h << std::endl;
cv::copyMakeBorder(resized_image, out, 0, hpad, 0, wpad, cv::BORDER_CONSTANT, cv::Scalar(127.5f, 127.5f, 127.5f));
}
template<class T>
inline float xyxyArea(T x1, T y1, T x2, T y2) {
auto w = x2 - x1;
auto h = y2 - y1;
return (float) (w * h);
}
template<class T>
inline float boundBoxOverlap(const cv::Rect_<T> &box1, const cv::Rect_<T> &box2) {
if (box1.x > box2.x + box2.width) { return 0.0; }
if (box1.y > box2.y + box2.height) { return 0.0; }
if (box1.x + box1.width < box2.x) { return 0.0; }
if (box1.y + box1.height < box2.y) { return 0.0; }
T colInt = std::min(box1.x + box1.width, box2.x + box2.width) - std::max(box1.x, box2.x);
T rowInt = std::min(box1.y + box1.height, box2.y + box2.height) - std::max(box1.y, box2.y);
T intersection = colInt * rowInt;
T area1 = box1.width * box1.height;
T area2 = box2.width * box2.height;
return intersection / (area1 + area2 - intersection);
}
#endif //ZEPHYRLPR_UTILS_H
@@ -0,0 +1,54 @@
//
// Created by tunm on 2023/2/11.
//
#include "basic_types.h"
#include "../test_settings.h"
#include "opencv2/opencv.hpp"
#include "nn_implementation_module/classification/all.h"
#include "basic_types.h"
#include "utils.h"
using namespace hyper;
TEST_CASE("test_Classification", "[nn_cls]") {
PRINT_SPLIT_LINE
LOGD("[UnitTest]->Classification Model");
std::string model_path = GET_DATA("models/r2_mobile/litemodel_cls_96xh.mnn");
std::vector<std::string> predict_images_list = {
GET_DATA("images/align/1.jpg"),
GET_DATA("images/align/3.jpg"),
GET_DATA("images/align/5.jpg"),
};
std::vector<PlateColor> predict_results_cls = {
PlateColor::BLUE, PlateColor::YELLOW, PlateColor::GREEN,
};
std::vector<float> predict_results_confidence = {
0.9999293f, 0.8975975f, 0.9997952f
};
CHECK(predict_results_confidence.size() == predict_results_cls.size());
CHECK(predict_results_confidence.size() == predict_images_list.size());
ClassificationEngine clsEngine;
auto ret = clsEngine.Initialize(model_path, cv::Size_<int>(96, 96));
CHECK(ret == InferenceHelper::kRetOk);
SECTION("test_ClassificationModelPredict") {
for (int i = 0; i < predict_images_list.size(); ++i) {
cv::Mat img = cv::imread(predict_images_list[i]);
CHECK(!img.empty());
CHECK(img.cols == 96);
CHECK(img.rows == 96);
ret = clsEngine.Inference(img);
CHECK(ret == InferenceHelper::kRetOk);
CHECK(PlateColor(clsEngine.getMOutputColor()) == predict_results_cls[i]);
CHECK(clsEngine.getMOutputMaxConfidence() == Approx(predict_results_confidence[i]).epsilon(0.001));
}
}
}
+46
View File
@@ -0,0 +1,46 @@
//
// Created by tunm on 2023/2/11.
//
#include "basic_types.h"
#include "../test_settings.h"
#include "opencv2/opencv.hpp"
#include "nn_implementation_module/detect/all.h"
#include "basic_types.h"
#include "utils.h"
using namespace hyper;
TEST_CASE("test_Detection", "[nn_detect]") {
PRINT_SPLIT_LINE
LOGD("[UnitTest]->Detect Model");
std::string b_model_path = GET_DATA("models/r2_mobile/b320_backbone_h.mnn");
std::string h_model_path = GET_DATA("models/r2_mobile/b320_header_h.mnn");
cv::Mat test_image_1 = cv::imread(GET_DATA("images/pre.jpg"));
CHECK(test_image_1.cols == 320);
CHECK(test_image_1.rows == 320);
SECTION("test_SplitDetectionSplitModel") {
LOGD("Detect Model SplitModel");
DetArch det;
auto ret = det.Initialize(b_model_path, h_model_path, 320, 1);
CHECK(ret == InferenceHelper::kRetOk);
det.Detection(test_image_1);
auto &result = det.m_results_;
CHECK(result.size() == 1);
auto &box = result[0];
cv::Rect2f proposal_box(cv::Point(153, 205), cv::Point(183, 215));
cv::Rect2f detected_box(cv::Point(box.x1, box.y1), cv::Point(box.x2, box.y2));
auto iou = boundBoxOverlap(proposal_box, detected_box);
CHECK(iou > 0.85);
}
#if ENABLE_BENCHMARK_TEST
SECTION("test_DetectionBenchmark") {
LOGD("[UnitTest]->Detection Benchmark");
}
#endif
}
+51
View File
@@ -0,0 +1,51 @@
//
// Created by tunm on 2023/2/11.
//
#include "basic_types.h"
#include "../test_settings.h"
#include "opencv2/opencv.hpp"
#include "nn_implementation_module/recognition/all.h"
#include "basic_types.h"
#include "utils.h"
using namespace hyper;
TEST_CASE("test_Recognition", "[nn_rec]") {
PRINT_SPLIT_LINE
LOGD("[UnitTest]->Recognition Model");
std::string model_path = GET_DATA("models/r2_mobile/rpv3_mdict_160h.mnn");
std::vector<std::string> predict_images_list = {
GET_DATA("images/rec_crop/_0_津B6H920.jpg"),
GET_DATA("images/rec_crop/_1_皖KD01833.jpg"),
GET_DATA("images/rec_crop/_6_蒙B023H6.jpg"),
GET_DATA("images/rec_crop/_8_冀D5L690.jpg"),
};
std::vector<std::string> predict_results_code = {
"津B6H920", "皖KD01833", "蒙B023H6", "冀D5L690",
};
RecognitionEngine recEngine;
auto ret = recEngine.Initialize(model_path);
CHECK(ret == InferenceHelper::kRetOk);
SECTION("test_SplitDetectionSplitModel") {
LOGD("Rec Model RPV3");
for (int i = 0; i < predict_images_list.size(); ++i) {
cv::Mat img = cv::imread(predict_images_list[i]);
CHECK(!img.empty());
float wh_ratio = (float) img.cols / img.rows;
cv::Mat align_image_pad;
imagePadding(img, align_image_pad, wh_ratio, recEngine.getMInputImageSize());
TextLine line;
ret = recEngine.Inference(align_image_pad, line);
LOGD("%s -> %s", predict_results_code[i].c_str(), line.code.c_str());
CHECK(ret == InferenceHelper::kRetOk);
CHECK(strcmp(predict_results_code[i].c_str(), line.code.c_str()) == 0);
}
}
}
+10
View File
@@ -0,0 +1,10 @@
//
// Created by tunm on 2023/2/11.
//
#define CATCH_CONFIG_RUNNER
#include "test_settings.h"
int main(int argc, char* argv[]) {
return Catch::Session().run(argc, argv);;
}
+21
View File
@@ -0,0 +1,21 @@
//
// Created by tunm on 2023/2/11.
//
#include "test_settings.h"
std::string getTestDataDir() {
return "./resource/";
}
std::string getTestData(const std::string& name) {
return getTestDataDir() + "/" + name;
}
std::string getTestSaveDir() {
return "./resource/save";
}
std::string getTestSaveData(const std::string& name) {
return getTestSaveDir() + "/" + name;
}
+41
View File
@@ -0,0 +1,41 @@
//
// Created by tunm on 2023/2/11.
//
#pragma once
#ifndef ZEPHYRLPR_TEST_SETTINGS_H
#define ZEPHYRLPR_TEST_SETTINGS_H
#include <catch2/catch.hpp>
#include <iostream>
using namespace Catch::Detail;
#define ENABLE_BENCHMARK_TEST 0 // 是否开启性能测试相关用例执行,默认不开启
#define TEST_MSG(...) SPDLOG_LOGGER_CALL(spdlog::get("TEST"), spdlog::level::trace, __VA_ARGS__)
#define GET_DIR getTestDataDir()
#define GET_DATA(filename) getTestData(filename)
#define GET_TMP_DIR getTestSaveDir()
#define GET_TMP_DATA(filename) getTestSaveData(filename)
std::string getTestDataDir();
std::string getTestData(const std::string &name);
std::string getTestSaveDir();
std::string getTestSaveData(const std::string &name);
struct test_case_line {
~test_case_line() {
std::cout
<< "==============================================================================="
<< std::endl;
}
#define PRINT_SPLIT_LINE test_case_line split_line_x;
};
#endif //ZEPHYRLPR_TEST_SETTINGS_H