【SSD目標檢測】1:圖片、視頻內的物體檢測與定位

一、SSD用於圖片物體的定位與檢測

SSD原理介紹這一篇博客對我的幫助比較大,很詳細的介紹了SSD原理,送給大家做了解

1、下載SSD框架源碼

1.1:閒話不多說——下載SSD源碼,解壓後打開文件,將checkpoints文件夾下的壓縮包也解壓出來,再在pycharm上建立工程,大體如下圖所示:
這裏寫圖片描述
1.2:打開demo文件夾,這裏就是用於外測的圖片集
這裏寫圖片描述

2、SSD做目標檢測

notebooks文件夾下,建立demo_test.py文件,在demo_test.py文件內寫入如下代碼後,直接運行demo_test.py(以下代碼也是notebooks文件夾ssd_tests.ipynb內的代碼,可以用notebook讀取;我只是做了一些小改動)

# -*- coding:utf-8 -*-
# -*- author:zzZ_CMing  CSDN address:https://blog.csdn.net/zzZ_CMing
# -*- 2018/07/14; 15:19
# -*- python3.5
"""
address: https://blog.csdn.net/qq_35608277/article/details/78660469
本文代碼來自於github中微軟官方倉庫
"""
import os
import cv2
import math
import random
import tensorflow as tf
import matplotlib.pyplot as plt
import matplotlib.cm as mpcm
import matplotlib.image as mpimg
from notebooks import visualization
from nets import ssd_vgg_300, ssd_common, np_methods
from preprocessing import ssd_vgg_preprocessing
import sys

# 當引用模塊和運行的腳本不在同一個目錄下,需在腳本開頭添加如下代碼:
sys.path.append('./SSD-Tensorflow/')

slim = tf.contrib.slim

# TensorFlow session
gpu_options = tf.GPUOptions(allow_growth=True)
config = tf.ConfigProto(log_device_placement=False, gpu_options=gpu_options)
isess = tf.InteractiveSession(config=config)

l_VOC_CLASS = ['aeroplane', 'bicycle', 'bird', 'boat', 'bottle',
               'bus', 'car', 'cat', 'chair', 'cow',
               'diningTable', 'dog', 'horse', 'motorbike', 'person',
               'pottedPlant', 'sheep', 'sofa', 'train', 'TV']

# 定義數據格式,設置佔位符
net_shape = (300, 300)
# 預處理,以Tensorflow backend, 將輸入圖片大小改成 300x300,作爲下一步輸入
img_input = tf.placeholder(tf.uint8, shape=(None, None, 3))
# 輸入圖像的通道排列形式,'NHWC'表示 [batch_size,height,width,channel]
data_format = 'NHWC'

# 數據預處理,將img_input輸入的圖像resize爲300大小,labels_pre,bboxes_pre,bbox_img待解析
image_pre, labels_pre, bboxes_pre, bbox_img = ssd_vgg_preprocessing.preprocess_for_eval(
    img_input, None, None, net_shape, data_format,
    resize=ssd_vgg_preprocessing.Resize.WARP_RESIZE)
# 拓展爲4維變量用於輸入
image_4d = tf.expand_dims(image_pre, 0)

# 定義SSD模型
# 是否複用,目前我們沒有在訓練所以爲None
reuse = True if 'ssd_net' in locals() else None
# 調出基於VGG神經網絡的SSD模型對象,注意這是一個自定義類對象
ssd_net = ssd_vgg_300.SSDNet()
# 得到預測類和預測座標的Tensor對象,這兩個就是神經網絡模型的計算流程
with slim.arg_scope(ssd_net.arg_scope(data_format=data_format)):
    predictions, localisations, _, _ = ssd_net.net(image_4d, is_training=False, reuse=reuse)

# 導入官方給出的 SSD 模型參數
ckpt_filename = '../checkpoints/ssd_300_vgg.ckpt'
# ckpt_filename = '../checkpoints/VGG_VOC0712_SSD_300x300_ft_iter_120000.ckpt'
isess.run(tf.global_variables_initializer())
saver = tf.train.Saver()
saver.restore(isess, ckpt_filename)

