自己训练了一个只有2种类别的yolov8模型之后,部署到瑞芯微RKNN。
踩坑一:类别的变化
之前用COCO数据集训练的.pt模型转rknn后,output0的shape为8400 x 176.
而把自定义数据集训练的模型转rknn后,output0的shape为8400 x 98.
为什么会不一样?一度以为模型或代码有问题,拿图片测了下发现模型没问题,能检测出来。
分析一下8400x176,其中8400是proposal的个数,176是box相关的64+80个类别+32的mask coefficient.
现在类别变成了2,自然要变成64+2+32=98.
所以,如果后处理中用到类别数=80,画图中的类别标签用了80个的,这里需要修改一下。
踩坑二:模型转化(.pt 转 .rknn)
用转化后的rknn模型检测,只能检测出一种类别,另一种检测不出来。
于是从后处理出发找原因,
现输入图片中有两种物体,每种一个,理论上应该检测出2个物体。
先看NMS之前检测了多少个物体。
std::vector<int> picked;
nms_sorted_bboxes(proposals, picked); //picked里面保存的是proposals的下标
int count = picked.size();
这里NMS之前 picked.size() = 1, 说明NMS之前就只检测出一个,已经不对了。
回到NMS之前的处理,
这里选择较大概率的类别作为label,保存label, score和box坐标。
int label = -1;
float score = -FLT_MAX;
//找到最大score和对应的label
for (int k = 0; k < num_class; k++) {
float confidence = deqnt_affine_to_f32(score_ptr[k], zp,
scale); //反量化,int转float
if (confidence > score) {
label = k;
score = confidence;
}
}
float box_prob = sigmoid(score);
if (box_prob >= thres) {
//忽略一段处理过程
obj.rect.x = x0;
obj.rect.y = y0;
obj.rect.width = x1 - x0;
obj.rect.height = y1 - y0;
obj.label = label;
obj.prob = box_prob;
}
于是追踪label = 0和label = 1的检测概率。
用同一图片测试,很神奇的是.pt模型(转rknn之前)测试中,label=0和label=1的目标 检测概率都>0.9,
而转rknn之后label=1的目标 检测概率只有0.5,label=0的目标 检测概率小得出奇。
观察到score作softmax运算前,label=0的目标 反量化的检测概率小于-7.
认为这是反量化出现了问题,不过反量化用的是从rknn模型中读出的量化参数scales和zp。
for (int i = 0; i < io_num.n_output; ++i) {
output_attrs[i].index = i;
if(rknn_query(ctx, RKNN_QUERY_OUTPUT_ATTR, &(output_attrs[i]), sizeof(rknn_tensor_attr)) < 0) {
LOGE("rknn_query output_attrs[%d] fail!\n", i);
return;
}
// set out_scales/out_zps for post_process
out_scales.push_back(output_attrs[i].scale);
out_zps.push_back(output_attrs[i].zp);
}
如果量化参数有问题,那就是转rknn模型的过程有问题。
找到转rknn模型的代码。
OUT_DIR = "rknn_models"
RKNN_MODEL_PATH = '{}/{}.rknn'.format(OUT_DIR,exp)
if NEED_BUILD_MODEL:
DATASET = './dataset.txt'
rknn.config(mean_values=[[0, 0, 0]], std_values=[[255, 255, 255]], target_platform="rkXXXX")
# Load model
print('--> Loading model')
ret = rknn.load_onnx(MODEL_PATH)
if ret != 0:
print('load model failed!')
exit(ret)
print('done')
# Build model
print('--> Building model')
ret = rknn.build(do_quantization=True, dataset=DATASET)
if ret != 0:
print('build model failed.')
exit(ret)
print('done')
# Export rknn model
if not os.path.exists(OUT_DIR):
os.mkdir(OUT_DIR)
print('--> Export RKNN model: {}'.format(RKNN_MODEL_PATH))
ret = rknn.export_rknn(RKNN_MODEL_PATH)
if ret != 0:
print('Export rknn model failed.')
exit(ret)
print('done')
else:
ret = rknn.load_rknn(RKNN_MODEL_PATH)
rknn.release()
注意到rknn模型的转换用到了dataset.txt,它指定的是如下图片。
这是原版COCO训练下的yolov8的图片,而现在的自定义数据集中没有这样的图片。
换成现在自定义数据集中的图片,再次转rknn模型。
然后观察scales和zp这两个量化值,发现跟之前不一样了。
再次检测,成功检测出2个目标,且检测概率>0.9,和.pt模型差不多。
总结:
转rknn模型时用的图片可能需要用现有数据集中的图片,不要用不相关的图片。
(原理不清楚)