1. 程式人生 > >Python 進行目標檢測

Python 進行目標檢測

一、前言

  從學微控制器開始鼓搗C語言,到現在為了學CV鼓搗Python,期間在CSDN、簡書、部落格園和github這些地方得到了很多幫助,所以也想把自己做的一些小東西分享給大家,希望能幫助到別人。記錄人生的第一篇部落格,mark。

二、影象檢測步驟

1. 讀取兩張圖片

  第一張是需要檢測的小物體,第二章圖片是小物體放置在大場景中。程式碼與輸出結果如下所示:

import numpy as np
import matplotlib.pyplot as plt
import cv2

def my_show(img):
    plt.imshow(cv2.cvtColor(img,cv2.COLOR_BGR2RGB))
    return 

# 讀取圖片
img_small=cv2.imread('small.jpg',1)
img_big=cv2.imread('big.jpg',1)

# 顯示圖片
plt.figure(figsize=(10,10))
plt.subplot(121)
my_show(img_small)
plt.subplot(122)
my_show(img_big)

 

2. 提取圖片中的特徵點

  這一步就像我們在區分不同的人的時候,一眼看到外貌就知道此人是誰,而外貌就是這個人的特徵。我們希望提取該物體的特徵點,以便在不同的場景中識別出來。圖片是由畫素點構成,但是畫素點包含的資訊太零散了,一般是識別物體的邊緣或者角點作為特徵資訊。常用的特徵描述演算法如下:

  SIFT:https://blog.csdn.net/zddblog/article/details/7521424

  harris corner detection: https://www.jianshu.com/p/efc81fdb8afb

  Hog: https://lear.inrialpes.fr/people/triggs/pubs/Dalal-cvpr05.pdf

  SURF: https://www.vision.ee.ethz.ch/~surf/eccv06.pdf

  BRISK: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.371.1343&rep=rep1&type=pdf

  Orb: http://www.willowgarage.com/sites/default/files/orb_final.pdf

  我們專案中用到的是SIFT演算法,SIFT精確度高,對尺度、亮度以及旋轉的魯棒性強,不過計算時間長,所需的計算資源較多。SURF是SIFT的加速版本,有興趣的小夥伴可以瞭解一下。SIFT演算法執行後,可以得到的特徵點的位置以及特徵向量。(PS:SIFT演算法申請了專利,所以在opencv3.4.2版本後不能使用了,需要用SIFT的請安裝3.4.2的opencv)

import numpy as np
import matplotlib.pyplot as plt
import cv2

def my_show(img):
    plt.imshow(cv2.cvtColor(img,cv2.COLOR_BGR2RGB))
    return 

# 讀取圖片
img_small=cv2.imread('small.jpg',1)
img_big=cv2.imread('big.jpg',1)

# 提取特徵點
sift=cv2.xfeatures2d.SIFT_create()
kp1,des1=sift.detectAndCompute(img_small,None)
kp2,des2=sift.detectAndCompute(img_big,None)