# 在網絡模型結構中,提取搜索網格的位置
# 根據模型超參數,得到每個特徵層(這裏用了6個特徵層,分別是4,7,8,9,10,11)的anchors_boxes
ssd_anchors = ssd_net.anchors(net_shape)
"""
每層的anchors_boxes包含4個arrayList,前兩個List分別是該特徵層下x,y座標軸對於原圖(300x300)大小的映射
第三,四個List爲anchor_box的長度和寬度,同樣是經過歸一化映射的,根據每個特徵層box數量的不同,這兩個List元素
個數會變化。其中,長寬的值根據超參數anchor_sizes和anchor_ratios制定。
"""


# 加載輔助作圖函數
def colors_subselect(colors, num_classes=21):
    dt = len(colors) // num_classes
    sub_colors = []
    for i in range(num_classes):
        color = colors[i * dt]
        if isinstance(color[0], float):
            sub_colors.append([int(c * 255) for c in color])
        else:
            sub_colors.append([c for c in color])
    return sub_colors


def bboxes_draw_on_img(img, classes, scores, bboxes, colors, thickness=2):
    shape = img.shape
    for i in range(bboxes.shape[0]):
        bbox = bboxes[i]
        color = colors[classes[i]]
        # Draw bounding box...
        p1 = (int(bbox[0] * shape[0]), int(bbox[1] * shape[1]))
        p2 = (int(bbox[2] * shape[0]), int(bbox[3] * shape[1]))
        cv2.rectangle(img, p1[::-1], p2[::-1], color, thickness)
        # Draw text...
        s = '%s/%.3f' % (l_VOC_CLASS[int(classes[i]) - 1], scores[i])
        p1 = (p1[0] - 5, p1[1])
        # cv2.putText(img, s, p1[::-1], cv2.FONT_HERSHEY_DUPLEX, 1.5, color, 3)


colors_plasma = colors_subselect(mpcm.plasma.colors, num_classes=21)


# 主流程函數
def process_image(img, case, select_threshold=0.15, nms_threshold=.1, net_shape=(300, 300)):
    # select_threshold:box閾值——每個像素的box分類預測數據的得分會與box閾值比較,高於一個box閾值則認爲這個box成功框到了一個對象
    # nms_threshold:重合度閾值——同一對象的兩個框的重合度高於該閾值,則運行下面去重函數

    # 執行SSD模型,得到4維輸入變量,分類預測,座標預測,rbbox_img參數爲最大檢測範圍,本文固定爲[0,0,1,1]即全圖
    rimg, rpredictions, rlocalisations, rbbox_img = isess.run([image_4d, predictions,
                                                               localisations, bbox_img], feed_dict={img_input: img})

    # ssd_bboxes_select()函數根據每個特徵層的分類預測分數,歸一化後的映射座標,
    # ancohor_box的大小,通過設定一個閾值計算得到每個特徵層檢測到的對象以及其分類和座標
    rclasses, rscores, rbboxes = np_methods.ssd_bboxes_select(rpredictions, rlocalisations, ssd_anchors,
                                                              select_threshold=select_threshold,
                                                              img_shape=net_shape,
                                                              num_classes=21, decode=True)

    """
    這個函數做的事情比較多,這裏說的細緻一些:
    首先是輸入,輸入的數據爲每個特徵層(一共6個,見上文)的:
                                                rpredictions: 分類預測數據,
                                                rlocalisations: 座標預測數據,
                                                ssd_anchors: anchors_box數據
                                            其中:
                                               分類預測數據爲當前特徵層中每個像素的每個box的分類預測
                                               座標預測數據爲當前特徵層中每個像素的每個box的座標預測
                                               anchors_box數據爲當前特徵層中每個像素的每個box的修正數據

        函數根據座標預測數據和anchors_box數據,計算得到每個像素的每個box的中心和長寬,這個中心座標和長寬會根據一個算法進行些許的修正,
    從而得到一個更加準確的box座標;修正的算法會在後文中詳細解釋,如果只是爲了理解算法流程也可以不必深究這個,因爲這個修正算法屬於經驗算
    法,並沒有太多邏輯可循。
        修正完box和中心後,函數會計算每個像素的每個box的分類預測數據的得分,當這個分數高於一個閾值(這裏是0.5)則認爲這個box成功
    框到了一個對象,然後將這個box的座標數據,所屬分類和分類得分導出,從而得到:
        rclasses:所屬分類
        rscores:分類得分
        rbboxes:座標

        最後要注意的是,同一個目標可能會在不同的特徵層都被檢測到,並且他們的box座標會有些許不同,這裏並沒有去掉重複的目標,而是在下文
    中專門用了一個函數來去重
    """

    # 檢測有沒有超出檢測邊緣
    rbboxes = np_methods.bboxes_clip(rbbox_img, rbboxes)
    rclasses, rscores, rbboxes = np_methods.bboxes_sort(rclasses, rscores, rbboxes, top_k=400)
    # 去重,將重複檢測到的目標去掉
    rclasses, rscores, rbboxes = np_methods.bboxes_nms(rclasses, rscores, rbboxes, nms_threshold=nms_threshold)
    # 將box的座標重新映射到原圖上(上文所有的座標都進行了歸一化,所以要逆操作一次)
    rbboxes = np_methods.bboxes_resize(rbbox_img, rbboxes)

    if case == 1:
        bboxes_draw_on_img(img, rclasses, rscores, rbboxes, colors_plasma, thickness=8)
        return img
    else:
        return rclasses, rscores, rbboxes


