opencv for java之——深度学习目标检测MobileNet-SSD

前言

当前,在目标检测领域,基于深度学习的目标检测方法在准确度上碾压传统的方法。基于深度学习的目标检测先后出现了RCNN,FastRCNN,FasterRCNN, 端到端目标检测方法YOLO,YOLO-9000,YOLO-v3, MobileNet-SSD,以及Mask-RCNN等。MobileNet是一种轻量级的网络,本文基于MobileNet-SSD+opencv实现目标检测。


开发环境

  • windows10 x64
  • IntellJ IDEA
  • opencv3.4.2
  • Visual Studio 2017(测试C++版本MobileNet-SSD)

MobileNet-SSD简介

MobileNet-SSD caffe


opencv调用MobileNet-SSD

C++版本MobileNet-SSD的运行

目前MobileNet有基于caffe框架训练好的,caffe本身就是C++实现的,因此网上的大部分opencv调用MobileNet都是C++代码。本人先采用vs+opencv3.4.1成功测试之后,再用Java代码进行移植。

Visual Stuido 2017配置opencv过程就不赘述了
MobileNet-SSD训练好的caffe模型在上面的MobileNet-SSD caffe链接下载

C++代码:

#include<iostream>
#include<opencv2/opencv.hpp>
#include<opencv2/dnn.hpp>

using namespace std;
using namespace cv;
using namespace cv::dnn;

class Object
{
public:
    Object();
    Object(int index, float confidence, String name, Rect rect);
    ~Object();

public:
    int index;
    String name;
    float confidence;
    Rect rect;

private:

};

Object::Object() {
}

Object::Object(int index,float confidence,String name,Rect rect) {
    this->index = index;
    this->confidence = confidence;
    this->name = name;
    this->rect = rect;
}

Object::~Object() {
}

//----------------------------全局常量----------------------------------
//配置好protxt文件,网络结构描述文件
//配置好caffemodel文件,训练好的网络权重
const String PROTOTX_FILE ="MobileNetSSD\\MobileNetSSD_deploy.prototxt";
const String CAFFE_MODEL_FILE = "MobileNet-SSD\\MobileNetSSD_deploy.caffemodel";
const String classNames[] = { "background", "aeroplane", "bicycle", "bird", "boat", "bottle", "bus", "car", "cat", "chair",
"cow", "diningtable", "dog", "horse", "motorbike", "person", "pottedplant", "sheep", "sofa", "train", "tvmonitor" };
const float CONF_THRESH = 0.7f;

int main() {

    //------------------------实例化网络----------------------------
    Net mobileNetSSD = readNetFromCaffe(PROTOTX_FILE, CAFFE_MODEL_FILE);
    if (mobileNetSSD.empty()) {
        cerr << "加载网络失败!" << endl;
        return -1;
    }

    TickMeter t;

    //----------------------设置网络输入-----------------------
    Mat srcImg = imread("D:\\小可爱\\Java学习\\day-6\\代码\\opencv调用MobileNet-SSD\\MobileNet-test.jpg");
    //将二维图像转换为CNN输入的张量Tensor,作为网络的输入
    mobileNetSSD.setInput(blobFromImage(srcImg, 1.0 / 127.5, Size(300, 300), Scalar(127.5, 127.5, 127.5), true, false));

    t.start();
    //--------------------CNN网络前向计算----------------------
    Mat netOut = mobileNetSSD.forward();
    t.stop();
    cout << "检测时间=" << t.getTimeMilli() << "ms" << endl;

    //----------------------解析计算结果-----------------------
    vector<Object> detectObjects;
    Mat detectionResult(netOut.size[2], netOut.size[3], CV_32F, netOut.ptr<float>());
    for (int i = 0; i < detectionResult.rows; i++) {

        //目标类别的索引
        int objectIndex = detectionResult.at<float>(i, 1);
        //检测结果置信度
        float confidence = detectionResult.at<float>(i, 2);

        //根据置信度阈值过滤掉置信度较小的目标
        if (confidence<CONF_THRESH) {
            continue;
        }

        //反归一化,得到图像坐标
        int xLeftUp = static_cast<int>(detectionResult.at<float>(i, 3)*srcImg.cols);
        int yLeftUp = static_cast<int>(detectionResult.at<float>(i, 4)*srcImg.rows);

        int xRightBottom = static_cast<int>(detectionResult.at<float>(i, 5)*srcImg.cols);
        int yRightBottom = static_cast<int>(detectionResult.at<float>(i, 6)*srcImg.rows);

        //矩形框
        Rect rect(Point{ xLeftUp,yLeftUp }, Point{ xRightBottom,yRightBottom });

        //保存结果
        detectObjects.push_back(Object{ objectIndex,confidence,classNames[objectIndex],rect });


    }

    //------------------------显示结果-----------------------------------
    int count = 0;
    for (auto& i:detectObjects) {

        rectangle(srcImg, i.rect, Scalar(0, 255, 255), 2);
        putText(srcImg, i.name, i.rect.tl(), 1, 1.8, Scalar(255, 0, 0),2);
        cout << "第" << count << "个目标:" << i.name << "\t" << i.rect << "\t" << i.confidence << endl;
        count++;
    }

    imshow("MobileNet-SSD", srcImg);
    waitKey(0);

}


