1. 程式人生 > >計算機視覺(三):目標檢測與識別

計算機視覺(三):目標檢測與識別

1 - 引言

目標檢測和識別,是計算機視覺最常見的挑戰之一。

目標檢測和識別的區別在於:目標檢測是用來確定影象的某個區域是否含有要識別的物件,而識別是程式識別物件的能力。識別通常只處理已檢測到物件的區域。

在計算機視覺中有很多目標檢測和識別的技術

  • 梯度直方圖(Histogram of Oriented Gradient, HOG)
  • 影象金字塔(image pyramid)
  • 滑動視窗(sliding window)

與特徵檢測演算法不同,這些演算法是互補的,比如在HOG中也會使用滑動視窗技術,下面我們來介紹一下這些技術

2 - 目標檢測與識別技術

2.1 - 基本概念與術語介紹

要實現目標檢測和識別,我們先要了解這方面的一些技術手段和術語

  • 梯度直方圖(Histogram of Oriented Gradient, HOG)

HOG是一個特徵描述符,因此HOG與SIFT、SURF、ORB屬於同一型別的描述符

HOG不是基於顏色值而是基於梯度來計算直方圖的。但是這種特徵會受到兩個方面的影響

  1. 尺度問題
  2. 位置問題

為了解決這些問題,需要使用影象金字塔和滑動視窗

  • 影象金字塔
    影象金字塔是影象的多尺度表示。

在這裡插入圖片描述

  • 滑動視窗
    通過一個滑動視窗掃描較大影象的較小區域來解決定位問題,進而在同一影象的不同尺度下重複掃描。
    但是滑動視窗會遇到一個問題,區域重疊,比如在對人臉檢測的時候可能對同一張人臉的四個不同位置進行匹配,但是我們只需要一個結果,所以我們需要使用非最大抑制來確定一個評價最高的圖片區域。

  • 支援向量機(SVM)

SVM的最優超平面是目標檢測的重要組成部分,用來區分哪些畫素是目標,哪些畫素不是目標

再瞭解一些基本概念後,我們可以試著搭建一些簡單的檢測程式

2.2 - 檢測人示例

OpenCV自帶的HOGDescriptor函式可檢測人

import cv2
import numpy as np

def is_inside(o,i):
    ox,oy,ow,oh = o
    ix,iy,iw,ih = i
    return ox > ix and oy > iy and ox + ow < ix + iw and oy + oh < iy +ih

def draw_person(image, person):
    x,y,w,h = person
    cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,255,2))

img = cv2.imread('images/timg (2).jpg')
hog = cv2.HOGDescriptor()
hog.setSVMDetector(cv2.HOGDescriptor_getDefaultPeopleDetector())
found, w = hog.detectMultiScale(img)

found_filtered = []
for ri, r in enumerate(found):
    for qi, q in enumerate(found):
        if ri != qi and is_inside(r,q):
            break
        else:
            found_filtered.append(r)
for person in found_filtered:
    draw_person(img,person)

cv2.imshow("people detection",img)
cv2.waitKey(0)

在這裡插入圖片描述

3 - 建立和訓練目標檢測器

雖然使用OpenCV內建的函式可以很容易得到應用的簡易模型,但是僅僅使用內建的函式是遠遠不夠的,我們需要改進這些特徵,搞明白這些檢測器能否應用與其他目標。

我們可以使用詞袋(Bag-of-Word,BOW)技術

3.1 - 詞袋

詞袋(BOW)最早並不是針對計算機視覺的,而是用於語言分析和資訊檢索領域,BOW用來在一系列文件中計算每個詞出現的次數,用這些次數構成向量來重新表示文件。

而在計算機視覺中,BOW方法的實現步驟如下:

  1. 取一個樣本資料集
  2. 對資料集中的每幅影象提取描述符(採用SIFT,SURT等方法)
  3. 將每一個描述符都新增到BOW訓練器中
  4. 使用K-means聚類將描述符聚類到K簇中