"""
# 只做目標定位,不做預測分析
case = 1
img = cv2.imread("../demo/person.jpg")
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
plt.imshow(process_image(img, case))
plt.show()
"""
# 做目標定位,同時做預測分析
case = 2
path = '../demo/person.jpg'
# 讀取圖片
img = mpimg.imread(path)
# 執行主流程函數
rclasses, rscores, rbboxes = process_image(img, case)
# visualization.bboxes_draw_on_img(img, rclasses, rscores, rbboxes, visualization.colors_plasma)
# 顯示分類結果圖
visualization.plt_bboxes(img, rclasses, rscores, rbboxes), rscores, rbboxes

3、SSD目標檢測結果

會得到如下圖示,如圖已經成功的把物體標註出來,每個標記框中前一個數是標籤項,後一個是預測的準確率;

# 標籤項與其對應的標籤內容
dict = {1:'aeroplane',    2:'bicycle', 3:'bird',   4:'boat',       5:'bottle',
        6:'bus',          7:'car',     8:'cat',    9:'chair',      10:'cow',
        11:'diningTable', 12:'dog',    13:'horse', 14:'motorbike', 15:'person',
        16:'pottedPlant', 17:'sheep',  18:'sofa',  19:'train',     20:'TV'}

這裏寫圖片描述

-------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------

二、SSD用於視頻內物體的定位

以上demo文件夾內都只是圖片,如果你想在視頻中標記物體——首先你需要拍一段視頻,建議不要太長不然你要跑很久,然後需要在主目錄下建立Video文件夾,在其下建立inputoutput文件夾,如下圖所示:
這裏寫圖片描述

再將拍攝的視頻存入input文件夾下,注意視頻的名稱哦!最後在主目錄下建立demo_Video.py文件,存入如下代碼,運行demo_Video.py

請注意:安裝imageio:pip install imageio==2.4.1

# -*- coding:utf-8 -*-
# -*- author:zzZ_CMing  CSDN address:https://blog.csdn.net/zzZ_CMing
# -*- 2018/07/09; 15:19
# -*- python3.6
import os
import cv2
import math
import random
import tensorflow as tf
import matplotlib.pyplot as plt
import matplotlib.cm as mpcm
import matplotlib.image as mpimg
from notebooks import visualization
from nets import ssd_vgg_300, ssd_common, np_methods
from preprocessing import ssd_vgg_preprocessing
import sys

# 當引用模塊和運行的腳本不在同一個目錄下,需在腳本開頭添加如下代碼:
sys.path.append('./SSD-Tensorflow/')

slim = tf.contrib.slim

# TensorFlow session
gpu_options = tf.GPUOptions(allow_growth=True)
config = tf.ConfigProto(log_device_placement=False, gpu_options=gpu_options)
isess = tf.InteractiveSession(config=config)

l_VOC_CLASS = ['aeroplane', 'bicycle', 'bird', 'boat', 'bottle',
               'bus', 'car', 'cat', 'chair', 'cow',
               'diningTable', 'dog', 'horse', 'motorbike', 'person',
               'pottedPlant', 'sheep', 'sofa', 'train', 'TV']

# 定義數據格式,設置佔位符
net_shape = (300, 300)
# 預處理,以Tensorflow backend, 將輸入圖片大小改成 300x300,作爲下一步輸入
img_input = tf.placeholder(tf.uint8, shape=(None, None, 3))
# 輸入圖像的通道排列形式,'NHWC'表示 [batch_size,height,width,channel]
data_format = 'NHWC'

