Closed bestpower closed 3 years ago
该问题是在这里问题的基础上提出的 补充:在输入到jni推理函数getFaceDetect前,已经在安卓端作了如下处理:
//计算原图最大边长
Bitmap bitmapBW ;
int max_size = Math.max(currentBitmap.getWidth(), currentBitmap.getHeight());
if(max_size>640) {
//以最大边长的正方形为背景图
bitmapBW = Bitmap.createBitmap(max_size, max_size, Bitmap.Config.ARGB_8888);
bwCanvas = new Canvas(bitmapBW);
bwCanvas.drawARGB(255, 0, 0, 0);
//将原图绘制在背景图左上位上
bwCanvas.drawBitmap(currentBitmap, 0, 0, null);
//将正方形背景图尺寸缩放到640x640
bitmapBW = ImageProcessUtil.scaleBitmap(bitmapBW, 640, 640, 0, false);
}else{
//以640边长的正方形为背景图
bitmapBW = Bitmap.createBitmap(640, 640, Bitmap.Config.ARGB_8888);
bwCanvas = new Canvas(bitmapBW);
bwCanvas.drawARGB(255, 0, 0, 0);
//将原图绘制在背景图左上位上
bwCanvas.drawBitmap(currentBitmap, 0, 0, null);
}
......
//以bitmapBW作为待检测的Bitmap输入对象
如果不方便从头分析的话,能否先帮忙看一下是否是模型加载和推理部分的代码有问题:
JNIEXPORT JNICALL jint
TNN_CLASSIFY(init)(JNIEnv *env, jobject thiz, jstring modelPath, jstring protoPath) {
std::string protoContent, modelContent;
std::string modelPathStr(jstring2string(env, modelPath));
std::string protoPathStr(jstring2string(env, protoPath));
protoContent = fdLoadFile(protoPathStr);
modelContent = fdLoadFile(modelPathStr);
TNN_NS::Status status = TNN_NS::TNN_OK;
TNN_NS::ModelConfig config = TNN_NS::ModelConfig();
config.model_type = TNN_NS::MODEL_TYPE_TNN;
config.params = {protoContent, modelContent};
auto net = std::make_shared<TNN_NS::TNN>();
status = net->Init(config);
device_type_ = TNN_NS::DEVICE_ARM;
TNN_NS::InputShapesMap shapeMap = TNN_NS::InputShapesMap();
TNN_NS::NetworkConfig network_config = TNN_NS::NetworkConfig();
network_config.library_path = {""};
network_config.device_type = device_type_;
faceDetect_net_ = net;
TNN_NS::DimsVector target_dims = {1, 3, 640, 640};
shapeMap.insert(make_pair("data", target_dims));
auto instance_0 = faceDetect_net_->CreateInst(network_config, status, shapeMap);
instance_0->SetCpuNumThreads(4);
faceDetect_instance_ = instance_0;
if (status != TNN_NS::TNN_OK) {
LOGE("TNN init failed %d", (int) status);
return -1;
}
return 0;
}
JNIEXPORT JNICALL jint
TNN_CLASSIFY(getFaceDetect)(JNIEnv *env, jobject thiz, jobject imageSource, jint width, jint height) {
jint ret = -1;
TNN_NS::Status status = TNN_NS::TNN_OK;
AndroidBitmapInfo sourceInfocolor;
void *sourcePixelscolor;
int origin_width = width;
int origin_height = height;
if (AndroidBitmap_getInfo(env, imageSource, &sourceInfocolor) < 0) {
return nullptr;
}
if (sourceInfocolor.format != ANDROID_BITMAP_FORMAT_RGBA_8888) {
return nullptr;
}
if (AndroidBitmap_lockPixels(env, imageSource, &sourcePixelscolor) < 0) {
return nullptr;
}
//orgin dims
TNN_NS::DimsVector origin_dims = {1, 4, origin_height, origin_width};
//target dims
TNN_NS::DimsVector target_dims = {1, 3, 640, 640};
auto input_mat = std::make_shared<TNN_NS::Mat>(device_type_, TNN_NS::N8UC4, origin_dims, sourcePixelscolor);
//here add the resize
auto target_mat = std::make_shared<TNN_NS::Mat>(device_type_, TNN_NS::N8UC3, target_dims);
TNN_NS::ResizeParam param = TNN_NS::ResizeParam();
TNN_NS::MatUtils::Resize(*input_mat, *target_mat, param, nullptr);
// step 1. set input mat
TNN_NS::MatConvertParam input_cvt_param = TNN_NS::MatConvertParam();
auto input_status = faceDetect_instance_->SetInputMat(target_mat, input_cvt_param, "data");//输入节点
if (input_status != TNN_NS::TNN_OK) {return nullptr;}
// step 2. Forward
status = faceDetect_instance_->ForwardAsync(nullptr);
if (status != TNN_NS::TNN_OK) {return nullptr;}
// step 3. get output mat
std::shared_ptr<TNN_NS::Mat> score_stride8 = nullptr;
std::shared_ptr<TNN_NS::Mat> bbox_stride8 = nullptr;
std::shared_ptr<TNN_NS::Mat> landmark_stride8 = nullptr;
std::shared_ptr<TNN_NS::Mat> score_stride16 = nullptr;
std::shared_ptr<TNN_NS::Mat> bbox_stride16 = nullptr;
std::shared_ptr<TNN_NS::Mat> landmark_stride16 = nullptr;
std::shared_ptr<TNN_NS::Mat> score_stride32 = nullptr;
std::shared_ptr<TNN_NS::Mat> bbox_stride32 = nullptr;
std::shared_ptr<TNN_NS::Mat> landmark_stride32 = nullptr;
TNN_NS::MatConvertParam output_cvt_param = TNN_NS::MatConvertParam();
/**
* stride 8
**/
status = faceDetect_instance_->GetOutputMat(score_stride8, output_cvt_param, "face_rpn_cls_prob_reshape_stride8");
if (status != TNN_NS::TNN_OK) {return nullptr;}
const auto *score_stride8_data = static_cast<float*>(score_stride8->GetData());
LOGE("score_stride8 shape: %d %d %d %d", score_stride8->GetBatch(), score_stride8->GetChannel(), score_stride8->GetHeight(), score_stride8->GetWidth());
status = faceDetect_instance_->GetOutputMat(bbox_stride8, output_cvt_param, "face_rpn_bbox_pred_stride8");
if (status != TNN_NS::TNN_OK) {return nullptr;}
const auto *bbox_stride8_data = static_cast<float*>(bbox_stride8->GetData());
LOGE("bbox_stride8 shape: %d %d %d %d", bbox_stride8->GetBatch(), bbox_stride8->GetChannel(), bbox_stride8->GetHeight(), bbox_stride8->GetWidth());
status = faceDetect_instance_->GetOutputMat(landmark_stride8, output_cvt_param, "face_rpn_landmark_pred_stride8");
if (status != TNN_NS::TNN_OK) {return nullptr;}
const auto *landmark_stride8_data = static_cast<float*>(landmark_stride8->GetData());
LOGE("landmark_stride8 shape: %d %d %d %d", landmark_stride8->GetBatch(), landmark_stride8->GetChannel(), landmark_stride8->GetHeight(), landmark_stride8->GetWidth());
/**
* stride 16
**/
status = faceDetect_instance_->GetOutputMat(score_stride16, output_cvt_param, "face_rpn_cls_prob_reshape_stride16");
if (status != TNN_NS::TNN_OK) {return nullptr;}
const auto *score_stride16_data = static_cast<float*>(score_stride16->GetData());
LOGE("score_stride16 shape: %d %d %d %d", score_stride16->GetBatch(), score_stride16->GetChannel(), score_stride16->GetHeight(), score_stride16->GetWidth());
status = faceDetect_instance_->GetOutputMat(bbox_stride16, output_cvt_param, "face_rpn_bbox_pred_stride16");
if (status != TNN_NS::TNN_OK) {return nullptr;}
const auto *bbox_stride16_data = static_cast<float*>(bbox_stride16->GetData());
LOGE("bbox_stride16 shape: %d %d %d %d", bbox_stride16->GetBatch(), bbox_stride16->GetChannel(), bbox_stride16->GetHeight(), bbox_stride16->GetWidth());
status = faceDetect_instance_->GetOutputMat(landmark_stride16, output_cvt_param, "face_rpn_landmark_pred_stride16");
if (status != TNN_NS::TNN_OK) {return nullptr;}
const auto *landmark_stride16_data = static_cast<float*>(landmark_stride16->GetData());
LOGE("landmark_stride16 shape: %d %d %d %d", landmark_stride16->GetBatch(), landmark_stride16->GetChannel(), landmark_stride16->GetHeight(), landmark_stride16->GetWidth());
/**
* stride 32
**/
status = faceDetect_instance_->GetOutputMat(score_stride32, output_cvt_param, "face_rpn_cls_prob_reshape_stride32");
if (status != TNN_NS::TNN_OK) {return nullptr;}
const auto *score_stride32_data = static_cast<float*>(score_stride32->GetData());
LOGE("score_stride32 shape: %d %d %d %d", score_stride32->GetBatch(), score_stride32->GetChannel(), score_stride32->GetHeight(), score_stride32->GetWidth());
status = faceDetect_instance_->GetOutputMat(bbox_stride32, output_cvt_param, "face_rpn_bbox_pred_stride32");
if (status != TNN_NS::TNN_OK) {return nullptr;}
const auto *bbox_stride32_data = static_cast<float*>(bbox_stride32->GetData());
LOGE("bbox_stride32 shape: %d %d %d %d", bbox_stride32->GetBatch(), bbox_stride32->GetChannel(), bbox_stride32->GetHeight(), bbox_stride32->GetWidth());
status = faceDetect_instance_->GetOutputMat(landmark_stride32, output_cvt_param, "face_rpn_landmark_pred_stride32");
if (status != TNN_NS::TNN_OK) {return nullptr;}
const auto *landmark_stride32_data = static_cast<float*>(landmark_stride32->GetData());
LOGE("landmark_stride32 shape: %d %d %d %d", landmark_stride32->GetBatch(), landmark_stride32->GetChannel(), landmark_stride32->GetHeight(), landmark_stride32->GetWidth());
AndroidBitmap_unlockPixels(env, imageSource);
/*
... 推理结果generate融合代码
*/
env->DeleteLocalRef(imageSource);
ret = 0;
return ret;
}
可以通过以下步骤一步步排查问题: 1、排查转出来的TNN模型是否正确,具体是跟onnx模型结果做对比。可以通过在模型转换的时候加上 -align 参数来校验,TNN的模型结果是否和onnx模型结果一致。 2、确定具体TNN执行推理的平台结果是否正确。可以通过model_check工具进行排查(https://github.com/Tencent/TNN/blob/master/doc/cn/development/model_check.md),通过-d指定需要运行的平台,最终看此平台结果是否与模型的参考输出结果一致。 3、如果上述都没有问题,那说明问题不在TNN这边,有可能在预处理或者后处理部分。可以参考的工程进行对比,是不是送入模型的数据有问题。或者模型的输出数据的处理有问题。
前两步排查是通过了的,关于预处理的方法已经是参考了其他demo来做的,也试了各种配置还是检测不到 现在主要有两点疑问: 1、这个模型在ncnn的预处理中是不需要将输入图像数据缩放即可检测出结果的,貌似在model_check时提示这个模型的输入可以是任意的,tnn是否一定要指定输入尺寸或者有其他什么处理方式? 2、输出节点数据类型NCHW_FLOAT数组的排列顺序是怎样的,我在mnn框架上也是设置的NCHW类型的输出,打日志它们输出的nchw size是一致的,通过同一套generate代码mnn就可以正确输出结果,而tnn就不行?
TNN相较于ncnn,对于数据输入预处理的可配置项明显多了很多,之前看了你们源码自带的Demo以及其他人写的Demo,预处理这一块的代码感觉都写的比较随意,看着很乱,我这边参考也只能一个个乱试,到现在也还是检测不出来,浪费了好多时间,最近我们也是在考虑用新的框架,如果大家总在这些问题上卡住得不到解决,那你们这个新框架也是很难推广的,所以关于输入节点数据预处理的流程及其他类似的问题,你们官方能否出一份标准文档?
看了一下代码, TNN_NS::MatUtils::Resize(input_mat, target_mat, param, nullptr); 这一步没有检查返回值,这里应该会返回错误,因为输入和输出的mat的mat type不一致,输入是N8UC4,但是输出是N8UC3。这里报错的话,就不会做resize操作,确认一下这里是否正确执行了。
看了一下代码, TNN_NS::MatUtils::Resize(input_mat, target_mat, param, nullptr); 这一步没有检查返回值,这里应该会返回错误,因为输入和输出的mat的mat type不一致,输入是N8UC4,但是输出是N8UC3。这里报错的话,就不会做resize操作,确认一下这里是否正确执行了。
我这边都试过,Resize操作在java层已经做过了,模型输入如果是N8UC4 {1, 4, 640, 640}会报错,输入N8UC3 {1, 3, 640, 640}会正常执行,但是最后也检测不出来
那建议先检查一下,SetInputMat的那个Mat数据是否正确。送入Mat的数据是RGB或者BGR的INT8数据吗?是否需要设置MatConvertParam?
那建议先检查一下,SetInputMat的那个Mat数据是否正确。送入Mat的数据是RGB或者BGR的INT8数据吗?是否需要设置MatConvertParam?
输入是BGR格式,按你们代码里的解释就是N8UC3,MatConvertParam里就是默认参数,我用ncnn和mnn框架推理都没有mean和normal的设置,其他我就是在java层做了个尺寸变换和背景填充,变成ARGB8888和640*640的Bitmap输入到native代码里
那这个模型的输入是RGB 3通道的,为什么要做背景填充。将ARGB8888数据送入native之后,有没有做一个ARGB8888到RGB的转换?
那这个模型的输入是RGB 3通道的,为什么要做背景填充。将ARGB8888数据送入native之后,有没有做一个ARGB8888到RGB的转换?
做背景填充是因为模型的输入size要求是640x640,而原图的宽高不一定相等,所以先做填充再做尺寸变换来保证输入尺寸的一致性。没有做ARGB8888到RGB的转换,我看框架里是有带CvtColor的方法,但是没有这两个格式转换类型,这种情况该怎么处理?
typedef enum {
COLOR_CONVERT_NV12TOBGR = 0x00,
COLOR_CONVERT_NV12TOBGRA = 0x01,
COLOR_CONVERT_NV21TOBGR = 0x02,
COLOR_CONVERT_NV21TOBGRA = 0x03,
COLOR_CONVERT_BGRTOGRAY = 0x04,
COLOR_CONVERT_BGRATOGRAY = 0x05,
COLOR_CONVERT_RGBTOGRAY = 0x06,
COLOR_CONVERT_RGBATOGRAY = 0x07,
} PUBLIC ColorConversionType;
RGBA到RGB的转换,直接将Alpah通道(第四个通道)的数据去除就行。重新分配一个存储RGB数据的内存,逐个像素进行重新赋值,取出RGB的值。
关于输入通道数问题我还有个疑问,我之前看了很多Demo模型文件都是3通道的输入参数,但在对应native代码里看到很多都是写的4通道的数据输入,这些情况是代码有误还是模型本身支持4通道的输入或者自动过滤掉了Alpah通道数据?
Demo里面使用的是TNN的API接口,TNN内部不会自动过滤Alpha通道,传入的Mat,数据的排布一定要和MatType对应,比如,实际的数据排布是RGBA,但是传入的MatType是N8UC3,那内部只会按照RGB的数据格式去读取,这样肯定就有问题。Demo里面肯定做了处理,具体是哪个demo写的是4通道输入?
TNN\examples\android\demo\src\main\jni\cc\blazeface_detector_jni.cc 模型输入要求是{}1, 3, 128, 128},而代码里写的是{1, 4, 128, 128}
//orgin dims
std::vector<int> origin_dims = {1, 4, orig_height, orig_width};
std::vector<int> resize_dims = {1, 4, target_height, target_width};
TNN_NS::DeviceType dt = TNN_NS::DEVICE_ARM;
auto input_mat = std::make_shared<TNN_NS::Mat>(dt, TNN_NS::N8UC4, origin_dims,
sourcePixelscolor);
//here add the resize
auto resize_mat = std::make_shared<TNN_NS::Mat>(dt, TNN_NS::N8UC4, resize_dims);
TNN_NS::ResizeParam param;
TNN_NS::MatUtils::Resize(*input_mat, *resize_mat, param, NULL);
TNN\examples\android\demo\src\main\jni\cc\face_detector_jni.cc 模型输入要求是{1, 3, 240, 320},而代码里写的是{1, 4, 240, 320}
TNN_NS::DeviceType dt = TNN_NS::DEVICE_ARM;
TNN_NS::DimsVector target_dims = {1, 4, height, width};
auto input_mat = std::make_shared<TNN_NS::Mat>(dt, TNN_NS::N8UC4, target_dims, sourcePixelscolor);
这种情况是支持的,就是输入的Mat格式是N8UC4,输入的数据也是8UC4的排布,如果模型是3维的,那内部会忽略第四维的数据。总之,输入的Mat的MatType需要和输入的数据排布一致。
找到出问题的原因了: 1、推理输入问题:按N8UC4和{1,4,640,640}格式输入推理就可以,不用做RGBA到RGB的转换 2、其他问题:在generate代码中,有个写法在tnn工程里调用不被支持,代码执行到那里就会报错,但按N8UC3和{1,3,640,640}的格式输入却不报错,而在ncnn和mnn工程里也不报错,具体原因未知。
能否贴一下出错位置的代码?我们排查一下,是否是内部少了异常判断。
native推理输入按以下写法就可以了,但是MatType写成TNN_NS::N8UC3,origin_dims写成{1, 4, origin_height, origin_width}也不会报错
AndroidBitmapInfo sourceInfocolor;
void *sourcePixelscolor;
int origin_width = width;
int origin_height = height;
if (AndroidBitmap_getInfo(env, imageSource, &sourceInfocolor) < 0) {
return nullptr;
}
if (sourceInfocolor.format != ANDROID_BITMAP_FORMAT_RGBA_8888) {
return nullptr;
}
if (AndroidBitmap_lockPixels(env, imageSource, &sourcePixelscolor) < 0) {
return nullptr;
}
TNN_NS::DimsVector origin_dims = {1, 4, origin_height, origin_width};
auto input_mat = std::make_shared<TNN_NS::Mat>(TNN_NS::DEVICE_ARM, TNN_NS::N8UC4, origin_dims, sourcePixelscolor);
generate代码也是在native中执行的,在执行到
#pragma omp parallel sections
语句会报错,做native调试会指向到 __kmp_abort_process libgcc2.c:?,我把这个语句去掉就OK了,报错应该跟TNN库无关,可能跟编译配置有关
好的,内部确实没有判断MatType和dims之间是否有矛盾,后面考虑会打印一个错误信息来提示调用者。
那这个问题先暂时这样了,后面推理如果再出现什么问题再来提
1. 环境(environment)
Build OS and Version: Ubuntu 18.04
RunTime OS Version: Android 7.1
RunTime DEVICE: ARM
2. Github版本
branch:hotfix_issue_1109
commit: ebc079cfc5b3c0c437d0eed1479731bb52ef0023
3. 编译方式(compile method)
1、模型转换工具编译: 编译脚本路径 TNN/tools/onnx2tnn/onnx-converter/build.sh
2、模型转换 转换脚本路径及命令 python3 TNN/tools/convert2tnn/converter.py onnx2tnn $ONNX_MODEL_DIR/${onnx_model} -optimize -v v3.0 -o $OUTPUT_TNN_MODEL_DIR/
3、推理库编译 编译命令 export ANDROID_NDK=/home/wyu/SDK/NDK/android-ndk-r19c cd TNN/scripts rm -rf release ./build_android.sh cp -r release $OUTPUT_TNN_LID_DIR
4. 编译日志(build log)
5. 详细描述bug 情况 (Describe the bug)
模型加载与推理部分核心代码:
//模型与推理网络加载 JNIEXPORT JNICALL jint TNN_CLASSIFY(init)(JNIEnv *env, jobject thiz, jstring modelPath, jstring protoPath) {
}
//模型推理代码 //输入参数主要为待检测图像bitmap对象、待检测图像宽度、待检测图像高度 JNIEXPORT JNICALL jint TNN_CLASSIFY(getFaceDetect)(JNIEnv *env, jobject thiz, jobject imageSource, jint width, jint height) {
}
6. 运行日志(runtime log)
模型详见附件文件! models.zip