OpenCV3.4调用YOLOv3模型完成目标检测

916 阅读3分钟

这是我参与11月更文挑战的第21天,活动详情查看:2021最后一次更文挑战

一、前言

OpenCV是开源的计算机视觉库,支持跨平台,本身虽然是C,C++编写,也提供了其他语言的调用接口,比如: python等等。 下载OpenCV后,可以使用OpenCV本身自带的模型,体验人脸、猫脸、人体、眼睛、鼻子等检测功能,效率也还是比较高的。这篇文章主要是介绍OpenCV如何调用YOLOv3的模型完成目标检测,YOLO的目标检测是非常强大的,YOLOV3自带的模型本身可以完成80多种目标检测,而且精度还不错。

下面是调用YOLO模型检测效果:

image.png image.png

image.png

image.png

当前的环境介绍:

OpenCV版本: 3.4.7 minGW 32位版本

(第二章提供例子代码使用opencv官方的x64库,运行崩溃,MSVC编译器需要对应OpenCV的版本才行)

YOLO 版本: YOLOv3

使用的YOLO模型: YOLOv3 官方自带模型

YOLOv4的模型,OpenCV3.x无法调用,需要用到OpenCV4.2以上的版本。

YOLOv3的模型文件直接去YOLO官网就能下载,OpenCV调用YOLO需要用到以下3个文件:

coco.names
yolov3.cfg
yolov3.weights

YOLO的官方地址: pjreddie.com/darknet/yol…

image.png 其中yolov3.cfg、yolov3.weights 可以直接在官网介绍里找到下载地址。 coco.names 没有下载地址,我这里就直接把内容贴出来:

person
bicycle
car
motorbike
aeroplane
bus
train
truck
boat
traffic light
fire hydrant
stop sign
parking meter
bench
bird
cat
dog
horse
sheep
cow
elephant
bear
zebra
giraffe
backpack
umbrella
handbag
tie
suitcase
frisbee
skis
snowboard
sports ball
kite
baseball bat
baseball glove
skateboard
surfboard
tennis racket
bottle
wine glass
cup
fork
knife
spoon
bowl
banana
apple
sandwich
orange
broccoli
carrot
hot dog
pizza
donut
cake
chair
sofa
pottedplant
bed
diningtable
toilet
tvmonitor
laptop
mouse
remote
keyboard
cell phone
microwave
oven
toaster
sink
refrigerator
book
clock
vase
scissors
teddy bear
hair drier
toothbrush

二、示例代码

函数声明:

//YOLOv3目标检测
void yolo_Handle(QImage LoadImage);
void postprocess(Mat& frame, const vector<Mat>& outs, float confThreshold, float nmsThreshold);
void drawPred(int classId, float conf, int left, int top, int right, int bottom, Mat& frame);
vector<String> getOutputsNames(Net&net);

核心代码

vector<String> ImageHandle::getOutputsNames(Net&net)
{
    static vector<String> names;
    if (names.empty())
    {
        vector<int> outLayers = net.getUnconnectedOutLayers();
        vector<String> layersNames = net.getLayerNames();
        names.resize(outLayers.size());
        for (size_t i = 0; i < outLayers.size(); ++i)
            names[i] = layersNames[outLayers[i] - 1];
    }
    return names;
}


//处理图片,在图片上绘制矩形框
//其中传入的参数解释:
//conf    目标在图片里x坐标位置
//left    目标在图片里y坐标位置
//right   目标在图片里的宽度
//bottom  目标在图片里的高度
//这个就是识别的目标物体在图片里的矩形尺寸
void ImageHandle::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);

    //获取识别到标签类名及其识别度(可信度)的标签,可信度的范围是0~1.0  越大表示目标越准确
    string label = format("%.5f", conf);
    if (!classes.empty())
    {
        CV_Assert(classId < (int)classes.size());
        label = classes[classId] + ":" + label;
    }
    else
    {
         qDebug()<<"类型标签为空. 请检查coco.names路径.";
    }

    qDebug()<<tr("识别到的标签与可信度:%1").arg(QString::fromStdString(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);
}


void ImageHandle::postprocess(Mat& frame, const vector<Mat>& outs, float confThreshold, float nmsThreshold)
{
    vector<int> classIds;
    vector<float> confidences;
    vector<Rect> boxes;

    for (size_t i = 0; i < outs.size(); ++i)
    {
        //扫描网络输出的所有边界框,只保留分数高的人。将长方体的类标签指定为类
        //以最高的分数为方块。
        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;
            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<int> indices;
    NMSBoxes(boxes, confidences, confThreshold, nmsThreshold, indices);

    //取出当前图片里的识别结果
    //indices容器保存的就是当前图片里识别出来的目标标签数量
    for (size_t i = 0; i < indices.size(); ++i)
    {
        int idx = indices[i];
        Rect box = boxes[idx];

        //在每个识别到的物体上绘制矩形,绘制目标物体的标签名称以及可信度(0~1.0)
        drawPred(classIds[idx], confidences[idx], box.x, box.y,
            box.x + box.width, box.y + box.height, frame);
    }
}


Mat ImageHandle::QImage2cvMat(QImage image)
{
    Mat mat;
    switch(image.format())
    {
    case QImage::Format_ARGB32:
    case QImage::Format_RGB32:
    case QImage::Format_ARGB32_Premultiplied:
        mat = Mat(image.height(), image.width(), CV_8UC4, (void*)image.constBits(), image.bytesPerLine());
        break;
    case QImage::Format_RGB888:
        mat = Mat(image.height(), image.width(), CV_8UC3, (void*)image.constBits(), image.bytesPerLine());
        cvtColor(mat, mat, CV_BGR2RGB);
        break;
    case QImage::Format_Indexed8:
        mat = Mat(image.height(), image.width(), CV_8UC1, (void*)image.constBits(), image.bytesPerLine());
        break;
    }
    return mat;
}

QImage ImageHandle::Mat2QImage(const Mat& mat)
{
    if(mat.type() == CV_8UC1)
    {
        QImage image(mat.cols, mat.rows, QImage::Format_Indexed8);
        image.setColorCount(256);
        for(int i = 0; i < 256; i++)
        {
            image.setColor(i, qRgb(i, i, i));
        }
        uchar *pSrc = mat.data;
        for(int row = 0; row < mat.rows; row ++)
        {
            uchar *pDest = image.scanLine(row);
            memcpy(pDest, pSrc, mat.cols);
            pSrc += mat.step;
        }
        return image;
    }
    else if(mat.type() == CV_8UC3)
    {
        const uchar *pSrc = (const uchar*)mat.data;
        QImage image(pSrc, mat.cols, mat.rows, mat.step, QImage::Format_RGB888);
        return image.rgbSwapped();
    }
    else if(mat.type() == CV_8UC4)
    {
        const uchar *pSrc = (const uchar*)mat.data;
        QImage image(pSrc, mat.cols, mat.rows, mat.step, QImage::Format_ARGB32);
        return image.copy();
    }
    else
    {
        return QImage();
    }
}

调用示例--在子线程里调用

代码里采用CPU进行图像处理,速度受电脑性能、输入的源图像尺寸影响。

Intel i7高性能CPU,输入1280*720图像,检测速度500ms一帧。

Intel i7低功耗CPU,输入1280*720图像,检测速度2秒~4秒一帧。

下面是检测效果展示--播放的是动物世界视频--每帧:

image.png

image.png

image.png