# 數據預處理,將img_input輸入的圖像resize爲300大小,labels_pre,bboxes_pre,bbox_img待解析
image_pre, labels_pre, bboxes_pre, bbox_img = ssd_vgg_preprocessing.preprocess_for_eval(
    img_input, None, None, net_shape, data_format,
    resize=ssd_vgg_preprocessing.Resize.WARP_RESIZE)
# 拓展爲4維變量用於輸入
image_4d = tf.expand_dims(image_pre, 0)

# 定義SSD模型
# 是否複用,目前我們沒有在訓練所以爲None
reuse = True if 'ssd_net' in locals() else None
# 調出基於VGG神經網絡的SSD模型對象,注意這是一個自定義類對象
ssd_net = ssd_vgg_300.SSDNet()
# 得到預測類和預測座標的Tensor對象,這兩個就是神經網絡模型的計算流程
with slim.arg_scope(ssd_net.arg_scope(data_format=data_format)):
    predictions, localisations, _, _ = ssd_net.net(image_4d, is_training=False, reuse=reuse)

# 導入官方給出的 SSD 模型參數
ckpt_filename = 'checkpoints/ssd_300_vgg.ckpt'
# ckpt_filename = '../checkpoints/VGG_VOC0712_SSD_300x300_ft_iter_120000.ckpt'
isess.run(tf.global_variables_initializer())
saver = tf.train.Saver()
saver.restore(isess, ckpt_filename)

# 在網絡模型結構中,提取搜索網格的位置
# 根據模型超參數,得到每個特徵層(這裏用了6個特徵層,分別是4,7,8,9,10,11)的anchors_boxes
ssd_anchors = ssd_net.anchors(net_shape)
"""
每層的anchors_boxes包含4個arrayList,前兩個List分別是該特徵層下x,y座標軸對於原圖(300x300)大小的映射
第三,四個List爲anchor_box的長度和寬度,同樣是經過歸一化映射的,根據每個特徵層box數量的不同,這兩個List元素
個數會變化。其中,長寬的值根據超參數anchor_sizes和anchor_ratios制定。
"""


# 加載輔助作圖函數
def colors_subselect(colors, num_classes=21):
    dt = len(colors) // num_classes
    sub_colors = []
    for i in range(num_classes):
        color = colors[i * dt]
        if isinstance(color[0], float):
            sub_colors.append([int(c * 255) for c in color])
        else:
            sub_colors.append([c for c in color])
    return sub_colors


def bboxes_draw_on_img(img, classes, scores, bboxes, colors, thickness=2):
    shape = img.shape
    for i in range(bboxes.shape[0]):
        bbox = bboxes[i]
        color = colors[classes[i]]
        # Draw bounding box...
        p1 = (int(bbox[0] * shape[0]), int(bbox[1] * shape[1]))
        p2 = (int(bbox[2] * shape[0]), int(bbox[3] * shape[1]))
        cv2.rectangle(img, p1[::-1], p2[::-1], color, thickness)
        # Draw text...
        s = '%s/%.3f' % (l_VOC_CLASS[int(classes[i]) - 1], scores[i])
        p1 = (p1[0] - 5, p1[1])
        # cv2.putText(img, s, p1[::-1], cv2.FONT_HERSHEY_DUPLEX, 1.5, color, 3)


colors_plasma = colors_subselect(mpcm.plasma.colors, num_classes=21)


