2018-08-21 22:37:58 qq_27158179 阅读数 19874
  • YOLOv3目标检测实战:训练自己的数据集

    YOLOv3是一种基于深度学习的端到端实时目标检测方法,以速度快见长。本课程将手把手地教大家使用labelImg标注和使用YOLOv3训练自己的数据集。课程分为三个小项目:足球目标检测(单目标检测)、梅西目标检测(单目标检测)、足球和梅西同时目标检测(两目标检测)。 本课程的YOLOv3使用Darknet,在Ubuntu系统上做项目演示。包括:安装Darknet、给自己的数据集打标签、整理自己的数据集、修改配置文件、训练自己的数据集、测试训练出的网络模型、性能统计(mAP计算和画出PR曲线)和先验框聚类。 Darknet是使用C语言实现的轻型开源深度学习框架,依赖少,可移植性好,值得深入探究。 除本课程《YOLOv3目标检测实战:训练自己的数据集》外,本人推出了有关YOLOv3目标检测的系列课程,请持续关注该系列的其它课程视频,包括: 《YOLOv3目标检测实战:交通标志识别》 《YOLOv3目标检测:原理与源码解析》 《YOLOv3目标检测:网络模型改进方法》 敬请关注并选择学习!

    1127 人正在学习 去看看 白勇

本文翻译自Deep Learning based Object Detection using YOLOv3 with OpenCV ( Python / C++ )

基于OpenCV和YOLOv3深度学习的目标检测

 

本文,我们学习如何在OpenCV上使用目前较为先进的目标检测技术YOLOv3。

YOLOv3是当前流行的目标检测算法YOLO(You Only Look Once)的最新变种算法。所发行的模型能识别图片和视频中的80种物体,而且更重要的是它实时性强,而且准确度接近Single Shot MultiBox(SSD)。

从OpenCV 3.4.2开始,我们可以很容易的在OpenCV应用中使用YOLOv3模型(即OpemCV-3.4.2开始支持YOLOv3这网络框架)。

YOLO是什么原理?

我们可以把目标检测看成是目标定位和目标识别的结合。

在传统的计算机视觉方法中,采用滑动窗口查找不同区域和大小的目标。因为这是消耗量较大的算法,通常假定目标的纵横比是固定的。

早期的基于深度学习的目标检测算法,如R-CNN和快速R-CNN,采用选择型搜索(Selective Search)来缩小必须测试的边界框的数量(本文的边界框指的是,在预测到疑似所识别到的目标后,在图片上把对象框出的一个矩形)。

另外一种称为Overfeat的方法,通过卷积地计算滑动窗口,以多个尺度扫描了图像。

然后有人提出了快速R-CNN算法,使用Region Proposal Network(RPN)区别将要测试的边界框。通过巧妙的设计,用于目标识别的特征点,也被RPN用于提出潜在的边界框,因此节省了大量的计算。

然而,YOLO使用了完全不同的方法解决目标检测问题。它将图像进行神经网络的一次性正向处理。SSD是另外一种将图像进行神经网络一次性正向处理的方法,但是YOLOv3比SSD实现了更高的精度,同时又较快的运算速度。YOLOv3在M40,TitanX和1080Ti这类GPU上实时效果更好。

让我们看看YOLO如何在一张图片中检测目标。

首先,它把原图按比例平均分解成一张有13x13网格的图片。这169个单元会根据原图的大小而改变。对于一张416x416像素的图片,每个图片单元的大小是32x32像素。处理图片时,会以图片单元为单位,预测单位中的多个边界框。

 对于每个边界框,这个网络会计算所包含物体的边界框的置信度,同时计算所包含的目标是属于一个特定类别的可能性大小。

非最大抑制(non-maximum suppression)可以消除低置信度的边界框,以及把同时包围着单个物体的多个高置信度的边界框消除到只剩下一个。

YOLOv3的作者,Joseph Redmon和Ali Farhadi,让YOLOv3比前一代YOLOv2更加精确和快速。YOLOv3在处理多个不同尺寸图片的场合中得到了优化。他们还通过加大了网络,并添加快捷链接将其引入剩余网络来改进网络。

为什么选择OpenCV的YOLO   

 这里有三个理由。

  1. 容易整合到现有的OpenCV程序中:如果应用程序已经使用了OpenCV,并想简单地使用YOLOv3,完全不需要担心Darknet源代码的编译和建立。
  2. OpenCV的CPU版本的运算速度比Darknet+OpenMP快9倍:OpenCV的DNN模块,其CPU运行是十分快的。举个例子,当用了OpenMP的Darknet在CPU上处理一张图片消耗2秒,OpenCV的实现只需要0.22秒。具体请看下面的表格。
  3. 支持Python。Darknet是用C语言写的,因此并不支持Python。相反,OpenCV是支持Python的。会有支持Darknet的编程接口。

 

在Darknet和OpenCV上跑YOLOv3的速度测试

下面的表格展示了在Darknet和OpenCV上YOLOv3的性能差距,输入图片的尺寸是416x416。不出所料,GPU版本的Darknet在性能上比其他方式优越。同时,理所当然的Darknet配合OpenMP会好于没有OpenMP的Darknet,因为OpenMP支持多核的CPU。

意外的是,CPU版本的OpenCV在执行DNN的运算速度,是9倍的快过Darknet和OpenML。
      

表1. 分别在Darknet和OpenCV上跑YOLOv3的速度对比
OS Framework CPU/GPU Time(ms)/Frame
Linux 16.04    Darknet    12x Intel Core i7-6850K CPU @ 3.60GHz    9370
Linux 16.04       Darknet + OpenMP  12x Intel Core i7-6850K CPU @ 3.60GHz    1942
Linux 16.04    OpenCV [CPU]       12x Intel Core i7-6850K CPU @ 3.60GHz  220
Linux 16.04    Darknet    NVIDIA GeForce 1080 Ti GPU    23
macOS    DarkNet    2.5 GHz Intel Core i7 CPU    7260
macOS      OpenCV [CPU]     2.5 GHz Intel Core i7 CPU  400


注意:我们在GPU版本的OpenCV上跑DNN时候遇到了困难。本文工作只是测试了Intel的GPU,因此如果没有Intel的GPU,程序会自动切换到CPU上跑相关算法。

 

采用YOLOv3的目标检测,C++/Python两种语言

让我们看看,如何在YOLOv3在OpenCV运行目标检测。

第1步:下载模型。

我们先从命令行中执行脚本getModels.sh开始。
   

sudo chmod a+x getModels.sh
./getModels.sh
//译者添加:
Windows下替代方案:

1、http://gnuwin32.sourceforge.net/packages/wget.htm 安装wget
 cd 到wget安装目录,执行
wget https://pjreddie.com/media/files/yolov3.weights
wget https://github.com/pjreddie/darknet/blob/master/cfg/yolov3.cfg?raw=true -O ./yolov3.cfg
wget https://github.com/pjreddie/darknet/blob/master/data/coco.names?raw=true -O ./coco.names

执行命令后开始下载yolov3.weights文件(包括了提前训练好的网络的权值),和yolov3.cfg文件(包含了网络的配置方式)和coco.names(包括了COCO数据库中使用的80种不同的目标种类名字)。

第2步:初始化参数

YOLOv3算法的预测结果就是边界框。每一个边界框都旁随着一个置信值。第一阶段中,全部低于置信度阀值的都会排除掉。

对剩余的边界框执行非最大抑制算法,以去除重叠的边界框。非最大抑制由一个参数nmsThrehold控制。读者可以尝试改变这个数值,观察输出的边界框的改变。

接下来,设置输入图片的宽度(inpWidth)和高度(inpHeight)。我们设置他们为416,以便对比YOLOv3作者提供的Darknets的C代码。如果想要更快的速度,读者可以把宽度和高度设置为320。如果想要更准确的结果,改变他们到608。

Python代码:

# Initialize the parameters
confThreshold = 0.5  #Confidence threshold
nmsThreshold = 0.4   #Non-maximum suppression threshold
inpWidth = 416       #Width of network's input image
inpHeight = 416      #Height of network's input image

C++代码:

// Initialize the parameters
float confThreshold = 0.5; // Confidence threshold
float nmsThreshold = 0.4;  // Non-maximum suppression threshold
int inpWidth = 416;        // Width of network's input image
int inpHeight = 416;       // Height of network's input image

第3步:读取模型和类别

文件coco.names包含了训练好的模型能识别的所有目标名字。我们读出各个类别的名字。

接着,我们读取了网络,其包含两个部分:

  1. yolov3.weights: 预训练得到的权重。
  2. yolov3.cfg:配置文件

我们把DNN的后端设置为OpenCV,目标设置为CPU。可以通过使cv.dnn.DNN_TARGET_OPENCL置为GPU,尝试设定偏好的运行目标为GPU。但是要记住当前的OpenCV版本只在Intel的GPU上测试,如果没有Intel的GPU则程序会自动设置为CPU。

Python:

# Load names of classes
classesFile = "coco.names";
classes = None
with open(classesFile, 'rt') as f:
    classes = f.read().rstrip('\n').split('\n')
 
# Give the configuration and weight files for the model and load the network using them.
modelConfiguration = "yolov3.cfg";
modelWeights = "yolov3.weights";
 
net = cv.dnn.readNetFromDarknet(modelConfiguration, modelWeights)
net.setPreferableBackend(cv.dnn.DNN_BACKEND_OPENCV)
net.setPreferableTarget(cv.dnn.DNN_TARGET_CPU)

 

C++

// Load names of classes
string classesFile = "coco.names";
ifstream ifs(classesFile.c_str());
string line;
while (getline(ifs, line)) classes.push_back(line);
 
// Give the configuration and weight files for the model
String modelConfiguration = "yolov3.cfg";
String modelWeights = "yolov3.weights";
 
// Load the network
Net net = readNetFromDarknet(modelConfiguration, modelWeights);
net.setPreferableBackend(DNN_BACKEND_OPENCV);
net.setPreferableTarget(DNN_TARGET_CPU);

第4步:读取输入

这一步我们读取图像,视频流或者网络摄像头。另外,我们也使用Videowriter(OpenCV里的一个类)以视频方式保存带有输出边界框的每一帧图片。

Python

outputFile = "yolo_out_py.avi"
if (args.image):
    # Open the image file
    if not os.path.isfile(args.image):
        print("Input image file ", args.image, " doesn't exist")
        sys.exit(1)
    cap = cv.VideoCapture(args.image)
    outputFile = args.image[:-4]+'_yolo_out_py.jpg'
elif (args.video):
    # Open the video file
    if not os.path.isfile(args.video):
        print("Input video file ", args.video, " doesn't exist")
        sys.exit(1)
    cap = cv.VideoCapture(args.video)
    outputFile = args.video[:-4]+'_yolo_out_py.avi'
else:
    # Webcam input
    cap = cv.VideoCapture(0)
 
# Get the video writer initialized to save the output video
if (not args.image):
    vid_writer = cv.VideoWriter(outputFile, cv.VideoWriter_fourcc('M','J','P','G'), 30, (round(cap.get(cv.CAP_PROP_FRAME_WIDTH)),round(cap.get(cv.CAP_PROP_FRAME_HEIGHT))))
    
    


C++

outputFile = "yolo_out_cpp.avi";
if (parser.has("image"))
{
    // Open the image file
    str = parser.get<String>("image");
    ifstream ifile(str);
    if (!ifile) throw("error");
    cap.open(str);
    str.replace(str.end()-4, str.end(), "_yolo_out.jpg");
    outputFile = str;
}
else if (parser.has("video"))
{
    // Open the video file
    str = parser.get<String>("video");
    ifstream ifile(str);
    if (!ifile) throw("error");
    cap.open(str);
    str.replace(str.end()-4, str.end(), "_yolo_out.avi");
    outputFile = str;
}
// Open the webcaom
else cap.open(parser.get<int>("device"));
 
// Get the video writer initialized to save the output video
if (!parser.has("image")) {
   video.open(outputFile, VideoWriter::fourcc('M','J','P','G'), 28, Size(cap.get(CAP_PROP_FRAME_WIDTH),          cap.get(CAP_PROP_FRAME_HEIGHT)));
}

 

第5步:处理每一帧

输入到神经网络的图像需要以一种叫bolb的格式保存。

读取了输入图片或者视频流的一帧图像后,这帧图像需要经过bolbFromImage()函数处理为神经网络的输入类型bolb。在这个过程中,图像像素以一个1/255的比例因子,被缩放到0到1之间。同时,图像在不裁剪的情况下,大小调整到416x416。注意我们没有降低图像平均值,因此传递[0,0,0]到函数的平均值输入,保持swapRB参数到默认值1。

输出的bolb传递到网络,经过网络正向处理,网络输出了所预测到的一个边界框清单。这些边界框通过后处理,滤除了低置信值的。我们随后再详细的说明后处理的步骤。我们在每一帧的左上方打印出了推断时间。伴随着最后的边界框的完成,图像保存到硬盘中,之后可以作为图像输入或者通过Videowriter作为视频流输入。

Python:

