隔離宅在家,我自己做了個社交距離檢測器
本文最初發表在 TowardsDataScience 博客,經原作者 Basile Roth 授權,InfoQ 中文站翻譯並分享。
介 紹
在隔離期間,我在 GitHub 上花了時間去探索 TensorFlow 的大量預訓練模型。在探索的過程中,我偶然發現了一個 倉庫 ,裏面有 25 個 預訓練的對象檢測模型 ,並帶有性能和速度指標。由於我對計算機視覺有一定的瞭解,並且考慮到實際情況,我認爲,使用其中的一個模型來構建一個社交距離應用可能會很有趣。
更重要的是,上學期我在 計算機視覺 (Computer Vision)課上接觸到了 OpenCV ,在做一些小項目的時候,我意識到 OpenCV 是多麼的強大。其中之一是對一張圖片進行鳥瞰視圖變換。 鳥瞰視圖 基本上就是一個自上而下的場景表現。這是在構建自動駕駛汽車應用程序時經常執行的任務。
這讓我意識到,在我們想要監控社交距離的場景中,應用這樣的技術,可以提高它的質量。本文介紹了我如何使用 深度學習 模型以及 計算機視覺 的一些知識來構建一個強大的社交距離檢測器。
本文所有的代碼以及安裝說明都可以在我的 GitHub 倉庫 中找到。
1. 模型的選擇
TensorFlow 對象監測模型動物園上的所有可用模型都已在 COCO ( C ommon O bjects in CO ntext 數據集 上進行訓練。這個 數據集 包含 120000 張圖片,其中 880000 張圖片中有已標記的對象。這些模型被訓練用來檢測數據集中標記的 90 種不同類型的對象 。所有這些不同對象的完整列表可以在 github repo 的 data 部分中找到。這個對象列表包括汽車、牙刷、香蕉,當然還有人。
根據模型的速度不同,它們有不同的性能。爲了確定如何根據預測速度來利用模型的質量,我做了一些測試。由於這個應用程序的目標並不是爲了執行實時分析,因此,我最終選擇了 faster_rcnn_inception_v2_coco ,它的 mAP(在驗證集上的檢測器性能)得分爲 28,性能相當強悍,執行速度爲 58 毫秒。
2. 人體檢測
要使用這種模型,爲了能夠檢測到人體,必須執行以下幾個步驟:
- 將包含模型的文件夾在到 TensorFlow 途中。並定義想要從模型中獲得的輸出。
- 對於每一幀,通過圖傳遞圖像以獲得所需的輸出。
- 過濾掉弱預測以及無需檢測的對象。
加載並啓動模型
TensorFlow 模型的工作方式是通過使用 圖 來設計的。第一步意味着將模型加載到 TensorFlow 圖中。這個圖將包含不同的操作,以獲得所需的檢測。下一步是創建一個 會話 ,它是一個實體,負責執行上一個圖中定義的操作。欲瞭解更多關於圖和會話的解釋,請參閱這篇文章:《 什麼是 TensorFlow 會話? 》(What is a TensorFlow Session?)。我已經決定實現一個類,將所有與 TensorFlow 圖相關的數據放在一起。
複製代碼
class Model: """ Class that contains the model and all its functions """ def __init__(self, model_path): """ Initialization function @ model_path : path to the model """ # Declare detection graph self.detection_graph = tf.Graph() # Load the model into the tensorflow graph with self.detection_graph.as_default(): od_graph_def = tf.compat.v1.GraphDef() with tf.io.gfile.GFile(model_path, 'rb') as file: serialized_graph = file.read() od_graph_def.ParseFromString(serialized_graph) tf.import_graph_def(od_graph_def, name='') # Create a session from the detection graph self.sess = tf.compat.v1.Session(graph=self.detection_graph) def predict(self,img): """ Get the predicition results on 1 frame @ img : our img vector """ # Expand dimensions since the model expects images to have shape: [1, None, None, 3] img_exp = np.expand_dims(img, axis=0) # Pass the inputs and outputs to the session to get the results (boxes, scores, classes) = self.sess.run([self.detection_graph.get_tensor_by_name('detection_boxes:0'), self.detection_graph.get_tensor_by_name('detection_scores:0'), self.detection_graph.get_tensor_by_name('detection_classes:0')],feed_dict={self.detection_graph.get_tensor_by_name('image_tensor:0'): img_exp}) return (boxes, scores, classes)
通過模型傳遞每個幀
對於需要處理的每個幀,都會啓動一個新會話。這是通過調用 run()
函數來完成的。執行此操作時必須指定一些參數。這些參數包括模型需要的輸入類型,以及我們希望從模型中返回哪些輸出。在我們的示例中,需要的輸出如下:
- 每個對象的 邊界框座標 。
- 每個預測的置信度 (0 或 1)。
- 預測的類 (0 到 90)。
過濾掉弱預測和無關對象
模型檢測到的許多類中有一個是人體,與人體關聯的類是 1。
爲了排除弱預測( 閾值:0.75 )和除了人體之外的所有其他類型的對象,我使用了一條 if 語句,結合這兩個條件來排除任何其他對象的進一步計算。
複製代碼
if int(classes[i]) == 1 and scores[i] > 0.75
3. 鳥瞰視圖變換
正如在本文介紹一節中所解釋的,執行鳥瞰視圖變換可以給我們一個 場景的俯視圖 。值得慶幸的是,OpenCV 具有強大的內置函數,可以將這種方法應用於圖像,從而將從透視角度獲取的圖像變換爲該圖像的俯視圖。我使用了 Adrian Rosebrock 的 教程 來了解如何做到這一點。
第一步是在原始圖像上選擇 4 個點,這些點將成爲要轉換的平面圖的角點。這些點必須形成一個矩形,至少有兩條對邊是平行的。如果不這樣做的話,當進行變換時,比例將會不一樣。我已經在我的 倉庫 中實現了一個腳本,它使用 OpenCV 的 setMouseCallback()
函數來獲取這些座標。計算變換矩陣的函數也需要圖像的維度,這些維度是使用圖像的 image.shape
適當計算的。
複製代碼
width, height, _ = image.shape
這將返回寬度、高度和其他不相關的顏色像素值。讓我們來看看它們是如何用於計算轉換矩陣的:
複製代碼
def compute_perspective_transform(corner_points,width,height,image): """ Compute the transformation matrix @ corner_points : 4 corner points selected from the image @ height, width : size of the image return : transformation matrix and the transformed image """ # Create an array out of the 4 corner points corner_points_array = np.float32(corner_points) # Create an array with the parameters (the dimensions) required to build the matrix img_params = np.float32([[0,0],[width,0],[0,height],[width,height]]) # Compute and return the transformation matrix matrix = cv2.getPerspectiveTransform(corner_points_array,img_params) img_transformed = cv2.warpPerspective(image,matrix,(width,height)) return matrix,img_transformed
請注意,我之所以選擇返回矩陣,是因爲在下一步中將使用它來計算檢測到的每個人體的新座標。其結果是幀中每個人的 “ GPS ” 座標 。使用這些點要比使用原始地面點 要精確得多 ,因爲在透視視圖中,當人體處於不同的平面時,距離是不一樣的,而不是與攝像機相同的距離。與使用原始幀中的點相比,這種方法可以大大提高社交距離測量的精度。
對於檢測到的每個人體,返回構建一個邊界框所需的兩個點。這些點是邊界框的左上角和右下角。從這些點中,我通過得到它們之間的中間點,計算出 邊界框的中心點 。利用這一結果,我計算出位於邊界框底部中心的點的座標。在我看來,我稱之爲 地面點 的這個點,是圖像中人體座標的最佳表示。
然後,我使用變換矩陣來計算每個檢測到的地面點的變換座標。這是在檢測到每個幀中的人體之後,使用 cv2.perspectiveTransform()
函數在每個幀執行此操作來完成的。下面就是我如何實現這項任務的方式:
複製代碼
def compute_point_perspective_transformation(matrix,list_downoids): """ Apply the perspective transformation to every ground point which have been detected on the main frame. @ matrix : the 3x3 matrix @ list_downoids : list that contains the points to transform return : list containing all the new points """ # Compute the new coordinates of our points list_points_to_detect = np.float32(list_downoids).reshape(-1, 1, 2) transformed_points = cv2.perspectiveTransform(list_points_to_detect, matrix) # Loop over the points and add them to the list that will be returned transformed_points_list = list() for i in range(0,transformed_points.shape[0]): transformed_points_list.append([transformed_points[i][0][0],transformed_points[i][0][1]]) return transformed_points_list
4. 測量社交距離
在每個幀上調用這個函數後,將返回一個包含所有新變換點的列表。根據這個列表,我必須計算出每對點之間的距離。我使用了 itertools 庫中的 combinations()
函數,它允許在列表中獲取每個可能的組合,而無需保持雙精度。這在 堆棧溢出問題 上有很好的解釋。剩下的就是簡單的數學計算:在 Python 中,使用 math.sqrt()
函數很容易計算出兩點之間的距離。選擇的閾值爲 120 個像素,因爲這一數字在我們的場景中大約相當於 2 英尺。
複製代碼
# Check if 2 or more people have been detected (otherwise no need to detect) if len(transformed_downoids) >= 2: # Iterate over every possible 2 by 2 between the points combinations list_indexes = list(itertools.combinations(range(len(transformed_downoids)), 2)) for i,pair in enumerate(itertools.combinations(transformed_downoids, r=2)): # Check if the distance between each combination of points is less than the minimum distance chosen if math.sqrt( (pair[0][0] - pair[1][0])**2 + (pair[0][1] - pair[1][1])**2 ) < int(distance_minimum): # Change the colors of the points that are too close from each other to red change_color_topview(pair) # Get the equivalent indexes of these points in the original frame and change the color to red index_pt1 = list_indexes[i][0] index_pt2 = list_indexes[i][1] change_color_originalframe(index_pt1,index_pt2)
一旦確定兩個點彼此距離太近,則標記該點的圓圈的顏色就會從綠色變爲紅色,並且原始幀上的邊界框顏色也會與之相同。
5. 結果
讓我繼續介紹這個項目的工作原理:
- 首先得到平面圖的 4 個角點,然後應用透視變換得到這個平面圖的鳥瞰視圖,並保存變換矩陣。
- 獲取在原始幀中檢測到的每個人體的邊界框。
- 計算這個邊界框的最低點,它位於邊界框兩個底部角點之間。
- 利用這些點的變換矩陣來得到每個人體的真實 “GPS” 座標。
- 使用
itertools.combinations()
來測量每個點到幀中所有其他點的距離。 - 如果檢測到社交距離過近,則將邊界框的顏色更改爲紅色。
我使用了一段來自 PETS2009 數據集的視頻,該數據集由包含不同人羣活動的多傳感器序列組成。它最初是用來完成人數統計和人羣密度估計等任務的。我決定使用第一個角度的視頻,因爲它是最寬的一個角度,具有最好的場景視圖。這段視頻展示了所獲得的結果。
https://v.qq.com/x/page/k3101orq51k.html
6. 結論與改進
如今,社交距離和其他基本衛生措施對儘可能減緩新冠肺炎病毒的傳播速度。但這個項目只是一個概念驗證,由於倫理和隱私問題,並沒有用來檢測公共或私人領域的社交距離。
我很清楚這個項目並不完美,所以,以下是我對如何改進這個應用程序的一些想法:
- 使用更快的模型來執行實時社交距離分析。
- 使用對遮擋情況更穩健的模型。
- 自動標定(Automatic calibration)是計算機視覺中一個非常著名的問題,它可以極大地改善不同場景下的鳥瞰圖變換。
我已在 GitHub 上提供了本文的完整代碼。
參考資料
-
《 基於 TensorFlow 對象檢測和 OpenCV 的足球比賽分析 》(Analyze a Soccer game using Tensorflow Object Detection and OpenCV),Priya Dwivedi,2018 年
-
《 4 點 OpenCV getPerspective 變換示例 》(4 Point OpenCV getPerspective Transform Example),Adrian Rosebrock,2014 年
-
《 RidgeRun 鳥瞰視圖項目研究 》(RidgeRun’s Birds Eye View project research),RidgeRun
-
《 PETS 2009 基準數據 》(PETS 2009 Benchmark Data),Miami,2009 年
作者介紹:
Basile Roth,加拿大魁北克省蒙特利爾碩士研究生。研究領域包括:機器學習、計算機視覺和自然語言處理。