本帖最后由 donatello1996 于 2022-2-22 12:25 编辑
飞凌文档中有对OpenCV-DNN例程的使用介绍,看着运行效果,我对此产生了浓厚的兴趣,想要自己尝试编译运行一回,直接就去官网下载压缩包到板子上进行编译安装。下载地址:
- https://codeload.github.com/opencv/opencv/zip/refs/tags/4.5.5
复制代码
下载完毕,解压:
可以看到目录下有cmake,也就是这个OpenCV-4.5.5源码是要用到cmake工具进行编译的,也就是要安装如下的软件:
- apt install make cmake libgtk2.0-dev pkg-config
复制代码
OpenCV-4.5.5官方的cmake编译选项非常繁多复杂,除了本体外,还有CUDA/CNDNN/CUBLAS/python3支持/V4L/FFMPEG/AVX/IPP/TBB/OPENGL/OPENCL等模块可选,官方默认的cmake选项是这个,内容非常非常多,有三四行那么多:
- cmake -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local -D INSTALL_PYTHON_EXAMPLES=ON -D INSTALL_C_EXAMPLES=OFF -D OPENCV_ENABLE_NONFREE=ON -D WITH_CUDA=ON -D WITH_CUDNN=ON -D OPENCV_DNN_CUDA=ON -DBUILD_opencv_cudacodec=OFF -D ENABLE_FAST_MATH=1 -D CUDA_FAST_MATH=1 -D CUDA_ARCH_BIN=7.5 -D WITH_CUBLAS=1 -D HAVE_opencv_python3=ON -D PYTHON_EXECUTABLE=~/anaconda3/envs/face/bin/python -D BUILD_EXAMPLES=OFF -D WITH_EIGEN=ON -D WITH_V4L=ON -D WITH_NVCUVID=1 -D WITH_FFMPEG=ON -D ENABLE_AVX=ON -D WITH_IPP=ON -D WITH_TBB=ON -D WITH_OPENGL=ON -D WITH_OPENCL=ON ..
复制代码
而我经过实测,很多模块在IMX8P 开发板上根本不支持(比如没有CUDA驱动支持),因此精简成如下编译选项,这个编译选项不会编译太多额外内容的同时又有DNN模块的支持:
- cmake -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local -D WITH_CUDNN=ON -D WITH_FFMPEG=ON -D WITH_OPENCL=ON ..
复制代码
cmake的config配置完成之后就是使用make进行正常的编译流程:
编译成功后会在/usr/local目录下生成第三方程序可供使用的include头文件和lib动态链接库:
将这些库文件复制到/usr目录下的相应位置,即/usr/include和/usr/lib后,就可以在第三方程序下直接引用了。使用OpenCV来检测摄像头输入图像并显示的Demo非常简单:
- void * Thread_Opencv(void *arg)
- {
- // IplImage* img = cvLoadImage(MJPEG_FILE_NAME);
- // cvNamedWindow("image" , 1);
- // cvResizeWindow("image" , IMAGEWIDTH/4 , IMAGEHEIGHT/4);
- // cvShowImage("image" , img);
- IplImage* pFrame = NULL;
- CvCapture* pCapture = cvCreateCameraCapture(0);
- cvNamedWindow("video" , 1);
- cvResizeWindow("video" , IMAGEWIDTH/4 , IMAGEHEIGHT/4);
-
- while(1)
- {
- pFrame = cvQueryFrame(pCapture);
- if(!pFrame)break;
- cvShowImage("video" , pFrame);
- cvWaitKey(1);
- }
- cvReleaseCapture(&pCapture);
- cvDestroyWindow("video");
-
- }
复制代码
实际上效果不如直接用V4L2库转为MJPEG的效果好,OpenCV有意思的应用是图像处理和人工智能识别,这里我参考了飞凌文档提供的,基于YOLOV3模型的OpenCV-DNN摄像头图像实时识别物品的Demo,从网上下载coco.names(物品类名称文件),yolov3.cfg(YOLOV3配置文件),yolov3.weights(YOLOV3模型文件)这三个文件:
其中yolov3.weights是通过人工智能训练算法训练得出的模型,一批次训练完毕可以在多个机器上重复使用,泛用性非常高,yolov3.cfg则是配置一些人工智能识别物体相关的参数,如置信度,最大抑制阈值,非最大抑制阈值等等,如果不是专业搞人工智能的,这部分的知识可以先略过,先看下识别功能是怎么实现的。
使用OpenCV和DNN的库可以用命名空间的方式进行加载:
- using namespace std;
- using namespace cv;
- using namespace cv::dnn;
复制代码
然后是加载物品类名文件,config文件和模型文件到DNN库的神经网络类对象中:
- //将类名存进容器
- string classesFile = "./model/coco.names";
- ifstream ifs(classesFile.c_str());
- string line;
- while(getline(ifs,line))classes.push_back(line);
- //取得模型的配置和权重文件
- String modelConfiguration = "./model/yolov3.cfg";
- String modelWeights = "./model/yolov3.weights";
- //加载网络
- Net net = readNetFromDarknet(modelConfiguration,modelWeights);
- net.setPreferableBackend(DNN_BACKEND_OPENCV);
- net.setPreferableBackend(DNN_TARGET_CPU);
- // net.setPreferableBackend(DNN_BACKEND_CUDA);
- // net.setPreferableTarget(DNN_TARGET_CUDA);
复制代码
实际上,OpenCV-DNN库并没有用到IMX8P的NPU做运算,仅用CPU做运算,效果非常差。
移除置信度边界框并画上物品识别框,填充物品类名实现的函数:
- //移除低置信度边界框
- void postprocess(Mat& frame,const vector& outs)
- {
- vector classIds;//储存识别类的索引
- vector confidences;//储存置信度
- vector boxes;//储存边框
- for(size_t i = 0; i < outs.size(); i++)
- {
- //从网络输出中扫描所有边界框
- //保留高置信度选框
- //目标数据data:x,y,w,h为百分比,x,y为目标中心点坐标
- float* data = (float*)outs[i].data;
- for(int j=0;j
- Mat scores = outs[i].row(j).colRange(5,outs[i].cols);
- Point classIdPoint;
- double confidence;//置信度
- //取得最大分数值与索引
- minMaxLoc(scores,0,&confidence,0,&classIdPoint);
- if(confidence>confThreshold)
- {
- int centerX = (int)(data[0]*frame.cols);
- int centerY = (int)(data[1]*frame.rows);
- int width = (int)(data[2]*frame.cols);
- int height = (int)(data[3]*frame.rows);
- int left = centerX-width/2;
- int top = centerY-height/2;
- classIds.push_back(classIdPoint.x);
- confidences.push_back((float)confidence);
- boxes.push_back(Rect(left, top, width, height));
- }
- }
- }
- //低置信度
- vector indices;//保存没有重叠边框的索引
- //该函数用于抑制重叠边框
- NMSBoxes(boxes,confidences,confThreshold,nmsThreshold,indices);
- for(size_t i = 0 ; i
- {
- int idx = indices[i];
- Rect box = boxes[idx];
- drawPred(classIds[idx],confidences[idx],box.x,box.y,
- box.x+box.width,box.y+box.height,frame);
- }
- }
- //绘制预测边界框
- void drawPred(int classId,float conf,int left,int top,int right,int bottom,Mat& frame)
- {
- //绘制边界框
- rectangle(frame,Point(left,top),Point(right,bottom),Scalar(255,178,50),3);
- string label = format("%.2f",conf);
- if(!classes.empty())
- {
- CV_Assert(classId < (int)classes.size());
- label = classes[classId]+":"+label;//边框上的类别标签与置信度
- }
- //绘制边界框上的标签
- int baseLine;
- Size labelSize = getTextSize(label,FONT_HERSHEY_SIMPLEX,0.5,1,&baseLine);
- top = max(top,labelSize.height);
-
-
- rectangle(frame,Point(left,top-round(1.5*labelSize.height)),Point(left+round(1.5*labelSize.width),top+baseLine),Scalar(255,255,255),FILLED);
- putText(frame, label,Point(left, top), FONT_HERSHEY_SIMPLEX, 0.75,Scalar(0, 0, 0), 1);
- }
- //从输出层得到名字
- vector getOutputNames(const Net& net)
- {
- static vector names;
- if(names.empty()){
- //取得输出层指标
- vector outLayers = net.getUnconnectedOutLayers();
- vector layersNames = net.getLayerNames();
- //取得输出层名字
- names.resize(outLayers.size());
- for(size_t i =0;i
- names[i] = layersNames[outLayers[i]-1];
- }
- }
- return names;
- }
- 与上面的检测摄像头内容并输出的代码语句结合:
- //处理每帧
- int frame_cnt = 0;
- while(waitKey(1) < 0)
- {
- cap >> frame;
- if(frame.empty())
- {
- break;
- }
- blobFromImage(frame,blob,1/255.0,Size(inpWidth,inpHeight));
- //在dnn中从磁盘加载图片
- net.setInput(blob);
- //设置输入网络
- vector outs;//设置输出层,储存识别结果
- net.forward(outs,getOutputNames(net));
- postprocess(frame,outs);
- //移除低置信度边界框
- vector layersTimes;
- double freq = getTickFrequency()/1000;
- double t=net.getPerfProfile(layersTimes)/freq;
- string label = format("Infercence time for a frame:%.2f ms",t);
- putText(frame,label,Point(0,15),FONT_HERSHEY_SIMPLEX,0.5,Scalar(0,255,255));
- //绘制识别框
- Mat detecteFrame;
- frame.convertTo(detecteFrame,CV_8U);
- imshow(kWinName,frame);
- cout << "Frame: " << frame_cnt++ << ", time: " << t << "ms" << endl;
- imwrite("output/frame_%d.jpg",frame);
- imwrite("output/frame_%d1.jpg",detecteFrame);
- waitKey(2);
- }
- cap.release();
- return 0;
}
复制代码
编译时Makefile添加相关的OpenCV库:
- CFLAGS += -ljpeg -lpthread -ludev -lstdc++ -lsctp
- #PKGFLAGS += `pkg-config opencv --cflags --libs opencv`
- LDFLAGS += -L./usr/local/lib/ -lopencv_videoio -lopencv_dnn -lopencv_imgcodecs
- LDFLAGS += -lopencv_imgproc -lopencv_gapi -lopencv_highgui -lopencv_core -ldl -rdynamic
复制代码
运行效果:
识别速度为十几秒一帧,效果不尽人意,因为没有用到NPU模块做运算。
0
|
|
|
|