a8799824190e9fcf6cd955fa66700f05
【飞凌嵌入式OK3568-C 开发板试用体验】OpenCV_SSD&RKNN应用代码移植&Mjpeg-Streamer视频流显示效果优化
Mjpeg-Streamer视频流显示在之前的飞凌OKMX8-MP开发板中已经有了初步应用,大概思路使用三个线程实现,一个线程用于MJPEG流采集,一个线程用于TCP发送,一个线程用于TCP接收,其中MJPEG流采集线程循环不间断采集MJPEG流数据到内存,TCP接收线程用于接收客户端HTTP GET请求(或进阶POST请求),TCP发送线程用于发送MJPEG报头+MJPEG流+相关描述头,加入一个pthread_mutex_t pmt和pthread_cond_t pct变量用于内存内容的同步,防止HTTP画面出现撕裂,实现思路如下:
- void * Thread_V4l2_Grab_Mjpeg(void *arg)
- {
- pic_data pic_temp;
- while(1)
- {
- int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- if(ioctl(fd_video , VIDIOC_STREAMON , &type) < 0)
- {
- qDebug("Unable to start capture.n");
- break;
- }
- ...
- if(ioctl(fd_video , VIDIOC_DQBUF, &buff) < 0)
- {
- qDebug("camera VIDIOC_DQBUF Failed.");
- usleep(1000*1000);
- break;
- }
- pthread_mutex_lock(&pmt);
- memcpy(pic_tmpbuffer , pic.tmpbuffer , buff.bytesused);
- pic.tmpbytesused = buff.bytesused;
- pic_tmpbytesused = pic.tmpbytesused;
- ...
- pthread_cond_broadcast(&pct);
- pthread_mutex_unlock(&pmt);
- ...
- if(ioctl(fd_video , VIDIOC_QBUF, &buff) < 0)
- {
- qDebug("camera VIDIOC_QBUF Failed.n");
- usleep(1000*1000);
- break;
- }
- }
- }
复制代码
采集过程使用ioctl+循环队列进行,内存拷贝过程中使用pthread_mutex_t pmt线程锁锁住内存内容防止被外部线程篡改。HTTP发送线程如下:
- void * Thread_TCP_Web_Send_Only_MJPEG(void *arg)
- {
- while(1)
- {
- usleep(1);
- if(flag_keep_alive && flag_post_once)
- {
- flag_post_once = 0;
- HTTP_Send_Mjpeg_Stream(fd_socket_conn);
- }
- }
- }
复制代码
QT程序中需要加入一些类似usleep的语句以提高线程执行优先级,若缺失此语句,则CPU分配给HTTP发送线程的时间片资源太少而导致功能无法实现。这里需要特别说明的是,HTTP发送线程获取图片数据可以用两种方式,内存拷贝或HTTP发送线程指针直接指向MJPEG内存,我这里经过测试,使用指针直接指向MJPEG内存也是可以实现功能的,且无bug,稳定运行,因为MJPEG图片数据原始内存的地址是保存于pic.tmpbuffer结构体成员中,而pic_tmpbuffer已经是一个中间存储的数组的指针,也就是已经实现了一次数据拷贝:
- HTTP_Send_Mjpeg_Stream()
- {
- ...
- //timestamp = pglobal->in[input_number].timestamp;
- //timestamp = 100;
- //memcpy(frame , pic_tmpbuffer , frame_size);
- //frame = pic_tmpbuffer;
-
- qDebug("got frame (size: %d)n", frame_size);
- pthread_mutex_unlock(&pmt);
- sprintf(buffer, "Content-Type: image/jpegrn"
- "Content-Length: %drn"
- "X-Timestamp: %d.%06drn"
- "rn", frame_size, (int)timestamp.tv_sec, (int)timestamp.tv_usec);
- qDebug("sending intemdiate header & frame & boundary");
- if(write(fd , buffer , strlen(buffer)) < 0)
- {
- qDebug("write buffer1 break.n");
- break;
- }
- if(write(fd , pic_tmpbuffer , frame_size) < 0)
- {
- qDebug("write frame break.n");
- break;
- }
- ...
- }
复制代码
实现效果非常流畅。
然后是有关Opencv_SSD例程的移植,这个难度不高,但是比较费心思,因为飞凌厂家的例程需要经过修改才可移植为摄像头数据处理:
- static unsigned char *load_model(const char *filename, int *model_size)
- {
- FILE *fp = fopen(filename, "rb");
- if(fp == nullptr) {
- qDebug("fopen %s fail!n", filename);
- return nullptr;
- }
- fseek(fp, 0, SEEK_END);
- int model_len = ftell(fp);
- unsigned char *model = (unsigned char*)malloc(model_len);
- fseek(fp, 0, SEEK_SET);
- if(model_len != fread(model, 1, model_len, fp)) {
- qDebug("fread %s fail!n", filename);
- free(model);
- return nullptr;
- }
- *model_size = model_len;
- if(fp) {
- fclose(fp);
- }
- return model;
- }
复制代码
- int RK3568_Load_Model_RKNN_Init(rknn_context * ctx , rknn_input_output_num * io_num)
- {
- int ret = 0;
- const char *model_path = RKNN_FILE_NAME;
- int model_len = 0;
- unsigned char *model = nullptr;
- model = load_model(model_path, &model_len);
- ret = rknn_init(ctx, model, model_len, 0, nullptr);
- if(ret < 0)
- {
- qDebug("rknn_init fail! ret=%dn", ret);
- return 1;
- }
- ret = rknn_query(*ctx, RKNN_QUERY_IN_OUT_NUM, io_num, sizeof(*io_num));
- if (ret != RKNN_SUCC)
- {
- qDebug("rknn_query fail! ret=%dn", ret);
- return 2;
- }
- qDebug("model input num: %d, output num: %dn", io_num->n_input, io_num->n_output);
- qDebug("input tensors:n");
- rknn_tensor_attr input_attrs[io_num->n_input];
- memset(input_attrs, 0, sizeof(input_attrs));
- for (int i = 0; i < io_num->n_input; i++)
- {
- input_attrs[i].index = i;
- ret = rknn_query(*ctx, RKNN_QUERY_INPUT_ATTR, &(input_attrs[i]), sizeof(rknn_tensor_attr));
- if (ret != RKNN_SUCC)
- {
- qDebug("rknn_query fail! ret=%dn", ret);
- return 3;
- }
- printRKNNTensor(&(input_attrs[i]));
- }
- qDebug("output tensors:n");
- rknn_tensor_attr output_attrs[io_num->n_output];
- memset(output_attrs, 0, sizeof(output_attrs));
- for (int i = 0; i < io_num->n_output; i++)
- {
- output_attrs[i].index = i;
- ret = rknn_query(*ctx, RKNN_QUERY_OUTPUT_ATTR, &(output_attrs[i]), sizeof(rknn_tensor_attr));
- if (ret != RKNN_SUCC)
- {
- qDebug("rknn_query fail! ret=%dn", ret);
- return 4;
- }
- printRKNNTensor(&(output_attrs[i]));
- }
- }
复制代码
- void * Thread_Opencv_SSD(void *arg)
- {
- //const int img_channels = 3;
- int ret;
- const int img_width = 300;
- const int img_height = 300;
- rknn_context ctx;
- rknn_input_output_num io_num;
- RK3568_Load_Model_RKNN_Init(&ctx , &io_num);
- while(1)
- {
- pthread_mutex_lock(&pmt);
- pthread_cond_wait(&pct , &pmt);
- cv::Mat orig_img = cv::imread(MJPEG_FILE_NAME , 1);
- pthread_mutex_unlock(&pmt);
- cv::Mat img = orig_img.clone();
- if(!orig_img.data)
- {
- qDebug("cv::imread %s fail!n", MJPEG_FILE_NAME);
- return (void*)5;
- }
- if(orig_img.cols != img_width || orig_img.rows != img_height)
- {
- qDebug("resize %d %d to %d %dn", orig_img.cols, orig_img.rows, img_width, img_height);
- cv::resize(orig_img, img, cv::Size(img_width, img_height), (0, 0), (0, 0), cv::INTER_LINEAR);
- }
- // Set Input Data
- rknn_input inputs[1];
- memset(inputs, 0, sizeof(inputs));
- inputs[0].index = 0;
- inputs[0].type = RKNN_TENSOR_UINT8;
- inputs[0].size = img.cols*img.rows*img.channels();
- inputs[0].fmt = RKNN_TENSOR_NHWC;
- inputs[0].buf = img.data;
- ret = rknn_inputs_set(ctx, io_num.n_input, inputs);
- if(ret < 0)
- {
- qDebug("rknn_input_set fail! ret=%dn", ret);
- return (void*)6;
- }
- // Run
- qDebug("rknn_runn");
- ret = rknn_run(ctx, nullptr);
- if(ret < 0)
- {
- qDebug("rknn_run fail! ret=%dn", ret);
- return (void*)7;
- }
- // Get Output
- rknn_output outputs[2];
- memset(outputs, 0, sizeof(outputs));
- outputs[0].want_float = 1;
- outputs[1].want_float = 1;
- ret = rknn_outputs_get(ctx, io_num.n_output, outputs, nullptr);
- if(ret < 0)
- {
- qDebug("rknn_outputs_get fail! ret=%dn", ret);
- return (void*)8;
- }
- // Post Process
- detect_result_group_t detect_result_group;
- postProcessSSD((float *)(outputs[0].buf), (float *)(outputs[1].buf), orig_img.cols, orig_img.rows, &detect_result_group);
- // Release rknn_outputs
- rknn_outputs_release(ctx, 2, outputs);
- // Draw Objects
- for (int i = 0; i < detect_result_group.count; i++)
- {
- detect_result_t *det_result = &(detect_result_group.results[i]);
- qDebug("%s @ (%d %d %d %d) %fn",
- det_result->name,
- det_result->box.left, det_result->box.top, det_result->box.right, det_result->box.bottom,
- det_result->prop);
- int x1 = det_result->box.left;
- int y1 = det_result->box.top;
- int x2 = det_result->box.right;
- int y2 = det_result->box.bottom;
- rectangle(orig_img, Point(x1, y1), Point(x2, y2), Scalar(255, 0, 0, 255), 3);
- putText(orig_img, det_result->name, Point(x1, y1 - 12), 1, 2, Scalar(0, 255, 0, 255));
- }
- imwrite(OPENCV_SSD_OUTPUT_FILE_NAME , orig_img);
- l_opencv_ssd_extern->setPixmap(QPixmap(OPENCV_SSD_OUTPUT_FILE_NAME));
- }
- if(ctx >= 0)
- {
- rknn_destroy(ctx);
- }
- // if(model)
- // {
- // free(model);
- // }
- return (void*)0;
- }
复制代码
加载模型(model对象),RKNN驱动,初始化RKNN和model对象,均只需要做一次,若重复执行,则会大大占用内存。同时,使用MJPEG原始数据,也需要加上线程锁。值得注意的是,不知道是RK3568的NPU性能存在硬件瓶颈,或是RKNN驱动代码优化不足,整体运行效果不尽人意,可以进行低实时性要求的物体检测,但是要进行车规级或军工级检测,则完全不合格。后续还需飞凌软件工程师跟进改进RKNN驱动代码。
b2aef09d551dab3ecea260cd4a0d8f6e
0
|
|
|
|