人流量计数
软件源代码,相比之前,加入了行人重识别网络,更好的提供跟踪效果,可以直接运行在rk3568的板子上。
目标检测
- 自己大约做了5000张图片,安装录像设备,抓拍公司人员信息,然后标注,训练,花了1-2个月,在公司环境下,目标检测效果很好。换一个环境,比如说地铁,工厂发现效果很不行。得出结论:图片中行人的角度和背景单一,标注不精确,影响模型训练。
- 直接用训练好的模型,比如RK356X/RK3588 RKNN SDK
目标跟踪
- ByteTrack算法,要求FPS达到30以上,但是实际运行只能达到17FS,就算能达到,trackID变化的频率还是较快,不采用;
- 使用Torchreid行人重识别网络,提取运动特征;DeepSort算法 根据运动特征和位置,大幅度提高了跟踪效果。
代码解析
-
选择模型
osnet_x0_25_imagenet从Torchreid下载,然后模型转换成RKNN,参考模型转换的文章;
yolov5s_relu_tk2_RK356X_i8使用的是RK356X/RK3588 RKNN SDK -
RKNN模型使用
RKNN模型使用都是一样的,初始化,设置输入,输出,处理结果,以osnet_x0_25_imagenet为例;
初始化模型:
bool FeatureTensor::init(const std::string &modelPath) {
LOGI("try feature rknn_init!");
const char *mp = modelPath.c_str();
// Load model
FILE *fp = fopen(mp, "rb");
if (fp == NULL) {
LOGE("feature fopen %s fail!\n", mp);
return false;
}
fseek(fp, 0, SEEK_END);
int model_len = ftell(fp);
void *model = malloc(model_len);
fseek(fp, 0, SEEK_SET);
if (model_len != fread(model, 1, model_len, fp)) {
LOGE("fread %s fail!\n", mp);
free(model);
fclose(fp);
return false;
}
fclose(fp);
// RKNN_FLAG_ASYNC_MASK: enable async mode to use NPU efficiently.
int ret = rknn_init(&feature_ctx, model, model_len,
RKNN_FLAG_PRIOR_MEDIUM | RKNN_FLAG_ASYNC_MASK,
NULL);
free(model);
if (ret < 0) {
LOGE("feature rknn_init fail! ret=%d\n", ret);
return false;
}
ret = rknn_query(feature_ctx, RKNN_QUERY_IN_OUT_NUM, &feature_io_num, sizeof(feature_io_num));
if (ret < 0) {
LOGI("feature rknn_init error ret=%d\n", ret);
return false;
}
LOGI("feature model input num: %d, output num: %d\n", feature_io_num.n_input,
feature_io_num.n_output);
rknn_tensor_attr input_attrs[feature_io_num.n_input];
memset(input_attrs, 0, sizeof(input_attrs));
for (int i = 0; i < feature_io_num.n_input; i++) {
input_attrs[i].index = i;
ret = rknn_query(feature_ctx, RKNN_QUERY_INPUT_ATTR, &(input_attrs[i]),
sizeof(rknn_tensor_attr));
if (ret < 0) {
LOGI("feature rknn_init error ret=%d\n", ret);
return false;
}
}
feature_output_attrs = new rknn_tensor_attr[feature_io_num.n_output];
for (int i = 0; i < feature_io_num.n_output; i++) {
feature_output_attrs[i].index = i;
ret = rknn_query(feature_ctx, RKNN_QUERY_OUTPUT_ATTR, &(feature_output_attrs[i]),
sizeof(rknn_tensor_attr));
if (ret < 0) {
LOGI("feature rknn_init error ret=%d\n", ret);
return false;
}
}
LOGI("feature rknn_init success!");
return true;
}
运行模型:
bool FeatureTensor::getRectsFeature( cv::Mat &img, DETECTIONS &d) {
for (DETECTION_ROW &dbox: d) {
// cv::Rect rc = cv::Rect(int(dbox.tlwh(0)), int(dbox.tlwh(1)),
// int(dbox.tlwh(2)), int(dbox.tlwh(3)));
// rc.x -= (rc.height * 0.5 - rc.width) * 0.5;
// rc.width = rc.height * 0.5;
// rc.x = (rc.x >= 0 ? rc.x : 0);
// rc.y = (rc.y >= 0 ? rc.y : 0);
// rc.width = (rc.x + rc.width <= img.cols ? rc.width : (img.cols - rc.x));
// rc.height = (rc.y + rc.height <= img.rows ? rc.height : (img.rows - rc.y));
// cv::Mat mattmp = img.clone();
// auto t = GetCurrentTime();
// cv::Mat resizedImage;
//
// cv::resize(img, resizedImage, cv::Size(width_, height_));
std::vector<float> inputTensorValues;
size_t inputTensorSize;
preprocess(img, inputTensorValues, inputTensorSize);
//LOGI("feature preprocess finish");
rknn_input inputs[1];
memset(inputs, 0, sizeof(inputs));
inputs[0].index = 0;
inputs[0].buf = img.data;
inputs[0].type = RKNN_TENSOR_UINT8;
inputs[0].size = width_ * height_ * 3;
inputs[0].fmt = RKNN_TENSOR_NHWC;
inputs[0].pass_through = 0;
//阻塞状态
int ret = rknn_inputs_set(feature_ctx, feature_io_num.n_input, inputs);
if (ret < 0) {
LOGE("feature rknn_input_set fail! ret=%d\n", ret);
}
rknn_output outputs[feature_io_num.n_output];
memset(outputs, 0, sizeof(outputs));
for (int i = 0; i < feature_io_num.n_output; i++) {
outputs[i].want_float = 0;
}
ret = rknn_run(feature_ctx, nullptr);
if (ret < 0) {
LOGE("feature rknn_run fail! ret=%d\n", ret);
}
ret = rknn_outputs_get(feature_ctx, feature_io_num.n_output, outputs, nullptr);
if (ret < 0) {
LOGE("feature rknn_outputs_get fail! ret=%d\n", ret);
}
int8_t *output = (int8_t *) outputs[0].buf;
for (int i = 0; i < 512; i++) //sisyphus
{
dbox.feature[i] = output[i];
}
rknn_outputs_release(feature_ctx, feature_io_num.n_output, outputs);
// auto reidTime = GetElapsedTime(t);
// LOGD("Feature reidTime costs %f ms", reidTime);
}
return true;
}
- 目标跟踪
DETECTIONS detections;
for (int i = 0; i < detect_result_group.count; ++i) {
if (detect_result_group.results[i].prop > mat_max_prob) {
mat_max_prob = detect_result_group.results[i].prop;
}
int left = detect_result_group.results[i].box.left;
int right = detect_result_group.results[i].box.right;
int top = detect_result_group.results[i].box.top;
int bottom = detect_result_group.results[i].box.bottom;
int width = right - left;
int height = bottom - top;
//人在画面中
if (top > 60 && bottom < img_height - 60 && left > 120 &&
right < img_width - 120 && detect_result_group.results[i].prop > 0.5) {
is_exist_best_mat = true;
}
//人在画面中
// if (detect_result_group.results[i].prop > 0.5) {
// is_exist_best_mat = true;
// }
//放入特征框
FeatureTensor::getInstance()->get_detections(
DETECTBOX(left, top, width, height),
detect_result_group.results[i].prop,
detections);
}
detect_result_group.count = 0;
//获取特征值
bool flag = FeatureTensor::getInstance()->getRectsFeature(rgb_copy, detections);
//LOGE("FeatureTensor getRectsFeature flag=%d\n", flag);
//目标跟踪
if (flag) {
tracker.predict();
tracker.update(detections);
for (Track &track: tracker.tracks) {
if (!track.is_confirmed() || track.time_since_update > 1)
continue;
detect_result_group.results[detect_result_group.count].trackId = track.track_id;
detect_result_group.results[detect_result_group.count].box.left = track.to_tlwh()[0];
detect_result_group.results[detect_result_group.count].box.top = track.to_tlwh()[1];
detect_result_group.results[detect_result_group.count].box.right =
track.to_tlwh()[0] + track.to_tlwh()[2];
detect_result_group.results[detect_result_group.count].box.bottom =
track.to_tlwh()[1] + track.to_tlwh()[3];
detect_result_group.count++;
}
}
- 计数
private var objectFlow = ObjectFlow(430, 470)
为合理计数,设置了两根线,根据需求,自行设置。
public synchronized int[] cameraCount(ArrayList<InferenceResult.Recognition> recognitions) {
//防止数据集过大
if (tops.size() > MAX_CACHE) {
tops.clear();
}
if (bottoms.size() > MAX_CACHE) {
bottoms.clear();
}
int[] flows = new int[2];
if (recognitions.size() == 0) {
current_life_time++;
//30帧之类,就判断丢失
if (current_life_time == MAX_LIFE_TIME) {
current_life_time = 0;
tops.clear();
bottoms.clear();
}
} else {
for (int i = 0; i < recognitions.size(); i++) {
String trackId = String.valueOf(recognitions.get(i).getTrackId());
if (trackId.equals("0")) {
continue;
}
if (recognitions.get(i).getLocation().bottom < topThresh) {
if (!tops.contains(trackId)) {
tops.add(trackId);
}
else if (bottoms.contains(trackId)) {
flows[0]++;
bottoms.remove(trackId);
}
} else if (recognitions.get(i).getLocation().bottom > bottomThresh) {
if (tops.contains(trackId)) {
flows[1]++;
tops.remove(trackId);
}
else if (!bottoms.contains(trackId)) {
bottoms.add(trackId);
}
}
}
}
return flows;
}
问题集
- 上面有人问到,花屏问题,只要按照RKNN提供的转换的Demo步骤去做,不会有这种,而我这边是因为Demo1去模型转换,而用Demo2去测试,Demo1模型转换多了一个sigmoid的处理,网络结构也会变化,当然就不能直接用Demo2的后处理方式去做。
- 如果要做俯拍的角度(垂直90度),这个模型效果是不好的,并且还要训练运动特征模型。所以不要垂直90度俯拍。
总结
试一试,不就知道了。