3.2 - 汽車檢測

  1. 先下載資料集
  2. 實現程式碼:
import cv2
import numpy as np
from os.path import join


# 獲取不同類別影象的路徑
def path(cls, i):
    return "%s/%s%d.pgm" % (datapath, cls, i + 1)


# 以灰度格式讀取影象,並從影象中提取SIFT特徵,然後返回描述符
def extract_sift(fn):
    im = cv2.imread(fn, 0)
    return extract.compute(im, detect.detect(im))[1]


# 宣告訓練影象的基礎路徑,最新下載地址:
# http://cogcomp.org/Data/Car/CarData.tar.gz
datapath = "./data/at/CarData/TrainImages/"

# 檢視下載的資料素材,發現汽車資料集中影象按:pos-x.pgm 和 neg-x.pgm 命名,其中x是一個數字。
# 這樣從path讀取影象時,只需傳遞變數i的值即可
pos, neg = "pos-", "neg-"

# 建立兩個SIFT例項,一個提取關鍵點,一個提取特徵;
detect = cv2.xfeatures2d.SIFT_create()
extract = cv2.xfeatures2d.SIFT_create()

# 建立基於FLANN匹配器例項
flann_params = dict(algorithm=1, trees=5)
flann = cv2.FlannBasedMatcher(flann_params, {})

# 建立BOW訓練器,指定簇數為40
bow_kmeans_trainer = cv2.BOWKMeansTrainer(40)

# 初始化BOW提取器,視覺詞彙將作為BOW類輸入,在測試影象中會檢測這些視覺詞彙
extract_bow = cv2.BOWImgDescriptorExtractor(extract, flann)

# 從每個類別中讀取8個正樣本和8個負樣本,並增加到訓練集的描述符
for i in range(8):
    bow_kmeans_trainer.add(extract_sift(path(pos, i)))
    bow_kmeans_trainer.add(extract_sift(path(neg, i)))

# cluster()函式執行k-means分類並返回詞彙
# 併為BOWImgDescriptorExtractor指定返回的詞彙,以便能從測試影象中提取描述符
voc = bow_kmeans_trainer.cluster()
extract_bow.setVocabulary(voc)


# 返回基於BOW描述符提取器計算得到的描述符
def bow_features(fn):
    im = cv2.imread(fn, 0)
    return extract_bow.compute(im, detect.detect(im))


# 建立兩個陣列,分別存放訓練資料和標籤
# 呼叫BOWImgDescriptorExtractor產生的描述符填充兩個陣列,生成正負樣本影象的標籤
traindata, trainlabels = [], []
for i in range(20):
    traindata.extend(bow_features(path(pos, i)))
    trainlabels.append(1)  # 1表示正匹配

    traindata.extend(bow_features(path(neg, i)))
    trainlabels.append(-1)  # -1表示負匹配

# 建立一個svm例項
svm = cv2.ml.SVM_create()

# 通過將訓練資料和標籤放到NumPy陣列中來進行訓練
svm.train(np.array(traindata), cv2.ml.ROW_SAMPLE, np.array(trainlabels))

'''
以上設定都是用於訓練好的SVM,剩下要做的是給SVM一些樣本影象
'''


# 顯示predict方法結果,並返回結果資訊
def predict(fn):
    f = bow_features(fn)
    p = svm.predict(f)
    print(fn, "\t", p[1][0][0])
    return p


# 定義兩個樣本影象的路徑,並讀取樣本影象資訊
car, notcar = "./images/car.jpg", "./images/bb.jpg"
car_img = cv2.imread(car)
notcar_img = cv2.imread(notcar)

# 將影象傳給已經訓練好的SVM,並獲取檢測結果
car_predict = predict(car)
not_car_predict = predict(notcar)

# 以下用於螢幕上顯示識別的結果和影象
font = cv2.FONT_HERSHEY_SIMPLEX

if (car_predict[1][0][0] == 1.0):
    cv2.putText(car_img, 'Car Detected', (10, 30), font, 1, (0, 255, 0), 2, cv2.LINE_AA)