# 主流程函數
def process_image(img, select_threshold=0.4, nms_threshold=.1, net_shape=(300, 300)):
    # select_threshold:box閾值——每個像素的box分類預測數據的得分會與box閾值比較,高於一個box閾值則認爲這個box成功框到了一個對象
    # nms_threshold:重合度閾值——同一對象的兩個框的重合度高於該閾值,則運行下面去重函數

    # 執行SSD模型,得到4維輸入變量,分類預測,座標預測,rbbox_img參數爲最大檢測範圍,本文固定爲[0,0,1,1]即全圖
    rimg, rpredictions, rlocalisations, rbbox_img = isess.run([image_4d, predictions, localisations, bbox_img],
                                                              feed_dict={img_input: img})

    # ssd_bboxes_select函數根據每個特徵層的分類預測分數,歸一化後的映射座標,
    # ancohor_box的大小,通過設定一個閾值計算得到每個特徵層檢測到的對象以及其分類和座標
    rclasses, rscores, rbboxes = np_methods.ssd_bboxes_select(rpredictions, rlocalisations, ssd_anchors,
                                                              select_threshold=select_threshold,
                                                              img_shape=net_shape,
                                                              num_classes=21, decode=True)

    """
    這個函數做的事情比較多,這裏說的細緻一些:
    首先是輸入,輸入的數據爲每個特徵層(一共6個,見上文)的:
                                                分類預測數據(rpredictions),
                                                座標預測數據(rlocalisations),
                                                anchors_box數據(ssd_anchors)
                                            其中:
                                               分類預測數據爲當前特徵層中每個像素的每個box的分類預測
                                               座標預測數據爲當前特徵層中每個像素的每個box的座標預測
                                               anchors_box數據爲當前特徵層中每個像素的每個box的修正數據

        函數根據座標預測數據和anchors_box數據,計算得到每個像素的每個box的中心和長寬,這個中心座標和長寬會根據一個算法進行些許的修正,
    從而得到一個更加準確的box座標;修正的算法會在後文中詳細解釋,如果只是爲了理解算法流程也可以不必深究這個,因爲這個修正算法屬於經驗算
    法,並沒有太多邏輯可循。
        修正完box和中心後,函數會計算每個像素的每個box的分類預測數據的得分,當這個分數高於一個閾值(這裏是0.5)則認爲這個box成功
    框到了一個對象,然後將這個box的座標數據,所屬分類和分類得分導出,從而得到:
        rclasses:所屬分類
        rscores:分類得分
        rbboxes:座標

        最後要注意的是,同一個目標可能會在不同的特徵層都被檢測到,並且他們的box座標會有些許不同,這裏並沒有去掉重複的目標,而是在下文
    中專門用了一個函數來去重
    """

    # 檢測有沒有超出檢測邊緣
    rbboxes = np_methods.bboxes_clip(rbbox_img, rbboxes)
    rclasses, rscores, rbboxes = np_methods.bboxes_sort(rclasses, rscores, rbboxes, top_k=400)
    # 去重,將重複檢測到的目標去掉
    rclasses, rscores, rbboxes = np_methods.bboxes_nms(rclasses, rscores, rbboxes, nms_threshold=nms_threshold)
    # 將box的座標重新映射到原圖上(上文所有的座標都進行了歸一化,所以要逆操作一次)
    rbboxes = np_methods.bboxes_resize(rbbox_img, rbboxes)

    bboxes_draw_on_img(img, rclasses, rscores, rbboxes, colors_plasma, thickness=8)
    return img


# 視頻物體定位
import imageio        # pip install imageio==2.4.1
imageio.plugins.ffmpeg.download()
from moviepy.editor import VideoFileClip


def get_process_video(input_path, output_path):
    video = VideoFileClip(input_path)
    result = video.fl_image(process_image)
    result.write_videofile(output_path, fps=25)



try:
    os.makedirs("Video/input/")
    os.makedirs("Video/output/")
except:
    input_folder = "Video/input/"
    input_video_name = sorted(os.listdir(input_folder))[-1]
    input_video_path = input_folder + input_video_name
    output_video_path = "Video/output/output_" + input_video_name

    get_process_video(input_video_path, output_video_path)

經過一段時間的等待,終於跑完程序;
這裏寫圖片描述
打開Video/input文件夾,查看輸出的視頻是什麼樣子的吧!

由於博客裏只能上傳gif圖,雖然原圖畫質很好,但到了這裏怎麼變成這樣?我能怎麼辦——我也很絕望
但是依稀可以看到裏面的汽車、行人、自行車都被不同的顏色框標註起來了,說明我們是成功的!趕快動手試一試,很好玩的

這裏寫圖片描述

-------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------

後續:

SSD目標檢測(2):如何製作自己的數據集(詳細說明附源碼)
SSD目標檢測(3):使用自己的數據集做預測(詳細說明附源碼)
這兩篇是SSD框架的實際運用——因爲SSD框架只有20類物體被標註,也就是說只能用來識別這特定的20種物體,而實際需求會隨着環境有很大的改變,所以這兩篇就是教大家如何利用SSD框架,製作自己的數據集,並通過自己的數據集標註定位新物體,這個拓展練習有趣且實用,也是模式識別常見的問題研究,大家趕快動手試一試吧。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章