1.參考等。
需要在跟蹤模型的前面把檢測模型加進去,傳統(tǒng)使用一些背景建模和軌跡建模的方式來做,對于動攝像頭以及復雜背景的適應性都比較差,所以考慮用深度學習的方法來做。我以前也只是大概看過這些東西,具體動手只做過分類,并沒有搞過檢測,所以找到一篇SSD訓練自己數(shù)據(jù)的參考,自己也來實現(xiàn)一下。
參考:SSD目標檢測
SSD的原理介紹可以參見:SSD原理介紹
2.環(huán)境準備。
剛重裝了系統(tǒng),所以都得重來一下,python+tensorflow這個直接下載anaconda來裝就可以了,opencv去網(wǎng)上下載whl文件,然后安裝(anaconda其實也有opencv,但是不知道為什么下載不下來就放棄了),裝好之后提示numpy版本不匹配,所以升級了numpy的版本,又提示tensorflow不支持,反正就是版本的問題,然后只能折中選擇了一個numpy的版本。環(huán)境配置有時候挺麻煩的,多看論壇就行了。
3.下載SSD框架源碼。
選擇的是tensorflow版本的:SSD_tensorflow
checkpoints文件夾下的壓縮包解壓,pycharm新建項目后應該張這個樣子:

ckpt這種文件就是訓練好的模型參數(shù)。
demo文件夾下是用來測試的文件夾。
4.測試。
# -*- coding:utf-8 -*-
# -*- author:zzZ_CMing CSDN address:https://blog.csdn.net/zzZ_CMing
# -*- 2018/07/14; 15:19
# -*- python3.5
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=False)
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']
# 定義數(shù)據(jù)格式,設置占位符
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'
# 數(shù)據(jù)預處理,將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
# 調(diào)出基于VGG神經(jīng)網(wǎng)絡的SSD模型對象,注意這是一個自定義類對象
ssd_net = ssd_vgg_300.SSDNet()
# 得到預測類和預測坐標的Tensor對象,這兩個就是神經(jīng)網(wǎng)絡模型的計算流程
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 模型參數(shù)
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)
# 在網(wǎng)絡模型結(jié)構(gòu)中,提取搜索網(wǎng)格的位置
# 根據(jù)模型超參數(shù),得到每個特征層(這里用了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的長度和寬度,同樣是經(jīng)過歸一化映射的,根據(jù)每個特征層box數(shù)量的不同,這兩個List元素
個數(shù)會變化。其中,長寬的值根據(jù)超參數(shù)anchor_sizes和anchor_ratios制定。
"""
# 加載輔助作圖函數(shù)
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)
# 主流程函數(shù)
def process_image(img, case, select_threshold=0.15, nms_threshold=.1, net_shape=(300, 300)):
# select_threshold:box閾值——每個像素的box分類預測數(shù)據(jù)的得分會與box閾值比較,高于一個box閾值則認為這個box成功框到了一個對象
# nms_threshold:重合度閾值——同一對象的兩個框的重合度高于該閾值,則運行下面去重函數(shù)
# 執(zhí)行SSD模型,得到4維輸入變量,分類預測,坐標預測,rbbox_img參數(shù)為最大檢測范圍,本文固定為[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()函數(shù)根據(jù)每個特征層的分類預測分數(shù),歸一化后的映射坐標,
# 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)
"""
這個函數(shù)做的事情比較多,這里說的細致一些:
首先是輸入,輸入的數(shù)據(jù)為每個特征層(一共6個,見上文)的:
rpredictions: 分類預測數(shù)據(jù),
rlocalisations: 坐標預測數(shù)據(jù),
ssd_anchors: anchors_box數(shù)據(jù)
其中:
分類預測數(shù)據(jù)為當前特征層中每個像素的每個box的分類預測
坐標預測數(shù)據(jù)為當前特征層中每個像素的每個box的坐標預測
anchors_box數(shù)據(jù)為當前特征層中每個像素的每個box的修正數(shù)據(jù)
函數(shù)根據(jù)坐標預測數(shù)據(jù)和anchors_box數(shù)據(jù),計算得到每個像素的每個box的中心和長寬,這個中心坐標和長寬會根據(jù)一個算法進行些許的修正,
從而得到一個更加準確的box坐標;修正的算法會在后文中詳細解釋,如果只是為了理解算法流程也可以不必深究這個,因為這個修正算法屬于經(jīng)驗算
法,并沒有太多邏輯可循。
修正完box和中心后,函數(shù)會計算每個像素的每個box的分類預測數(shù)據(jù)的得分,當這個分數(shù)高于一個閾值(這里是0.5)則認為這個box成功
框到了一個對象,然后將這個box的坐標數(shù)據(jù),所屬分類和分類得分導出,從而得到:
rclasses:所屬分類
rscores:分類得分
rbboxes:坐標
最后要注意的是,同一個目標可能會在不同的特征層都被檢測到,并且他們的box坐標會有些許不同,這里并沒有去掉重復的目標,而是在下文
中專門用了一個函數(shù)來去重
"""
# 檢測有沒有超出檢測邊緣
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 = 2
path = '../demo/desk.jpg'
# 讀取圖片
img = mpimg.imread(path)
# 執(zhí)行主流程函數(shù)
rclasses, rscores, rbboxes = process_image(img, case)
#isualization.bboxes_draw_on_img(img, rclasses, rscores, rbboxes, visualization.colors_plasma)
# 顯示分類結(jié)果圖
visualization.plt_bboxes(img, rclasses, rscores, rbboxes,figsize=(10,10),linewidth=2)
代碼來源看注釋,反正不是我寫的,需要根據(jù)自己需要改的主要有:
①gpu_options = tf.GPUOptions(allow_growth=False)根據(jù)自己是否有GPU來設置,我一開始在筆記本上跑的,所以false。
②path = '../demo/desk.jpg'這里改成自己測試的圖片。
③def process_image(img, case, select_threshold=0.5, nms_threshold=.1, net_shape=(300, 300)):這句默認的閾值可以選,意思就是只顯示置信概率大于0.5的對象。
然后跑起來就可以了,可能加載模型比較慢吧,所以第一次時間還挺長的,自己隨便找了一張照片,結(jié)果如下:

看起來還不錯的樣子,標簽對應的類別名稱代碼里有。
另外,圖片可以的話,視頻測試也是可以的,把視頻讀進來轉(zhuǎn)換為圖片寫循環(huán)就可以了。
接下來需要采集數(shù)據(jù),加標簽以及訓練模型了,估計會要花一段時間。