因为项目需要,我需要用C++调用YOLOv3来进行物体检测,本文记录了我尝试的几种调用方法,可能都有些旁门左道的感觉,大佬们不要见笑哈。
首先按照下面步骤把tensorflow版本的YOLOv3跑起来
(1)下载项目代码
git clone https://github.com/qqwweee/keras-yolo3.git
(2)下载完成后进到项目目录里:
cd keras-yolo3
(3)YOLO官网下载weights文件或者执行:
wget https://pjreddie.com/media/files/yolov3.weights
(4)转换YOLO的weights文件格式为Keras的格式:
python convert.py yolov3.cfg yolov3.weights model_data/yolo.h5
(5) 缺什么装什么,其中注意:ImportError: No module named PIL 错误 的解决方法:
pip install Pillow
(6)进行测试
测试图片:
python yolo_video.py --image --input ''
测试视频:
python yolo_video.py --input videos/traffic.mp4 --output videos/traffic_p.mp4
下边的命令不保存视频:
python yolo_video.py --input videos/traffic.mp4
启动摄像头
python yolo_video.py --input /dev/video0
前面都是成功的,然后我的思路是先用python写一个调用上述YOLOv3的接口,然后用通过C++调用Python函数的方式滴调用这个接口,具体代码就不贴了,实现在 我的GIthub 里面,反正是不好使的,会遇到如下的问题:
‘’’ File “/home/leo/anaconda2/envs/yolo/lib/python3.5/threading.py” assert tlock.locked() ‘’’
感觉应该是c++调用anaconda里面的python3.5或者tensorflow的问题。因为除了YOLOv3这个框架,还有那么多框架是基于tensorflow实现的,之前实现过是通过ROS节点实现的,不过直接调用这条路是肯定也是走得通的。
darknet是YOLO的作者基于C写的一个深度学习框架(牛逼!),通过python调用C编译生成的动态库(.so文件),我的思路是还是通过C++调用python接口,代码同样在 我的GIthub 里面,然后惨痛经历如下:
(1)首先我尝试了用c++给python传mat数据 失败!因为darknet压根就没有提供mat的数据接口,好坑啊,为什么!
(2)然后我尝试了用c++给python传一个float的指针,因为image的data数据就是float 失败!python的拓展接口里面没有float*,没法直接传,因此得分装成结构体再强转,太麻烦,放弃吧
(2)最后我尝试了修改darknet的接口,希望提供一个mat_to_image的接口,但是又遇到了c调用c++接口的namespace问题,刚刚好我的电脑装的有事3.4.1版本的opencv,这一版opencv里面提供了c的接口,但是却不能用c调用,3.4.0的好像就可以,哇,自己被自己坑到了
后来我幡然醒悟,.so文件不是可以直接通过C++调用吗,为啥我要绕python这个弯呢?于是就有了最后一种成功的方法
这个方法我从头开始讲,我的电脑的GPU是750Ti的(比较渣),下面的配置我都是按照我的电脑配置的,首先你要装好cuda以及opencv,我装的是cuda8.0和opencv3.4.1,接下来就可以按照下面步骤进行编译了:
(1)首先下载YOLOv3
git clone https://github.com/pjreddie/darknet
(2)下载权重
wget https://pjreddie.com/media/files/yolov3.weights
(3)打开yolo.clf文件,按照如下修改对应代码,修改了batch、subdivisions、width、height(width,height越大精度会越高,但计算量也会越大,这个取决于你的GPU)
[net]
# Testing
batch=1
subdivisions=1
# Training
#batch=64
#subdivisions=16
width=416
height=416
(4)打开makefile文件找到对应代码进行如下修改
GPU=1
CUDNN=1
OPENCV=1
OPENMP=0
DEBUG=0
...
ARCH= -gencode arch=compute_50,code=sm_50 \
...
ifeq ($(GPU), 1)
COMMON+= -DGPU -I/usr/local/cuda-8.0/include/
CFLAGS+= -DGPU
LDFLAGS+= -L/usr/local/cuda-8.0/lib64 -lcuda -lcudart -lcublas -lcurand
endif
...
NVCC=/usr/local/cuda-8.0/bin/nvcc
这里由于两点要注意
1)下面这个配置是根据你的GPU的算力确定的,算力越高对应数字越大,具体的GPU的算力可以再英伟达官网查到的
ARCH= -gencode arch=compute_50,code=sm_50 \
2)如果你没有把opencv安装在默认路径可能会遇到找不到opencv各种文件的问题,例如我之前只装了ROS Kinetic,我希望用ROS Kinetic自带的opencv编译文件,然后就倒腾了下makefile的写法,进行如下修改链接到opencv即可
ifeq ($(OPENCV), 1)
COMMON+= -DOPENCV -I/opt/ros/kinetic/include/opencv-3.3.1-dev
CFLAGS+= -DOPENCV -I/opt/ros/kinetic/include/opencv-3.3.1-dev
LDFLAGS+= -L/opt/ros/kinetic/lib/x86_64-linux-gnu -lopencv_core3 -lopencv_highgui3 -lopencv_videoio3 -lopencv_imgcodecs3
COMMON+= -I/opt/ros/kinetic/include/opencv-3.3.1-dev
endif
其实思路和cmakelist是差不多的
COMMON+= 后面加的是头文件
LDFLAGS+= 后面加的lib库, -L指的路径, -l指的lib文件, 然后libopencv_core3.so链接进来应该改成-lopencv_core3,就这样
(5)建立你的工程,把 libdarnet.so文件,darket.h,yolov3.cfg,coco.names,coco.data放到你的工程,然后写个类把它调用起来就好了,下面一部分代码是我从工程里摘抄出来的,能体现如何是调用接口的,但是并不能直接运行起来哈,需要进行一部分修改
Detecting.cpp
#include "Detecting.h"
namespace YOLO
{
Detecting::Detecting()
{
string ConfigPath = "/home/leo/Desktop/sematic_slam_project/src/sematic_slam/YOLO_V3/config/yolov3.cfg";
string WeightsPath = "/home/leo/Desktop/Data/yolov3.weights";
string MetaDataPath = "/home/leo/Desktop/sematic_slam_project/src/sematic_slam/YOLO_V3/config/coco.data";
mpNetwork = load_network((char *) ConfigPath.data(), (char *) WeightsPath.data(), 0);
mData = get_metadata((char *) MetaDataPath.data());
mpTransMethod = new TransMethod;
}
void Detecting::Detect(cv::Mat Frame, vector& vDetectResults)
{
vDetectResults.clear();
image Image = mpTransMethod->MattoImage(Frame);//讲Mat数据转成Image类型
//下面的检测过程是仿照python接口写的,还没有太弄明白是怎么回事,具体可能需要花时间看paper了,先把框架搭起来吧
int *pCount = new int(0);
network_predict_image(mpNetwork, Image);
detection *pDetection = get_network_boxes(mpNetwork, Image.w, Image.h, 0.5, 0.5, nullptr, 0,
pCount);//第一步:get_network_boxes
do_nms_obj(pDetection, *pCount, mData.classes, 0.45);//第二步:do_nms_obj
//获取检测结果
for (size_t j = 0; j < *pCount; j++)
{
for (size_t i = 0; i < mData.classes; i++)
{
if (pDetection[j].prob[i] > 0)
{
DetectResult Result;
Result.mName = mData.names[i];
Result.mConfidence = pDetection[j].prob[i];
Result.mTop = (pDetection[j].bbox.y - pDetection[j].bbox.h / 2);
Result.mBottom = (pDetection[j].bbox.y + pDetection[j].bbox.h / 2);
Result.mLeft = (pDetection[j].bbox.x - pDetection[j].bbox.w / 2);
Result.mRight = (pDetection[j].bbox.x + pDetection[j].bbox.w / 2);
vDetectResults.push_back(Result);
}
}
}
}
void Detecting::DrawResult( cv::Mat &Image, vector Result)
{
for (vector::iterator it = Result.begin(); it != Result.end(); it++)
{
cv::Point2f PointA(it->mLeft, it->mTop);
cv::Point2f PointB(it->mRight, it->mBottom);
cv::rectangle(Image, PointA, PointB, cv::Scalar(5, 100, 255), 5);
}
}
}
Detecting.h
//
// Created by leo on 18-11-13.
//
#ifndef PROJECT_DETECTING_H
#define PROJECT_DETECTING_H
#include
#include
#include
#include
#include
#include
#include "DetectResult.h"
#include "ORB_SLAM2/include/Tracking.h"
using namespace std;
namespace YOLO
{
class TransMethod;
class Tracking;
class Detecting
{
public:
Detecting();
void Detect(cv::Mat Frame, vector& vDetectResults);
void DrawResult( cv::Mat &Image, vector Result);
private:
network *mpNetwork;
metadata mData;
TransMethod *mpTransMethod;
};
class TransMethod
{
public:
image MattoImage(cv::Mat m)
{
IplImage ipl = m;
image im = IpltoImage(&ipl);
rgbgr_image(im);
return im;
}
private:
image IpltoImage(IplImage *src)
{
int h = src->height;
int w = src->width;
int c = src->nChannels;
image im = make_image(w, h, c);
unsigned char *data = (unsigned char *) src->imageData;
int step = src->widthStep;
int i, j, k;
for (i = 0; i < h; ++i)
{
for (k = 0; k < c; ++k)
{
for (j = 0; j < w; ++j)
{
im.data[k * w * h + i * w + j] = data[i * step + j * c + k] / 255.;
}
}
}
return im;
}
};
}
#endif //PROJECT_DETECTING_H
DetectResult.h
//
// Created by leo on 18-11-20.
//
#ifndef PROJECT_DETECTRESULT_H
#define PROJECT_DETECTRESULT_H
#include
using namespace std;
//把这个类单独放一个h文件是因为Frame类的编译链接问题
class DetectResult
{
public:
string mName;
float mConfidence;
float mTop;
float mBottom;
float mLeft;
float mRight;
bool mbGoodFlag = false;//不是好的检测结果
};
#endif //PROJECT_DETECTRESULT_H
这部分代码其实是我做语义SLAM中间调用YOLOv3的一部分,参考代码在 我的Github中,通过上面的接口就能调用起来YOLOv3了,这种方法主要是因为YOLOv3是基于c实现的,其他的深度学习框架的C++调用应该还是通过第一种方法实现。