1. 程式人生 > 其它 >基於計算機視覺和OpenCV:建立一個能夠計算道路交通流量的應用

基於計算機視覺和OpenCV:建立一個能夠計算道路交通流量的應用

本文將介紹如何在不需要大量的深度學習演算法的情況下,基於計算機視覺來計算道路交通流量。本教程只使用Python和OpenCV,在背景差分演算法的幫助下,實現非常簡單的運動檢測方法。

本專案所需要的程式碼:

https://github.com/creotiv/object_detection_projects/tree/master/opencv_traffic_counting

主要內容

1.瞭解用於前景檢測的背景差分演算法的主旨 2.OpenCV影象濾波器 3.物體輪廓的檢測 4.為進一步的資料操作構建處理管道

專案最終效果視訊:https://youtu.be/_o5iLbRHKao

背景差分演算法

背景差分有很多不同的演算法,但它們的主旨非常簡單。假設你有一個你房間的視訊,在這個視訊裡沒有出現人或者寵物,所以基本上這個房間是靜態的,我們把它叫做背景層。為了獲得在視訊中移動的物體我們需要做的是:

foreground_objects = current_frame – background_layer(前景物體=當前幀 – 背景層)

但是在某些情況下,因為光線總是在改變,還有一些被人為移動或者本身可以運動的物體,我們不能得到靜態幀。在這種情況下我們儲存一些的幀數,並試圖找出在大多數畫素中,它們哪些是相同的,那麼這些畫素就會成為背景層的一部分。問題是,我們如何得到這個背景層和額外的濾波,從而使選擇更加準確。

所以,我們將使用MOG演算法來進行背景差分,在處理之後,它看起來是這樣的:

原圖(上),使用MOG差分前景(帶有陰影檢測)(下)

可以看到在前景掩模上有一些噪聲,我們會用一些標準的濾波技術去除。現在程式碼看上去是這樣的:

程式碼地址:

https://gist.githubusercontent.com/creotiv/f01ec1a4b7b1d88cad43c36be8fccc96/raw/f7b6ef1d4d13049e0ba28c10112e98b43f39baf0/bg_subtract.py

濾波

對於我們的情況,我們需要這些濾波器:“Threshold”, “Erode, Dilate, Opening, Closing”。請通過閱讀下面連結內容檢視這些濾波器是如何工作的。

http://docs.opencv.org/3.1.0/d7/d4d/tutorial_py_thresholding.html

http://docs.opencv.org/3.1.0/d9/d61/tutorial_py_morphological_ops.html

現在我們要用它們來去除前景蒙版上的一些噪聲。首先,我們將使用“Closing”來移除區域的間隙,然後用“Opening”移除1–2 px點,然後用“Dilate”使物體變得bolder。

def filter_mask(img):

    kernel= cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (2,2))

    # Fill any small holes
    closing= cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
    # Remove noise
    opening= cv2.morphologyEx(closing, cv2.MORPH_OPEN, kernel)

    # Dilate to merge adjacent blobs
    dilation= cv2.dilate(opening, kernel, iterations=2)

    # threshold
    th= dilation[dilation <240]= 0

    return th

然後我們的前景看上去是這樣的

物體輪廓的檢測

為此,我們將使用標準的帶有引數的cv2.findContours方法:

cv2.CV_RETR_EXTERNAL — get only outer contours.
cv2.CV_CHAIN_APPROX_TC89_L1 - use Teh-Chin chain approximation algorithm (faster)
def
 
get_centroid(x, y, w, h):
    x1= int(w/ 2)
    y1= int(h/ 2)

    cx= x+ x1
    cy= y+ y1

    return (cx, cy)

def detect_vehicles(fg_mask, min_contour_width=35, min_contour_height=35):

    matches= []

    # finding external contours
    im, contours, hierarchy= cv2.findContours(
        fg_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_TC89_L1)

    # filtering by with, height
    for (i, contour)in enumerate(contours):
        (x, y, w, h)= cv2.boundingRect(contour)
        contour_valid= (w >= min_contour_width)and (
            h >= min_contour_height)

        if not contour_valid:
            continue

        # getting center of the bounding box
        centroid= get_centroid(x, y, w, h)

        matches.append(((x, y, w, h), centroid))

    return matches

構建處理管道

現在我們將構建簡單的處理管道:

程式碼地址:

https://gist.githubusercontent.com/creotiv/6db4c523ae7c64554c3d08ff5edb8e79/raw/3b3a9f7253412dab686fe935808f66eb101057ed/pipeline.py

輸入建構函式(input constructor)將會獲取一個將按順序執行的處理器列表。每個處理器都有各自的工作。因此,現在讓我們來建立輪廓檢測處理器。

程式碼地址:

https://gist.githubusercontent.com/creotiv/75a84e9a3f634c0f5c399bc495137075/raw/535b048a088e734e7bfcc1d14cb9e5e9cd434ebd/detection.py