while cv.waitKey(1) < 0:
     
    # get frame from the video
    hasFrame, frame = cap.read()
     
    # Stop the program if reached end of video
    if not hasFrame:
        print("Done processing !!!")
        print("Output file is stored as ", outputFile)
        cv.waitKey(3000)
        break
 
    # Create a 4D blob from a frame.
    blob = cv.dnn.blobFromImage(frame, 1/255, (inpWidth, inpHeight), [0,0,0], 1, crop=False)
 
    # Sets the input to the network
    net.setInput(blob)
 
    # Runs the forward pass to get output of the output layers
    outs = net.forward(getOutputsNames(net))
 
    # Remove the bounding boxes with low confidence
    postprocess(frame, outs)
 
    # Put efficiency information. The function getPerfProfile returns the
    # overall time for inference(t) and the timings for each of the layers(in layersTimes)
    t, _ = net.getPerfProfile()
    label = 'Inference time: %.2f ms' % (t * 1000.0 / cv.getTickFrequency())
    cv.putText(frame, label, (0, 15), cv.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255))
 
    # Write the frame with the detection boxes
    if (args.image):
        cv.imwrite(outputFile, frame.astype(np.uint8));
    else:
        vid_writer.write(frame.astype(np.uint8))
        

c++

// Process frames.
while (waitKey(1) < 0)
{
    // get frame from the video
    cap >> frame;
 
    // Stop the program if reached end of video
    if (frame.empty()) {
        cout << "Done processing !!!" << endl;
        cout << "Output file is stored as " << outputFile << endl;
        waitKey(3000);
        break;
    }
    // Create a 4D blob from a frame.
    blobFromImage(frame, blob, 1/255.0, cvSize(inpWidth, inpHeight), Scalar(0,0,0), true, false);
     
    //Sets the input to the network
    net.setInput(blob);
     
    // Runs the forward pass to get output of the output layers
    vector<Mat> outs;
    net.forward(outs, getOutputsNames(net));
     
    // Remove the bounding boxes with low confidence
    postprocess(frame, outs);
     
    // Put efficiency information. The function getPerfProfile returns the
    // overall time for inference(t) and the timings for each of the layers(in layersTimes)
    vector<double> layersTimes;
    double freq = getTickFrequency() / 1000;
    double t = net.getPerfProfile(layersTimes) / freq;
    string label = format("Inference time for a frame : %.2f ms", t);
    putText(frame, label, Point(0, 15), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0, 0, 255));
     
    // Write the frame with the detection boxes
    Mat detectedFrame;
    frame.convertTo(detectedFrame, CV_8U);
    if (parser.has("image")) imwrite(outputFile, detectedFrame);
    else video.write(detectedFrame);
     
}

现在,让我们详细分析一下上面调用的函数。

第5a步:得到输出层的名字

OpenCV的网络类中的前向功能需要结束层,直到它在网络中运行。因为我们需要运行整个网络,所以我们需要识别网络中的最后一层。我们通过使用getUnconnectedOutLayers()获得未连接的输出层的名字,该层基本就是网络的最后层。然后我们运行前向网络,得到输出,如前面的代码片段(net.forward(getOutputsNames(net)))。
python:

# Get the names of the output layers
def getOutputsNames(net):
    # Get the names of all the layers in the network
    layersNames = net.getLayerNames()
    # Get the names of the output layers, i.e. the layers with unconnected outputs
    return [layersNames[i[0] - 1] for i in net.getUnconnectedOutLayers()]
    

c++

// Get the names of the output layers
vector<String> getOutputsNames(const Net& net)
{
    static vector<String> names;
    if (names.empty())
    {
        //Get the indices of the output layers, i.e. the layers with unconnected outputs
        vector<int> outLayers = net.getUnconnectedOutLayers();
         
        //get the names of all the layers in the network
        vector<String> layersNames = net.getLayerNames();
         
        // Get the names of the output layers in names
        names.resize(outLayers.size());
        for (size_t i = 0; i < outLayers.size(); ++i)
        names[i] = layersNames[outLayers[i] - 1];
    }
    return names;
}

第5b步:后处理网络输出

网络输出的每个边界框都分别由一个包含着类别名字和5个元素的向量表示。

头四个元素代表center_x, center_y, width和height。第五个元素表示包含着目标的边界框的置信度。

其余的元素是和每个类别(如目标种类)有关的置信度。边界框分配给最高分数对应的那一种类。

一个边界框的最高分数也叫做它的置信度(confidence)。如果边界框的置信度低于规定的阀值,算法上不再处理这个边界框。

置信度大于或等于置信度阀值的边界框,将进行非最大抑制。这会减少重叠的边界框数目。
Python

# Remove the bounding boxes with low confidence using non-maxima suppression
def postprocess(frame, outs):
    frameHeight = frame.shape[0]
    frameWidth = frame.shape[1]
 
    classIds = []
    confidences = []
    boxes = []
    # Scan through all the bounding boxes output from the network and keep only the
    # ones with high confidence scores. Assign the box's class label as the class with the highest score.
    classIds = []
    confidences = []
    boxes = []
    for out in outs:
        for detection in out:
            scores = detection[5:]
            classId = np.argmax(scores)
            confidence = scores[classId]
            if confidence > confThreshold:
                center_x = int(detection[0] * frameWidth)
                center_y = int(detection[1] * frameHeight)
                width = int(detection[2] * frameWidth)
                height = int(detection[3] * frameHeight)
                left = int(center_x - width / 2)
                top = int(center_y - height / 2)
                classIds.append(classId)
                confidences.append(float(confidence))
                boxes.append([left, top, width, height])
 
    # Perform non maximum suppression to eliminate redundant overlapping boxes with
    # lower confidences.
    indices = cv.dnn.NMSBoxes(boxes, confidences, confThreshold, nmsThreshold)
    for i in indices:
        i = i[0]
        box = boxes[i]
        left = box[0]
        top = box[1]
        width = box[2]
        height = box[3]
        drawPred(classIds[i], confidences[i], left, top, left + width, top + height)

c++

// Remove the bounding boxes with low confidence using non-maxima suppression
void postprocess(Mat& frame, const vector<Mat>& outs)
{
    vector<int> classIds;
    vector<float> confidences;
    vector<Rect> boxes;
     
    for (size_t i = 0; i < outs.size(); ++i)
    {
        // Scan through all the bounding boxes output from the network and keep only the
        // ones with high confidence scores. Assign the box's class label as the class
        // with the highest score for the box.
        float* data = (float*)outs[i].data;
        for (int j = 0; j < outs[i].rows; ++j, data += outs[i].cols)
        {
            Mat scores = outs[i].row(j).colRange(5, outs[i].cols);
            Point classIdPoint;
            double confidence;
            // Get the value and location of the maximum score
            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));
            }
        }
    }
     
    // Perform non maximum suppression to eliminate redundant overlapping boxes with
    // lower confidences
    vector<int> indices;
    NMSBoxes(boxes, confidences, confThreshold, nmsThreshold, indices);
    for (size_t i = 0; i < indices.size(); ++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);
    }
}