# 在圖中顯示特徵點
img_small_sift=cv2.drawKeypoints(img_small,kp1,outImage=np.array([]),flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
img_big_sift=cv2.drawKeypoints(img_big,kp2,outImage=np.array([]),flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)

plt.figure(figsize=(15,15))
plt.subplot(121)
my_show(img_small_sift)
plt.subplot(122)
my_show(img_big_sift)

 

3.特徵點匹配

  在分別提取了兩張圖的特徵點後,就需要進行特徵點匹配啦。這裡需要注意的是,兩張圖的特徵點數量一般情況下是不一樣的,opencv演算法裡預設用第一張圖的特徵點來匹配,所以匹配矩陣的行數與第一張圖特徵點的行數一致。常用的匹配方法:

  Brute-Force: 暴力搜尋,用圖1的特徵點逐一與圖二的特徵點比較,找到歐式距離最小的點作為匹配點。不過這樣匹配的錯誤點就會很多,所以常用交叉匹配法消除錯誤匹配。交叉匹配法指的是,反過來匹配一次,用匹配到圖2的點反過來匹配圖1的點,如果匹配結果與原來相同,則認為是正確匹配。

  KNN: K近鄰匹配,在匹配的時候選擇K個和特徵點最相似的點,如果這K個點之間的區別足夠大,則選擇最相似的那個點作為匹配點,通常選擇K = 2。KNN匹配也會出現一些誤匹配,這時候需要對比第一鄰近與第二鄰近之間的距離大小,假如 distance_1< (0.5~0.7)*distance_2, 則認為是正確匹配。

  FLANN: FLANN是快速最近鄰搜尋包(Fast Library for Approximate Nearest Neighbors)的簡稱,是最近鄰搜尋的演算法的集合。

  一般我們會選擇呼叫cv2.Brute-Force或者cv2.FlannBasedMatcher來進行特徵點匹配,FLANN裡邊就包含的KNN、KD樹還有其他的最近鄰演算法。

 

4.計算單應性矩陣

  這裡我們需要在大場景中用矩形框出匹配的小物體,所以就要計算單應性矩陣,然後做投影變換。RANSAC(Random Sample Consensus)隨機抽樣一致性演算法是計算單應性矩陣的有效方法,並且在尋找單應性矩陣的過程中可以進一步剔除錯誤匹配點。RANSAC演算法的步驟如下:

  •  隨機抽取四個匹配點對計算投影矩陣,需要檢驗四個點是否共線
  •  圖片1特徵點座標齊次變換後(3,1)乘上投影矩陣(3,3),然後計算變換後的特徵點座標與圖片2特徵點座標的歐氏距離,小於設定的閾值則記錄為內點,反之則為內點
  •  判斷此次內點的數量是否比以往記錄的內點最大值多,如果是,則更新投影矩陣,如果不是,則不更新
  •  判定迴圈次數是夠達到設定次數或內點數佔全部匹配點的比例達到設定比例,則跳出迴圈,否則跳到步驟1繼續迴圈

  尋找到最優的單應性矩陣後,將框住物體一的矩陣經投影變換後在圖片二上畫出來,完成目標檢測框選。以下程式碼摘自Brook@CV的部落格並修改,如有侵權,請通知刪除

import numpy as np
import cv2
from matplotlib import pyplot as plt

MIN_MATCH_COUNT = 10

img1 = cv2.imread('small.jpg',1)
img2 = cv2.imread('big.jpg',1)

# 使用SIFT檢測角點
sift = cv2.xfeatures2d.SIFT_create()
# 獲取關鍵點和描述符
kp1, des1 = sift.detectAndCompute(img1,None)
kp2, des2 = sift.detectAndCompute(img2,None)

# 定義FLANN匹配器
index_params = dict(algorithm = 1, trees = 5)
search_params = dict(checks = 50)
flann = cv2.FlannBasedMatcher(index_params, search_params)
# 使用KNN演算法匹配
matches = flann.knnMatch(des1,des2,k=2)

# 去除錯誤匹配
good = []
for m,n in matches:
    if m.distance <= 0.7*n.distance:
        good.append(m)

# 單應性
if len(good)>MIN_MATCH_COUNT:
    # 改變陣列的表現形式,不改變資料內容,資料內容是每個關鍵點的座標位置
    src_pts = np.float32([ kp1[m.queryIdx].pt for m in good ]).reshape(-1,1,2)
    dst_pts = np.float32([ kp2[m.trainIdx].pt for m in good ]).reshape(-1,1,2)
    # findHomography 函式是計算變換矩陣
    # 引數cv2.RANSAC是使用RANSAC演算法尋找一個最佳單應性矩陣H,即返回值M
    # 返回值:M 為變換矩陣,mask是掩模
    M, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC,5.0)
    # ravel方法將資料降維處理,最後並轉換成列表格式
    matchesMask = mask.ravel().tolist()
    # 獲取img1的影象尺寸
    h,w,dim = img1.shape
    # pts是影象img1的四個頂點
    pts = np.float32([[0,0],[0,h-1],[w-1,h-1],[w-1,0]]).reshape(-1,1,2)
    # 計算變換後的四個頂點座標位置
    dst = cv2.perspectiveTransform(pts,M)

    # 根據四個頂點座標位置在img2影象畫出變換後的邊框
    img2 = cv2.polylines(img2,[np.int32(dst)],True,(0,0,255),3, cv2.LINE_AA)

else:
    print("Not enough matches are found - %d/%d") % (len(good),MIN_MATCH_COUNT)
    matchesMask = None

# 顯示匹配結果
draw_params = dict(matchColor = (0,255,0), # draw matches in green color
                   singlePointColor = None,
                   matchesMask = matchesMask, # draw only inliers
                   flags = 2)
img3 = cv2.drawMatches(img1,kp1,img2,kp2,good,None,**draw_params)
plt.figure(figsize=(20,20))
plt.imshow(cv2.cvtColor(img3,cv2.COLOR_BGR2RGB))
plt.show()

最後給大家放上原圖可以跑一下程式!

 

 

 

 

 

 

 

      

 

 

 

 

&nb