测试图像:


MobileNet-test.jpg

代码说明

  • 相关API
    • blobFromImage() 将输入的二维图像转换为一个4维的张量/高维矩阵,4维张量的顺序为NCHW(N个数,C通道数,H高度,W宽度 )
   /** @brief Creates 4-dimensional blob from series of images. Optionally resizes and
     *  crops @p images from center, subtract @p mean values, scales values by @p scalefactor,
     *  swap Blue and Red channels.
     *  @param images input images (all with 1-, 3- or 4-channels).
     *  @param size spatial size for output image
     *  @param mean scalar with mean values which are subtracted from channels. Values are intended
     *  to be in (mean-R, mean-G, mean-B) order if @p image has BGR ordering and @p swapRB is true.
     *  @param scalefactor multiplier for @p images values.
     *  @param swapRB flag which indicates that swap first and last channels
     *  in 3-channel image is necessary.
     *  @param crop flag which indicates whether image will be cropped after resize or not
     *  @details if @p crop is true, input image is resized so one side after resize is equal to corresponing
     *  dimension in @p size and another one is equal or larger. Then, crop from the center is performed.
     *  If @p crop is false, direct resize without cropping and preserving aspect ratio is performed.
     *  @returns 4-dimansional Mat with NCHW dimensions order.
     */
    CV_EXPORTS_W Mat blobFromImages(const std::vector<Mat>& images, double scalefactor=1.0,
                                    Size size = Size(), const Scalar& mean = Scalar(), bool swapRB=true, bool crop=true);

  • net.forward() 深度神经网络的前向传播计算,返回值也是一个4D的张量

        /** @brief Runs forward pass to compute output of layer with name @p outputName.
         *  @param outputName name for layer which output is needed to get
         *  @return blob for first output of specified layer.
         *  @details By default runs forward pass for the whole network.
         */
        CV_WRAP Mat forward(const String& outputName = String());

  • 调试步骤,输入图像为RGB图像,包含dog,person


    image.png
  • 调试步骤,网络的最终计算输出是一个张量,需要转换为一个2维矩形Mat,是一个7(width)*6(height)的float矩阵,第2列的整数代表目标类别的索引,第3列代表检测结果的置信度,最后4列是归一化的目标矩形框。网络只输出置信度最大的前6个目标,因此输出矩阵的形状为7x6.


    image.png

运行结果

image.png
image.png

检测到了5个目标,2个dog+3个person,后面坐着的2个人以及椅子上中间的人露检了。


Java版本MobileNet-SSD的移植

笔者查看了好几遍Java封装的Mat,没有发现如何将一个高维/4D张量转换为2D矩阵的方法,比较坑。在C++ Mat中,Mat有一个成员变量size,进一步查看之后发现是一个结构体,里面封装了指针,在Java Mat中找不到对应的。卡在这里了,只能用JNI了,好麻烦。

以后有时间再继续倒腾Java版本的 MobileNet-SSD。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,099评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,828评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,540评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,848评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,971评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,132评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,193评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,934评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,376评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,687评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,846评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,537评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,175评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,887评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,134评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,674评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,741评论 2 351

推荐阅读更多精彩内容