把背景差分,濾波和檢測的部分合並在一起。現在,讓我們建立一個處理器,它將在不同的幀上鍊接檢測到的物件,然後建立路徑,並且還將計算出到達出口區的車輛數量。

程式碼地址:

https://gist.githubusercontent.com/creotiv/da7fca1b237619a756133a1fa816350e/raw/243204ca5d277b20c9addbc3559a69bcdb9d132b/counting.py

這個類有點複雜,讓我們通過分部來完成。

影象上的綠色掩膜是出口區,是我們計算車輛的地方。使用掩膜是因為它比使用向量演算法更有效,也更簡單。只要使用“二進位制”操作來檢查該區域的那個點就可以了。下面是我們如何設定它的方法:

EXIT_PTS= np.array([
    [[732,720], [732,590], [1280,500], [1280,720]],
    [[0,400], [645,400], [645,0], [0,0]]
])

base= np.zeros(SHAPE+ (3,), dtype='uint8')
exit_mask= cv2.fillPoly(base, EXIT_PTS, (255,255,255))[:, :,0]

在路徑上將點連線起來。

new_pathes= []

for pathin self.pathes:
    _min= 999999
    _match= None
    for pin points:
        if len(path)== 1:
            # distance from last point to current
            d= utils.distance(p[0], path[-1][0])
        else:
            # based on 2 prev points predict next point and calculate
            # distance from predicted next point to current
            xn= 2 * path[-1][0][0]- path[-2][0][0]
            yn= 2 * path[-1][0][1]- path[-2][0][1]
            d= utils.distance(
                p[0], (xn, yn),
                x_weight=self.x_weight,
                y_weight=self.y_weight
            )

        if d < _min:
            _min= d
            _match= p

    if _matchand _min <= self.max_dst:
        points.remove(_match)
        path.append(_match)
        new_pathes.append(path)

    # do not drop path if current frame has no matches
    if _matchis None:
        new_pathes.append(path)

self.pathes= new_pathes

# add new pathes
if len(points):
    for pin points:
        # do not add points that already should be counted
        if self.check_exit(p[1]):
            continue
        self.pathes.append([p])

# save only last N points in path
for i, _in enumerate(self.pathes):
    self.pathes[i]= self.pathes[i][self.path_size* -1:]

在第一幀,我們只是把所有的點都新增到新的路徑中。

接下來,如果len(path)==1,那麼對於快取中的每條路徑,我們將嘗試從新檢測到的物件中找到點(質心),這將是到路徑的最後一點的最小的歐氏距離。

如果len(path) > 1,那麼在路徑的最後兩個點上,我們就會在同一直線上預測新的點,並在它和當前點之間找到最小距離。最小距離新增到當前路徑的末尾,並從列表中刪除。如果在這之後留下一些點,我們將會把它們作為新的路徑新增。同時我們也限制了路徑上的點的個數。

# count vehicles and drop counted pathes:
 new_pathes= []
 for i, pathin enumerate(self.pathes):
     d= path[-2:]


    if (
         # need at list two points to count
         len(d) >= 2 and
         # prev point not in exit zone
         not self.check_exit(d[0][1])and
         # current point in exit zone
         self.check_exit(d[1][1])and
         # path len is bigger then min
         self.path_size <= len(path)
     ):
         self.vehicle_count+= 1
    else:
         # prevent linking with path that already in exit zone
         add= True
         for pin path:
             if self.check_exit(p[1]):
                add= False
                 break
        if add:
            new_pathes.append(path)


 self.pathes= new_pathes


 context['pathes']= self.pathes
 context['objects']= objects
 context['vehicle_count']= self.vehicle_count


 self.log.debug('#VEHICLES FOUND: %s' % self.vehicle_count)


 return context

現在我們將嘗試計算進入出口區的車輛。要做到這一點,我們只需要在路徑中取兩個最後的點,並檢查它們在出口區域中的最後一個點,以及之前沒有的點,並且檢查len(path)是否應該大於限制。後面的部分是防止將新點與出口區的點反向連線起來。

最後兩個處理器是CSV寫入器,用於建立報告CSV檔案,以及用於除錯和圖片的視覺化。

程式碼地址:

https://gist.github.com/creotiv/2998928abe5ab8606c07450965393261/raw/7b7633492d4ae0e8499e706029ceb452fe44ba60/output.py

CSV寫入器是按時間儲存資料的,因為我們需要將它進一步分析。因此,我使用這個公式向unixtimestamp新增額外的幀計時:

time = ((self.start_time + int(frame_number / self.fps)) * 100 
        + int(100.0 / self.fps) * (frame_number % self.fps))

所以在起始時間=1 000 000和fps=10時,會得到這樣的結果 幀1=1 000000010 幀1=1 000000020 …

在得到完整的csv報告之後,你可以按照你的需要聚合這些資料。

這個專案的完整程式碼:https://github.com/creotiv/object_detection_projects/tree/master/opencv_traffic_counting