if (not_car_predict[1][0][0] == -1.0):
    cv2.putText(notcar_img, 'Car Not Detected', (10, 30), font, 1, (0, 0, 255), 2, cv2.LINE_AA)

cv2.imshow('BOW + SVM Success', car_img)
cv2.imshow('BOW + SVM Failure', notcar_img)
cv2.waitKey(0)
cv2.destroyAllWindows()

在這裡插入圖片描述

現在我們完成了汽車的目標檢測,但是我們希望可以增加以下功能:

  • 檢測影象中同一物體的多個目標
  • 確定檢測到的目標在影象中的位置

3.3 - 多目標汽車和位置檢測

步驟如下:

  1. 獲取一個訓練集資料
  2. 建立BOW訓練器並獲得視覺詞彙
  3. 採用詞彙訓練SVM
  4. 嘗試對測試影象的金字塔採用滑動視窗進行檢測
  5. 對重疊的矩形使用非最大抑制
  6. 輸出結果
    該專案的結構如下:
    在這裡插入圖片描述
    下面給出例程:
    首先下載專案檔案

實際應用中,只需修改car_sliding_windows.py檔案即可,附內容:

import cv2
import numpy as np
from car_detector.detector import car_detector, bow_features
from car_detector.pyramid import pyramid
from car_detector.non_maximum import non_max_suppression_fast as nms
from car_detector.sliding_window import sliding_window


def in_range(number, test, thresh=0.2):
    return abs(number - test) < thresh


test_image = "./images/car3.jpg"

svm, extractor = car_detector()
detect = cv2.xfeatures2d.SIFT_create()

w, h = 150, 80
img = cv2.imread(test_image)

rectangles = []
scaleFactor = 1.25

for resized in pyramid(img, scaleFactor):

    scale = float(img.shape[1]) / float(resized.shape[1])

    for (x, y, roi) in sliding_window(resized, 20, (w, h)):

        if roi.shape[1] != w or roi.shape[0] != h:
            continue

        try:
            bf = bow_features(roi, extractor, detect)
            _, result = svm.predict(bf)
            a, res = svm.predict(bf, flags=cv2.ml.STAT_MODEL_RAW_OUTPUT | cv2.ml.STAT_MODEL_UPDATE_MODEL)

            # 所有小於-1.0的視窗被視為一個好的結果
            if result[0][0] == 1 and res[0][0] < -1.0:
                print("Class: %d, Score: %f, a: %s" % (result[0][0], res[0][0], res))

                # 提取感興趣區域(ROI)的特徵,它與滑動視窗相對應
                rx, ry, rx2, ry2 = int(x * scale), int(y * scale), int((x + w) * scale), int((y + h) * scale)
                rectangles.append([rx, ry, rx2, ry2, abs(res[0][0])])
        except:
            pass

# 將矩形陣列轉換為NumPy陣列
windows = np.array(rectangles)

# 非最大抑制nms,按打分最高到最低排序
boxes = nms(windows, 0.25)

# 列印檢測結果
for (x, y, x2, y2, score) in boxes:
    print(x, y, x2, y2, score)
    cv2.rectangle(img, (int(x), int(y)), (int(x2), int(y2)), (0, 255, 0), 1)
    cv2.putText(img, "%f" % score, (int(x), int(y)), cv2.FONT_HERSHEY_PLAIN, 1, (0, 255, 0))

cv2.imshow("img", img)
cv2.waitKey(0)
cv2.destroyAllWindows()

在這裡插入圖片描述

對於支援向量機(SVM)來說,不需要每次都訓練檢測器,可以儲存訓練結果到檔案,以後需要用到時,使用load函式直接載入即可。

svm.save('/svmxml')

感覺OpenCV提供的例程效果不是很好,要想在實際中完成比較好的效果還是需要耐心的修改優化程式

4 - 總結

我們介紹了許多的概念,HOG、BOW、SVM、金字塔、滑動視窗和非最大抑制,內容還是很多的,需要一點時間來吸收