非最大抑制由参数nmsThreshold控制。如果nmsThreshold设置太少,比如0.1,我们可能检测不到相同或不同种类的重叠目标。如果设置得太高,比如1,可能出现一个目标有多个边界框包围。所以我们在上面的代码使用了0.4这个中间的值。下面的gif展示了NMS阀值改变时候的效果。

第5c步:画出计算得到的边界框  

 最后,经过非最大抑制后,得到了边界框。我们把边界框在输入帧上画出,并标出种类名和置信值。

Python

# Draw the predicted bounding box
def drawPred(classId, conf, left, top, right, bottom):
    # Draw a bounding box.
    cv.rectangle(frame, (left, top), (right, bottom), (0, 0, 255))
     
    label = '%.2f' % conf
         
    # Get the label for the class name and its confidence
    if classes:
        assert(classId < len(classes))
        label = '%s:%s' % (classes[classId], label)
 
    #Display the label at the top of the bounding box
    labelSize, baseLine = cv.getTextSize(label, cv.FONT_HERSHEY_SIMPLEX, 0.5, 1)
    top = max(top, labelSize[1])
    cv.putText(frame, label, (left, top), cv.FONT_HERSHEY_SIMPLEX, 0.5, (255,255,255))

c++

// Draw the predicted bounding box
void drawPred(int classId, float conf, int left, int top, int right, int bottom, Mat& frame)
{
    //Draw a rectangle displaying the bounding box
    rectangle(frame, Point(left, top), Point(right, bottom), Scalar(0, 0, 255));
     
    //Get the label for the class name and its confidence
    string label = format("%.2f", conf);
    if (!classes.empty())
    {
        CV_Assert(classId < (int)classes.size());
        label = classes[classId] + ":" + label;
    }
     
    //Display the label at the top of the bounding box
    int baseLine;
    Size labelSize = getTextSize(label, FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseLine);
    top = max(top, labelSize.height);
    putText(frame, label, Point(left, top), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(255,255,255));

    


订阅&下载代码
如果你喜欢本文,想下载代码(C++和Python),和在文中的例子图片,请订阅我们的时事通信。你会获得一封免费的计算机视觉指南。在我们的时事通信上,我们共享了C++/Python语言的OpenCV教程和例子,同时还有计算机视觉和机器学习算法和新闻。
    


 

以上就是原文的全部内容。

原文地址:https://www.learnopencv.com/deep-learning-based-object-detection-using-yolov3-with-opencv-python-c/

作者:Sunita Nayak


可参考:YOLOv3 Tech Report获得与本文相关的知识内容。
    
有几句话是机翻协助的。当时也没标记。2018年9月18日进行了一次润色,已经修复部分翻译错误。第一遍快到结束了,按了下退格键+空格键,页面后退了,内容没了,痛心。然后重新润了一遍,没那么好的耐心了。如有错漏请多多包涵。

 

2019-05-01 11:17:09 weixin_44580210 阅读数 1448
  • YOLOv3目标检测实战:训练自己的数据集

    YOLOv3是一种基于深度学习的端到端实时目标检测方法,以速度快见长。本课程将手把手地教大家使用labelImg标注和使用YOLOv3训练自己的数据集。课程分为三个小项目:足球目标检测(单目标检测)、梅西目标检测(单目标检测)、足球和梅西同时目标检测(两目标检测)。 本课程的YOLOv3使用Darknet,在Ubuntu系统上做项目演示。包括:安装Darknet、给自己的数据集打标签、整理自己的数据集、修改配置文件、训练自己的数据集、测试训练出的网络模型、性能统计(mAP计算和画出PR曲线)和先验框聚类。 Darknet是使用C语言实现的轻型开源深度学习框架,依赖少,可移植性好,值得深入探究。 除本课程《YOLOv3目标检测实战:训练自己的数据集》外,本人推出了有关YOLOv3目标检测的系列课程,请持续关注该系列的其它课程视频,包括: 《YOLOv3目标检测实战:交通标志识别》 《YOLOv3目标检测:原理与源码解析》 《YOLOv3目标检测:网络模型改进方法》 敬请关注并选择学习!

    1127 人正在学习 去看看 白勇

深度学习框架YOLOv3的C++调用

因为项目需要,我需要用C++调用YOLOv3来进行物体检测,本文记录了我尝试的几种调用方法,可能都有些旁门左道的感觉,大佬们不要见笑哈。

(1)tensorflow版本的YOLOv3的C++调用(失败)

首先按照下面步骤把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节点实现的,不过直接调用这条路是肯定也是走得通的。


(2)darknet版本的YOLOv3的C++调用一(失败)

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这个弯呢?于是就有了最后一种成功的方法


(3)darknet版本的YOLOv3的C++调用二(成功)

这个方法我从头开始讲,我的电脑的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<DetectResult>& 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<DetectResult> Result)
    {
        for (vector<DetectResult>::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 <string>
#include <YOLO_V3/include/darknet.h>
#include <opencv2/opencv.hpp>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <condition_variable>
#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<DetectResult>& vDetectResults);
        void DrawResult( cv::Mat &Image, vector<DetectResult> 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 <string>

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++调用应该还是通过第一种方法实现。

2018-07-25 11:02:42 linmingan 阅读数 164
  • YOLOv3目标检测实战:训练自己的数据集

    YOLOv3是一种基于深度学习的端到端实时目标检测方法,以速度快见长。本课程将手把手地教大家使用labelImg标注和使用YOLOv3训练自己的数据集。课程分为三个小项目:足球目标检测(单目标检测)、梅西目标检测(单目标检测)、足球和梅西同时目标检测(两目标检测)。 本课程的YOLOv3使用Darknet,在Ubuntu系统上做项目演示。包括:安装Darknet、给自己的数据集打标签、整理自己的数据集、修改配置文件、训练自己的数据集、测试训练出的网络模型、性能统计(mAP计算和画出PR曲线)和先验框聚类。 Darknet是使用C语言实现的轻型开源深度学习框架,依赖少,可移植性好,值得深入探究。 除本课程《YOLOv3目标检测实战:训练自己的数据集》外,本人推出了有关YOLOv3目标检测的系列课程,请持续关注该系列的其它课程视频,包括: 《YOLOv3目标检测实战:交通标志识别》 《YOLOv3目标检测:原理与源码解析》 《YOLOv3目标检测:网络模型改进方法》 敬请关注并选择学习!

    1127 人正在学习 去看看 白勇

YOLOv3的主要改进主要在两个方面,分别是分类网络的设计以及加入了多尺度特征图预测。当然还有其他的改变,比如进行多分类的时候不用softmax了而是使用independent logistic classifiers,然后损失函数是 binary cross-entropy loss。作者还是修复了yolov2中的数据读取bug,这提升了2%的mAP。

多尺度特征图预测

YOLOv3使用了3个不同尺度的特征图进行预测,来缓解YOLO对小目标检测的缺陷。借鉴于FPN网络的设计思想,YOLOv3在基础网络后面加入了一系列的上采样模块和卷积模块,来生成多尺度特征图用于预测。每个特征图只预测3个box,不像以前v2的5个box,这样总共预测9个box。这9个box的初始先验宽高也是用聚类聚出来的。

特征抽取网络

这里写图片描述

主要是使用了多尺度卷积核和跳跃连接。

具体的多尺度预测结构和特征抽取网络结构还是看作者提供的网络结构配置文件会清晰一点。
作者还尝试了一些失败的试验,比如加入focal loss,或者使用rcnn的样本选取策略。

试验结果

首先是分类网络在imagenet上的比较,速度和精度都不错:
这里写图片描述
与其他检测框架在精度上的比较,相比最好的二阶段框架还是差了些,但是对于其他的一阶段还是很好的:
这里写图片描述

速度的比较:
这里写图片描述

2018-07-12 21:31:44 qq_41994006 阅读数 4458
  • YOLOv3目标检测实战:训练自己的数据集

    YOLOv3是一种基于深度学习的端到端实时目标检测方法,以速度快见长。本课程将手把手地教大家使用labelImg标注和使用YOLOv3训练自己的数据集。课程分为三个小项目:足球目标检测(单目标检测)、梅西目标检测(单目标检测)、足球和梅西同时目标检测(两目标检测)。 本课程的YOLOv3使用Darknet,在Ubuntu系统上做项目演示。包括:安装Darknet、给自己的数据集打标签、整理自己的数据集、修改配置文件、训练自己的数据集、测试训练出的网络模型、性能统计(mAP计算和画出PR曲线)和先验框聚类。 Darknet是使用C语言实现的轻型开源深度学习框架,依赖少,可移植性好,值得深入探究。 除本课程《YOLOv3目标检测实战:训练自己的数据集》外,本人推出了有关YOLOv3目标检测的系列课程,请持续关注该系列的其它课程视频,包括: 《YOLOv3目标检测实战:交通标志识别》 《YOLOv3目标检测:原理与源码解析》 《YOLOv3目标检测:网络模型改进方法》 敬请关注并选择学习!

    1127 人正在学习 去看看 白勇

可结合第一张与第二张理解网络结构

layer     filters    size              input                output
    0 conv     32  3 x 3 / 1   416 x 416 x   3   ->   416 x 416 x  32
    1 conv     64  3 x 3 / 2   416 x 416 x  32   ->   208 x 208 x  64
    2 conv     32  1 x 1 / 1   208 x 208 x  64   ->   208 x 208 x  32
    3 conv     64  3 x 3 / 1   208 x 208 x  32   ->   208 x 208 x  64
    4 Shortcut Layer: 1
    5 conv    128  3 x 3 / 2   208 x 208 x  64   ->   104 x 104 x 128
    6 conv     64  1 x 1 / 1   104 x 104 x 128   ->   104 x 104 x  64
    7 conv    128  3 x 3 / 1   104 x 104 x  64   ->   104 x 104 x 128
    8 Shortcut Layer: 5
    9 conv     64  1 x 1 / 1   104 x 104 x 128   ->   104 x 104 x  64
   10 conv    128  3 x 3 / 1   104 x 104 x  64   ->   104 x 104 x 128
   11 Shortcut Layer: 8
   12 conv    256  3 x 3 / 2   104 x 104 x 128   ->    52 x  52 x 256
   13 conv    128  1 x 1 / 1    52 x  52 x 256   ->    52 x  52 x 128
   14 conv    256  3 x 3 / 1    52 x  52 x 128   ->    52 x  52 x 256
   15 Shortcut Layer: 12
   16 conv    128  1 x 1 / 1    52 x  52 x 256   ->    52 x  52 x 128
   17 conv    256  3 x 3 / 1    52 x  52 x 128   ->    52 x  52 x 256
   18 Shortcut Layer: 15
   19 conv    128  1 x 1 / 1    52 x  52 x 256   ->    52 x  52 x 128
   20 conv    256  3 x 3 / 1    52 x  52 x 128   ->    52 x  52 x 256
   21 Shortcut Layer: 18
   22 conv    128  1 x 1 / 1    52 x  52 x 256   ->    52 x  52 x 128
   23 conv    256  3 x 3 / 1    52 x  52 x 128   ->    52 x  52 x 256
   24 Shortcut Layer: 21
   25 conv    128  1 x 1 / 1    52 x  52 x 256   ->    52 x  52 x 128
   26 conv    256  3 x 3 / 1    52 x  52 x 128   ->    52 x  52 x 256
   27 Shortcut Layer: 24
   28 conv    128  1 x 1 / 1    52 x  52 x 256   ->    52 x  52 x 128
   29 conv    256  3 x 3 / 1    52 x  52 x 128   ->    52 x  52 x 256
   30 Shortcut Layer: 27
   31 conv    128  1 x 1 / 1    52 x  52 x 256   ->    52 x  52 x 128
   32 conv    256  3 x 3 / 1    52 x  52 x 128   ->    52 x  52 x 256
   33 Shortcut Layer: 30
   34 conv    128  1 x 1 / 1    52 x  52 x 256   ->    52 x  52 x 128
   35 conv    256  3 x 3 / 1    52 x  52 x 128   ->    52 x  52 x 256
   36 Shortcut Layer: 33
   37 conv    512  3 x 3 / 2    52 x  52 x 256   ->    26 x  26 x 512
   38 conv    256  1 x 1 / 1    26 x  26 x 512   ->    26 x  26 x 256
   39 conv    512  3 x 3 / 1    26 x  26 x 256   ->    26 x  26 x 512
   40 Shortcut Layer: 37
   41 conv    256  1 x 1 / 1    26 x  26 x 512   ->    26 x  26 x 256
   42 conv    512  3 x 3 / 1    26 x  26 x 256   ->    26 x  26 x 512
   43 Shortcut Layer: 40
   44 conv    256  1 x 1 / 1    26 x  26 x 512   ->    26 x  26 x 256
   45 conv    512  3 x 3 / 1    26 x  26 x 256   ->    26 x  26 x 512
   46 Shortcut Layer: 43
   47 conv    256  1 x 1 / 1    26 x  26 x 512   ->    26 x  26 x 256
   48 conv    512  3 x 3 / 1    26 x  26 x 256   ->    26 x  26 x 512
   49 Shortcut Layer: 46
   50 conv    256  1 x 1 / 1    26 x  26 x 512   ->    26 x  26 x 256
   51 conv    512  3 x 3 / 1    26 x  26 x 256   ->    26 x  26 x 512
   52 Shortcut Layer: 49
   53 conv    256  1 x 1 / 1    26 x  26 x 512   ->    26 x  26 x 256
   54 conv    512  3 x 3 / 1    26 x  26 x 256   ->    26 x  26 x 512
   55 Shortcut Layer: 52
   56 conv    256  1 x 1 / 1    26 x  26 x 512   ->    26 x  26 x 256
   57 conv    512  3 x 3 / 1    26 x  26 x 256   ->    26 x  26 x 512
   58 Shortcut Layer: 55
   59 conv    256  1 x 1 / 1    26 x  26 x 512   ->    26 x  26 x 256
   60 conv    512  3 x 3 / 1    26 x  26 x 256   ->    26 x  26 x 512
   61 Shortcut Layer: 58
   62 conv   1024  3 x 3 / 2    26 x  26 x 512   ->    13 x  13 x1024
   63 conv    512  1 x 1 / 1    13 x  13 x1024   ->    13 x  13 x 512
   64 conv   1024  3 x 3 / 1    13 x  13 x 512   ->    13 x  13 x1024
   65 Shortcut Layer: 62
   66 conv    512  1 x 1 / 1    13 x  13 x1024   ->    13 x  13 x 512
   67 conv   1024  3 x 3 / 1    13 x  13 x 512   ->    13 x  13 x1024
   68 Shortcut Layer: 65
   69 conv    512  1 x 1 / 1    13 x  13 x1024   ->    13 x  13 x 512
   70 conv   1024  3 x 3 / 1    13 x  13 x 512   ->    13 x  13 x1024
   71 Shortcut Layer: 68
   72 conv    512  1 x 1 / 1    13 x  13 x1024   ->    13 x  13 x 512
   73 conv   1024  3 x 3 / 1    13 x  13 x 512   ->    13 x  13 x1024
   74 Shortcut Layer: 71
   75 conv    512  1 x 1 / 1    13 x  13 x1024   ->    13 x  13 x 512
   76 conv   1024  3 x 3 / 1    13 x  13 x 512   ->    13 x  13 x1024
   77 conv    512  1 x 1 / 1    13 x  13 x1024   ->    13 x  13 x 512
   78 conv   1024  3 x 3 / 1    13 x  13 x 512   ->    13 x  13 x1024
   79 conv    512  1 x 1 / 1    13 x  13 x1024   ->    13 x  13 x 512
   80 conv   1024  3 x 3 / 1    13 x  13 x 512   ->    13 x  13 x1024
   81 conv     18  1 x 1 / 1    13 x  13 x1024   ->    13 x  13 x  18
   82 detection
   83 route  79
   84 conv    256  1 x 1 / 1    13 x  13 x 512   ->    13 x  13 x 256
   85 upsample            2x    13 x  13 x 256   ->    26 x  26 x 256
   86 route  85 61
   87 conv    256  1 x 1 / 1    26 x  26 x 768   ->    26 x  26 x 256
   88 conv    512  3 x 3 / 1    26 x  26 x 256   ->    26 x  26 x 512
   89 conv    256  1 x 1 / 1    26 x  26 x 512   ->    26 x  26 x 256
   90 conv    512  3 x 3 / 1    26 x  26 x 256   ->    26 x  26 x 512
   91 conv    256  1 x 1 / 1    26 x  26 x 512   ->    26 x  26 x 256
   92 conv    512  3 x 3 / 1    26 x  26 x 256   ->    26 x  26 x 512
   93 conv     18  1 x 1 / 1    26 x  26 x 512   ->    26 x  26 x  18
   94 detection
   95 route  91
   96 conv    128  1 x 1 / 1    26 x  26 x 256   ->    26 x  26 x 128
   97 upsample            2x    26 x  26 x 128   ->    52 x  52 x 128
   98 route  97 36
   99 conv    128  1 x 1 / 1    52 x  52 x 384   ->    52 x  52 x 128
  100 conv    256  3 x 3 / 1    52 x  52 x 128   ->    52 x  52 x 256
  101 conv    128  1 x 1 / 1    52 x  52 x 256   ->    52 x  52 x 128
  102 conv    256  3 x 3 / 1    52 x  52 x 128   ->    52 x  52 x 256
  103 conv    128  1 x 1 / 1    52 x  52 x 256   ->    52 x  52 x 128
  104 conv    256  3 x 3 / 1    52 x  52 x 128   ->    52 x  52 x 256
  105 conv     18  1 x 1 / 1    52 x  52 x 256   ->    52 x  52 x  18
  106 detection

82 detection对应图中y1=13 x 13 x 18

94 detection对应图中y2=26 x 26 x 18

106 detection对应图中y3=52 x 52 x 18

根据论文,输出张量形式为N*N*(3*(4+1+n))

3代表每种尺寸有3个box,4代表bbox的偏移信息,包括中心点以及宽度高度,n代表进行分n类。

参考:https://blog.csdn.net/qq_35608277/article/details/79954537
论文地址:https://pjreddie.com/media/files/papers/YOLOv3.pdf
2019-11-18 15:31:31 sinat_33896833 阅读数 12
  • YOLOv3目标检测实战:训练自己的数据集

    YOLOv3是一种基于深度学习的端到端实时目标检测方法,以速度快见长。本课程将手把手地教大家使用labelImg标注和使用YOLOv3训练自己的数据集。课程分为三个小项目:足球目标检测(单目标检测)、梅西目标检测(单目标检测)、足球和梅西同时目标检测(两目标检测)。 本课程的YOLOv3使用Darknet,在Ubuntu系统上做项目演示。包括:安装Darknet、给自己的数据集打标签、整理自己的数据集、修改配置文件、训练自己的数据集、测试训练出的网络模型、性能统计(mAP计算和画出PR曲线)和先验框聚类。 Darknet是使用C语言实现的轻型开源深度学习框架,依赖少,可移植性好,值得深入探究。 除本课程《YOLOv3目标检测实战:训练自己的数据集》外,本人推出了有关YOLOv3目标检测的系列课程,请持续关注该系列的其它课程视频,包括: 《YOLOv3目标检测实战:交通标志识别》 《YOLOv3目标检测:原理与源码解析》 《YOLOv3目标检测:网络模型改进方法》 敬请关注并选择学习!

    1127 人正在学习 去看看 白勇

定义网络

我们前面指出,用nn.Module类去搭建pytorch传统结构。让我们从我们的检测文件中定义网络
在darknet.py中,我们加入了如下类

class Darknet(nn.Module):
    def __init__(self, cfgfile):
        super(Darknet, self).__init__()
        self.blocks = parse_cfg(cfgfile)
        self.net_info, self.module_list = create_modules(self.blocks)

继承了nn.Module类并命名为darknet初始化成员为member,blocks,net_info和module_list

网络的前向传播

前向传播通过重写nn.Module类中的forward函数实现
forward函数两个目的,1计算输出2以某种方式变化输出检测特征图以便后面处理

def forward(self, x, CUDA):
    modules = self.blocks[1:]
    outputs = {}   #We cache the outputs for the route layer

forward有三个参数self,x,CUDA如果CUDA is True,用GPU加速前向传播
这里我们迭代self.blocks[1:]而不是self.blocks因为self.blocks的第一个元素是网络块而不是前向传播的部分
因为路线和段阶层需要前面层的输出特征,我们暂时将每一层的输出特征放到outputs这个目录中,关键字是层的索引,权值是特征图。像create_modules函数的例子,我们迭代含有网络的模块module_list,需要注意的是模块在配置文件中已经加入了一定的顺序,说明我们只需要运行我们的输入模块来获得输出

write = 0     #This is explained a bit later
for i, module in enumerate(modules):        
    module_type = (module["type"])

卷积和上采样层

遇到卷积或者上采样模块,就该使用前向传播

        if module_type == "convolutional" or module_type == "upsample":
            x = self.module_list[i](x)

路线层和短接层

如果看到路线层的代码,我们必须想到两种情况,如果我们要链接两个特征图,我们用到torch.cat函数第二个参数为1,因为我们想沿着深度方向链接特征图

        elif module_type == "route":
            layers = module["layers"]
            layers = [int(a) for a in layers]

            if (layers[0]) > 0:
                layers[0] = layers[0] - i

            if len(layers) == 1:
                x = outputs[i + (layers[0])]

            else:
                if (layers[1]) > 0:
                    layers[1] = layers[1] - i

                map1 = outputs[i + layers[0]]
                map2 = outputs[i + layers[1]]

                x = torch.cat((map1, map2), 1)

        elif  module_type == "shortcut":
            from_ = int(module["from"])
            x = outputs[i-1] + outputs[i+from_]

YOLO (检测层)

YOLO检测层的输出是包含着特征图深度方向的属性的锚框的卷积层,相邻的网格所预测锚框的属性。
另外一个问题是因为检测在三中尺度下进行,预测图的维度也不同,尽管三种特征图的维度不同,但输出处理方式是一致的,将在一个向量中操作而不是三个分开的向量。

转换输出

在util.py中提供了predict_transform函数,当我们使用Darknet类中的forward函数时我们会引入predict_transform这个函数
在util.py中添加导入的模块

from __future__ import division

import torch 
import torch.nn as nn
import torch.nn.functional as F 
from torch.autograd import Variable
import numpy as np
import cv2

predict_transform函数有五个参数:prediction (our output), inp_dim (input image dimension), anchors, num_classes, and an optional CUDA flag
def predict_transform(prediction, inp_dim, anchors, num_classes, CUDA = True):
predict_transform函数读取检测特征图并将其转化成2-D向量,每一行的向量对应着锚框的权值
如下是前面转化的代码

    batch_size = prediction.size(0)
    stride =  inp_dim // prediction.size(2)
    grid_size = inp_dim // stride
    bbox_attrs = 5 + num_classes
    num_anchors = len(anchors)
    
    prediction = prediction.view(batch_size, bbox_attrs*num_anchors, grid_size*grid_size)
    prediction = prediction.transpose(1,2).contiguous()
    prediction = prediction.view(batch_size, grid_size*grid_size*num_anchors, bbox_attrs)

锚点的维度是根据net块的高和宽的属性确定的,这些属性描述了输入图像的维度,比检测图要大,所以,我们必须根据检测特征图的步长来划分锚框

    anchors = [(a[0]/stride, a[1]/stride) for a in anchors]

我们需要根据第一部分的公式来转换我们的输出
用sigmoid函数处理XY坐标和目标得分

    #Sigmoid the  centre_X, centre_Y. and object confidencce
    prediction[:,:,0] = torch.sigmoid(prediction[:,:,0])
    prediction[:,:,1] = torch.sigmoid(prediction[:,:,1])
    prediction[:,:,4] = torch.sigmoid(prediction[:,:,4])

给中心预测坐标加入网格偏置

    #Add the center offsets
    grid = np.arange(grid_size)
    a,b = np.meshgrid(grid, grid)

    x_offset = torch.FloatTensor(a).view(-1,1)
    y_offset = torch.FloatTensor(b).view(-1,1)

    if CUDA:
        x_offset = x_offset.cuda()
        y_offset = y_offset.cuda()

    x_y_offset = torch.cat((x_offset, y_offset), 1).repeat(1,num_anchors).view(-1,2).unsqueeze(0)

    prediction[:,:,:2] += x_y_offset

将锚点添加到锚框的维度上去

    #log space transform height and the width
    anchors = torch.FloatTensor(anchors)

    if CUDA:
        anchors = anchors.cuda()

    anchors = anchors.repeat(grid_size*grid_size, 1).unsqueeze(0)
    prediction[:,:,2:4] = torch.exp(prediction[:,:,2:4])*anchors

将sigmoid激活函数应用到类的得分中

    prediction[:,:,5: 5 + num_classes] = torch.sigmoid((prediction[:,:, 5 : 5 + num_classes]))

最后我们想做的是将预测图转换成输入图像一样大小,锚框的大小属性根据特征图(13*13),如果输入416×416,我们在这个属性乘步长,将stride设置成variable

prediction[:,:,:4] *= stride

包含循环体
在函数末尾返回预测值

    return prediction

重新访问检测层

将输出向量转换后,我们就能连接三个不同尺度的检测图成为一个大的向量,
现在我们使用predict_transform函数,我们在forward函数中写调整检测特征图的代码:
在darknet.py文件中
加入如下导入包

from util import * 

然后在forward函数中

        elif module_type == 'yolo':        

            anchors = self.module_list[i][0].anchors
            #Get the input dimensions
            inp_dim = int (self.net_info["height"])

            #Get the number of classes
            num_classes = int (module["classes"])

            #Transform 
            x = x.data
            x = predict_transform(x, inp_dim, anchors, num_classes, CUDA)
            if not write:              #if no collector has been intialised. 
                detections = x
                write = 1

            else:       
                detections = torch.cat((detections, x), 1)

        outputs[i] = x

返回检测值

    return detections

测试前向传播

现在一个创建虚拟输入的函数,将这个输入传输到网络中,在写这函数之前,将图片保存到工作目录中去,如果是在linux系统中输入

wget https://github.com/ayooshkathuria/pytorch-yolo-v3/raw/master/dog-cycle-car.png

然后,在darknet.py文件开始定义函数

def get_test_input():
    img = cv2.imread("dog-cycle-car.png")
    img = cv2.resize(img, (416,416))          #Resize to the input dimension
    img_ =  img[:,:,::-1].transpose((2,0,1))  # BGR -> RGB | H X W C -> C X H X W 
    img_ = img_[np.newaxis,:,:,:]/255.0       #Add a channel at 0 (for batch) | Normalise
    img_ = torch.from_numpy(img_).float()     #Convert to float
    img_ = Variable(img_)                     # Convert to Variable
    return img_

然后输入如下代码

model = Darknet("cfg/yolov3.cfg")
inp = get_test_input()
pred = model(inp, torch.cuda.is_available())
print (pred)

就可以看到输出文件了

(  0  ,.,.) = 
   16.0962   17.0541   91.5104  ...     0.4336    0.4692    0.5279
   15.1363   15.2568  166.0840  ...     0.5561    0.5414    0.5318
   14.4763   18.5405  409.4371  ...     0.5908    0.5353    0.4979
               ⋱                ...             
  411.2625  412.0660    9.0127  ...     0.5054    0.4662    0.5043
  412.1762  412.4936   16.0449  ...     0.4815    0.4979    0.4582
  412.1629  411.4338   34.9027  ...     0.4306    0.5462    0.4138
[torch.FloatTensor of size 1x10647x85]

向量的形状是11064785,第一个维度是批的大小,因为我们只用了一张图片所以是1,对于一批中的每张图片,我们有10647*85表,表的每一行代表锚框
在这一点上,网络有随机权值,并且不会产生正确输出,我们需要在网络中加载权值文件,所以我们使用了官方的权值文件

下载预训练权重

将权值文件下载到工作目录

wget https://pjreddie.com/media/files/yolov3.weights

理解权重文件

官方权值文件是包含权重的二进制文件。
首先,权重只属于两种层,批处理层或者卷积层
这些层的权重像配置文件中存储的那样。所以,如果卷积层紧接着是短接层,短接层和另一个卷积块成块,我们会期望文件包含先前卷积块的权值,紧接着后面的块。
当卷积块中出现批量归一化,就没有偏置,但是当没有批量归一化层,权重的偏置必须从文件中得到。
下图总结权重怎么储存
在这里插入图片描述

载入权重

写一个载入权重函数,是Darknet类的成员函数,会取一个参数作为权重文件的路径

def load_weights(self, weightfile):

权重文件的前160字节存储5个int32值构成文件的开头

    #Open the weights file
    fp = open(weightfile, "rb")

    #The first 5 values are header information 
    # 1. Major version number
    # 2. Minor Version Number
    # 3. Subversion number 
    # 4,5. Images seen by the network (during training)
    header = np.fromfile(fp, dtype = np.int32, count = 5)
    self.header = torch.from_numpy(header)
    self.seen = self.header[3]

剩下的位代表权值,权重保存在float32,用np.adarray加载剩下的权重

    weights = np.fromfile(fp, dtype = np.float32)

使用了权值文件,权值加载进了网络的模块中去

    ptr = 0
    for i in range(len(self.module_list)):
        module_type = self.blocks[i + 1]["type"]

        #If module_type is convolutional load weights
        #Otherwise ignore.

在循环中,我们首先检查卷积块是否含有批量归一化,基于这个,我们载入权重

        if module_type == "convolutional":
            model = self.module_list[i]
            try:
                batch_normalize = int(self.blocks[i+1]["batch_normalize"])
            except:
                batch_normalize = 0

            conv = model[0]

设置ptr变量来跟踪我们在权值阵列的位置,如果批量归一化是True,按下面的方式加载权重

        if (batch_normalize):
            bn = model[1]

            #Get the number of weights of Batch Norm Layer
            num_bn_biases = bn.bias.numel()

            #Load the weights
            bn_biases = torch.from_numpy(weights[ptr:ptr + num_bn_biases])
            ptr += num_bn_biases

            bn_weights = torch.from_numpy(weights[ptr: ptr + num_bn_biases])
            ptr  += num_bn_biases

            bn_running_mean = torch.from_numpy(weights[ptr: ptr + num_bn_biases])
            ptr  += num_bn_biases

            bn_running_var = torch.from_numpy(weights[ptr: ptr + num_bn_biases])
            ptr  += num_bn_biases

            #Cast the loaded weights into dims of model weights. 
            bn_biases = bn_biases.view_as(bn.bias.data)
            bn_weights = bn_weights.view_as(bn.weight.data)
            bn_running_mean = bn_running_mean.view_as(bn.running_mean)
            bn_running_var = bn_running_var.view_as(bn.running_var)

            #Copy the data to model
            bn.bias.data.copy_(bn_biases)
            bn.weight.data.copy_(bn_weights)
            bn.running_mean.copy_(bn_running_mean)
            bn.running_var.copy_(bn_running_var)

如果批量归一化不为真,从卷积层加载偏置

        else:
            #Number of biases
            num_biases = conv.bias.numel()

            #Load the weights
            conv_biases = torch.from_numpy(weights[ptr: ptr + num_biases])
            ptr = ptr + num_biases

            #reshape the loaded weights according to the dims of the model weights
            conv_biases = conv_biases.view_as(conv.bias.data)

            #Finally copy the data
            conv.bias.data.copy_(conv_biases)

最后,加载卷积层的权重

#Let us load the weights for the Convolutional layers
num_weights = conv.weight.numel()

#Do the same as above for weights
conv_weights = torch.from_numpy(weights[ptr:ptr+num_weights])
ptr = ptr + num_weights

conv_weights = conv_weights.view_as(conv.weight.data)
conv.weight.data.copy_(conv_weights)

我们已经写完了函数,现在可以在darknet对象中调用load_weights函数将权重文件加载到Darknet对象中去

model = Darknet("cfg/yolov3.cfg")
model.load_weights("yolov3.weights")
没有更多推荐了,返回首页