案例|如何用Python 和 Mask R-CNN 自動尋找停車位(附源碼)

作者想用深度學習來解決一個小麻煩,於是用 Python 和 Mask R-CNN 設計了一個模型。該模型可以自動檢測停車位並在發現可用車位後向他發送短信。這是什麼神仙(sao)操作?

案例|如何用Python 和 Mask R-CNN 自動尋找停車位(附源碼)

我居住在一個大城市。但是和在很多大城市一樣,找個停車位總沒那麼容易。車位很快就被搶佔一空,即使你有一個屬於自己的專用車位,朋友們順路來訪也很難,因為他們找不到車位。

我的解決方案就是將一個攝像頭伸出窗外,再用深度學習讓我的計算機在有車位空出來的時候給我發短信:

案例|如何用Python 和 Mask R-CNN 自動尋找停車位(附源碼)

這聽起來也許很複雜,但是藉助深度學習構建一個實用的版本實際上又簡單又快。所有的工具都是可用的——你只要知道去哪兒找到工具並把它們組合在一起就行。

那麼,我們花點時間用 python 和深度學習構建一個高準確率的停車位通知系統吧!

問題分解

當我們面臨想要用機器學習解決的複雜問題時,第一步就是將問題分解成若干簡單問題。然後,使用詳細分解作為指導,我們可以從機器學習工具箱中使用不同的工具來解決這些小問題。通過將幾個不同的解決方案鏈接到一個流程中,我們就得到了能夠做一些複雜事情的系統。

下面是我分解的空車位檢測流程:

案例|如何用Python 和 Mask R-CNN 自動尋找停車位(附源碼)

機器學習流程的輸入是來自一個伸出窗外的普通網絡攝像頭的視頻流:

案例|如何用Python 和 Mask R-CNN 自動尋找停車位(附源碼)

從攝像頭中截取的示例視頻

我們將通過工作流程傳送每一幀視頻,一次一幀。

這個流程的第一步就是檢測一幀視頻中所有可能的停車位。顯然,在我們能夠檢測哪個是沒有被佔用的停車位之前,我們需要知道圖像中的哪些部分是停車位。

第二步就是檢測每幀視頻中的所有車輛。這樣我們可以逐幀跟蹤每輛車的運動。

第三步就是確定哪些車位目前是被佔用的,哪些沒有。這需要結合前兩步的結果。

最後一步就是出現新車位時通知我。這需要基於視頻中兩幀之間車輛位置的變化。

這裡的每一步,我們都可以使用多種技術用很多種方式實現。構建這個流程並沒有唯一正確或者錯誤的方式,但不同的方法會有優劣之分。

第一步:檢測一幅圖像中的停車位

攝像頭的視野是這樣的:

案例|如何用Python 和 Mask R-CNN 自動尋找停車位(附源碼)

我們需要掃描這幅圖,然後返回一個有效停車區域列表,就像這樣:

案例|如何用Python 和 Mask R-CNN 自動尋找停車位(附源碼)

這個城市街道上的有效停車位。

一種比較懶的方法就是手動把每個停車位的位置硬編碼到程序中,而不是自動檢測停車位。但是如果我們移動了攝像頭或者想要檢測另一條街道上的車位時,就必須再一次手動硬編碼車位的位置。這樣很麻煩,還是找一種自動檢測車位的方法吧。

一個思路是尋找停車計時器並假設每個計時器旁邊都有一個停車位:

案例|如何用Python 和 Mask R-CNN 自動尋找停車位(附源碼)

檢測每幅圖像中的停車計時器。

但這種方法有些複雜。首先,並非每個停車位都有停車計時器——實際上,我們最喜歡找的是無需付費的車位!其次,只知道停車計時器的位置並不能確切地告訴我們停車位的確切位置。它只是讓我們更加接近停車位罷了。

另一個思路就是構建目標檢測模型,讓它尋找道路上繪製的停車位標誌,就像這樣:


案例|如何用Python 和 Mask R-CNN 自動尋找停車位(附源碼)

請注意這些黃色的小標誌——它們就是畫在道路上的每個車位的邊界。

但是這種方法也很令人痛苦。首先,我所在城市的停車位標誌線特別小,在這麼遠的距離很難看見,所以很難用計算機檢測到它們。其次,道路上有各種無關的線和標誌。很難區分哪些線是停車位標誌,哪些是車道分離線或者人行道線。

當你遇到似乎很困難的問題時,花幾分鐘時間想一想,你是否可以採用不同的方法來解決這個問題,避開一些技術性挑戰。到底什麼是停車位?停車位不就是車輛可以停放很長時間的地方嗎。所以,也許我們根本就沒必要去檢測停車位。為何不能僅僅檢測長時間沒有移動的車輛並且假設它們就停在停車位呢?

換句話說,有效停車位就是包含非移動車輛的地方。

案例|如何用Python 和 Mask R-CNN 自動尋找停車位(附源碼)

這裡,每輛車的邊界框實際上就是一個停車位!如果我們能夠檢測靜態的車輛,就沒必要檢測停車位。

所以,如果我們能夠檢測車輛,並且可以判斷哪些車輛在視頻幀中是沒有移動的,那我們就能夠推測出停車位的位置。夠簡單了——讓我們來檢測車輛吧!

檢測圖像中的車輛

檢測視頻幀裡的車輛就是目標檢測中的一道練習題。我們可以用很多機器學習方法來檢測圖像中的目標。下面是我列出的幾種最常用的目標檢測算法:

  • 訓練一個 HOG(方向梯度直方圖) 目標檢測器,並用它滑過我們的圖像以尋找所有的車輛。這個古老的非深度學習方法運行起來相當快,但是它並不能很好地處理向不同方向移動的車輛。
  • 訓練一個 CNN(卷積神經網絡) 目標檢測器,用它滑過我們的圖像直到找到所有的車輛。這種方法很準確,但並不是很高效,因為我們必須多次掃描同一張圖像來尋找所有的車輛。並且,雖然它可以輕易找到向不同方向移動的車輛,但它需要的訓練數據要比 HOG 目標檢測器多得多。
  • 使用更新的深度學習方法,如 Mask R-CNN、Faster R-CNN 或者 YOLO。它們將靈活的設計和高效的技巧與 CNN 的準確性結合在了一起,能夠極大地加速檢測過程。只要我們有足夠多的數據來訓練模型,它能在 GPU 上運行地相對快一些。


通常情況下,我們希望選擇最簡單的方法來解決問題,使用最少的訓練數據,並不認為需要最新、最流行的算法。但是在這個特殊的情況下,*Mask R-CNN*是一個比較合理的選擇,雖然它是一個比較新、比較流行的算法。

Mask R-CNN 架構在不使用滑動窗口的情況下以一種高效的計算方式在整幅圖像中檢測目標。換句話說,它運行得相當快。在具有比較先進的 GPU 時,我們應該能夠以數幀每秒的速度檢測到高分辨率視頻中的目標。所以它應該比較適合這個項目。

此外,Mask R-CNN 給我們提供了很多關於每個檢測對象的信息。絕大多數目標檢測算法僅僅返回了每個對象的邊界框。但是 Mask R-CNN 並不會僅僅給我們提供每個對象的位置,它還會給出每個對象的輪廓 (掩模),就像這樣:


案例|如何用Python 和 Mask R-CNN 自動尋找停車位(附源碼)

為了訓練 Mask R-CNN,我們需要大量關於需要檢測的目標的圖像。我們可以拍一些車輛的圖像,然後將這些圖像中的汽車標註出來,但是這可能會花費幾天的工作。幸運的是,汽車是很常見的檢測目標,很多人都想檢測,因此早就有了幾個公開的汽車數據集。

有一個很流行的數據集叫做 COCO,它裡面的圖像都用目標掩膜標註過。在這個數據集中,已經有超過 12000 張汽車圖像做好了輪廓標註。下面就是 COCO 數據集中的一張圖像。


案例|如何用Python 和 Mask R-CNN 自動尋找停車位(附源碼)

COCO 數據集中已標註輪廓的圖像。

這個數據集非常適合用來訓練 Mask R-CNN 模型。

使用 COCO 數據集來構建目標檢測數據集是很常見的一件事情,所以好多人已經做過並且分享了他們的結果。因此,我們可以用一個訓練好的模型作為開始,而不用從頭去訓練自己的模型。針對這個項目,我們可以使用很棒的開源 Mask R-CNN,它是由 Matterport 公司實現的,還提供了訓練好的模型。

地址:https://github.com/matterport/Mask_RCNN

旁註:你不必為訓練定製化的 Mask R-CNN 而擔心!標註數據是很耗時間的,但是並不困難。如果你想使用自己的數據完整地訓練 Mask R-CNN 模型,可以參考這本書:

https://www.machinelearningisfun.com/get-the-book

如果在我自己的相機圖像上運行預訓練模型,以下是檢測結果:

案例|如何用Python 和 Mask R-CNN 自動尋找停車位(附源碼)

經過 COCO 默認目標識別的圖像——車輛、人、交通信號燈和樹。

我們不僅識別了車輛,還識別出了交通信號燈和人。而且比較滑稽的是,它將其中的一棵樹識別成了「盆栽植物」。

對於圖像中被檢測到的每一個目標,我們從 Mask R-CNN 模型中得到了下面四個結果:

  1. 被檢測到的目標(作為整數)類型。預訓練的 COCO 模型知道如何檢測 80 種不同的常見目標,例如汽車和卡車。
  2. 目標檢測的置信得分。這個數字越大,越說明模型準確識別了目標。
  3. 中目標的邊界框,以 X/Y 像素位置地形式給了出來。
  4. 位圖「掩模」,能夠分辨出邊界框裡哪些像素是目標的一部分,哪些不是。有了掩模數據,我們也可以標註目標的輪廓。

下面是 python 代碼,用於根據 Matterport』s Mask R-CNN 實現和 OpneCV 預訓練的模型來檢測汽車邊界框:

import os
import numpy as np
import cv2
import mrcnn.config
import mrcnn.utils
from mrcnn.model import MaskRCNN
from pathlib import Path
# Configuration that will be used by the Mask-RCNN library
class MaskRCNNConfig(mrcnn.config.Config):
NAME = "coco_pretrained_model_config"
IMAGES_PER_GPU = 1
GPU_COUNT = 1
NUM_CLASSES = 1 + 80 # COCO dataset has 80 classes + one background class
DETECTION_MIN_CONFIDENCE = 0.6
# Filter a list of Mask R-CNN detection results to get only the detected cars / trucks
def get_car_boxes(boxes, class_ids):
car_boxes = []
for i, box in enumerate(boxes):
# If the detected object isn t a car / truck, skip it
if class_ids[i] in [3, 8, 6]:
car_boxes.append(box)
return np.array(car_boxes)
# Root directory of the project
ROOT_DIR = Path(".")
# Directory to save logs and trained model
MODEL_DIR = os.path.join(ROOT_DIR, "logs")
# Local path to trained weights file
COCO_MODEL_PATH = os.path.join(ROOT_DIR, "mask_rcnn_coco.h5")
# Download COCO trained weights from Releases if needed
if not os.path.exists(COCO_MODEL_PATH):
mrcnn.utils.download_trained_weights(COCO_MODEL_PATH)
# Directory of images to run detection on
IMAGE_DIR = os.path.join(ROOT_DIR, "images")
# Video file or camera to process - set this to 0 to use your webcam instead of a video file
VIDEO_SOURCE = "test_images/parking.mp4"
# Create a Mask-RCNN model in inference mode
model = MaskRCNN(mode="inference", model_dir=MODEL_DIR, config=MaskRCNNConfig())
# Load pre-trained model
model.load_weights(COCO_MODEL_PATH, by_name=True)
# Location of parking spaces
parked_car_boxes = None
# Load the video file we want to run detection on
video_capture = cv2.VideoCapture(VIDEO_SOURCE)
# Loop over each frame of video
while video_capture.isOpened():
success, frame = video_capture.read()
if not success:
break
# Convert the image from BGR color (which OpenCV uses) to RGB color
rgb_image = frame[:, :, ::-1]

# Run the image through the Mask R-CNN model to get results.
results = model.detect([rgb_image], verbose=0)
# Mask R-CNN assumes we are running detection on multiple images.
# We only passed in one image to detect, so only grab the first result.
r = results[0]
# The r variable will now have the results of detection:
# - r[ rois ] are the bounding box of each detected object
# - r[ class_ids ] are the class id (type) of each detected object
# - r[ scores ] are the confidence scores for each detection
# - r[ masks ] are the object masks for each detected object (which gives you the object outline)
# Filter the results to only grab the car / truck bounding boxes
car_boxes = get_car_boxes(r[ rois ], r[ class_ids ])
print("Cars found in frame of video:")
# Draw each box on the frame
for box in car_boxes:
print("Car: ", box)
y1, x1, y2, x2 = box
# Draw the box
cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 1)
# Show the frame of video on the screen
cv2.imshow( Video , frame)
# Hit q to quit
if cv2.waitKey(1) & 0xFF == ord( q ):
break
# Clean up everything when finished
video_capture.release()
cv2.destroyAllWindows()

當你運行這段腳本時,會在屏幕上得到一幅圖,每輛檢測到的汽車都有一個邊界框,像這樣:

案例|如何用Python 和 Mask R-CNN 自動尋找停車位(附源碼)

每輛檢測到的汽車都有一個綠色的邊界框。

你還會看到被檢測到的汽車座標被打印在了控制檯上,就像這樣:

Cars found in frame of video:
Car: [492 871 551 961]
Car: [450 819 509 913]
Car: [411 774 470 856]

經過以上這些步驟,我們已經成功地檢測到了圖像中的汽車。

檢測空置的停車位

我們知道了每張圖像中每輛車的像素位置。通過查看視頻中按順序出現的多幀,我們可以輕易知道哪些車沒有動,並且假設它們所在的位置就是車位。但是,當一輛車離開車位的時候,我們如何檢測得到呢?

問題在於我們圖像中的邊界框是部分重疊的。

案例|如何用Python 和 Mask R-CNN 自動尋找停車位(附源碼)

即使是在不同車位中的車輛,每輛車的邊界框都會有一小部分的重疊。

所以,如果我們假設每個邊界框代表一個車位,那麼,即使車位是空的,也有可能顯示為被部分佔用。我們需要一個方法來測量兩個對象的重疊度,以便檢查「大部分是空的」邊界框。

我們將要使用的測量方法為交併比(IoU)。IoU 通過兩個對象重疊的像素數量除以兩個對象覆蓋的像素數量計算得到。像這樣:


案例|如何用Python 和 Mask R-CNN 自動尋找停車位(附源碼)

這將為我們提供汽車邊界框與停車位邊界框重疊的程度。有了這個,我們可以輕易確定汽車是否在停車位。如果 IoU 測量值很低,如 0.15,那意味著汽車並沒有真正佔用大部分停車位。但如果指標很高,如 0.6,這意味著汽車佔據了大部分停車位區域,因此我們可以確定該空間被佔用。

由於 IoU 是計算機視覺中常見的測量方法,因此你在使用的庫通常已經實現了它的計算。事實上,Matterport Mask R-CNN 庫將它作為一個名為 mrcnn.utils.compute_overlaps()的函數包含在內,因此我們可以直接使用該函數。

假設我們有一個表示圖像中停車區域的邊界框列表,查看檢測到的車輛是否在這些邊界框內就像添加一行或兩行代碼一樣簡單:

# Filter the results to only grab the car / truck bounding boxes
car_boxes = get_car_boxes(r[ rois ], r[ class_ids ])
# See how much cars overlap with the known parking spaces
overlaps = mrcnn.utils.compute_overlaps(car_boxes, parking_areas)
print(overlaps)

結果是這樣子的:

[
[1. 0.07040032 0. 0.]
[0.07040032 1. 0.07673165 0.]
[0. 0. 0.02332112 0.]
]
In that 2D array, each

在這個二維數組中,每一行代表一個停車位的邊界框。同樣,每一列代表著這個停車位被檢測到的汽車佔用了多少。1.0 分表示完全被佔用,較低的分,如 0.02 則表示這輛汽車接觸到了車位的空間,但是並沒有佔據大部分區域。

要尋找未被佔用的停車位,我們只需要檢查此陣列中的每一行。如果所有數字都為零或非常小,那意味著沒有任何東西佔據那個空間,它就是空著的!

但請記住,目標檢測並非總是與實時視頻完美配合。即使 Mask R-CNN 非常準確,偶爾也會在單幀視頻中錯過一兩輛車。因此,在將停車位標記為空閒之前,我們應該確保它在一段時間內保持空閒 - 可能是 5 或 10 個連續的視頻幀。這將防止系統僅僅因為目標檢測在一幀視頻上有短暫的停頓就錯誤地檢測到空閒的停車位。但是,只要我們看到至少有一個空閒停車位出現在連續幾幀視頻中,我們就可以發送短信了!

發送短信

這個項目的最後一步就是當檢測到一個空閒停車位出現在視頻的連續幾幀中時就發送短信提醒。

使用 Twilio 從 Python 中發送短信很簡單。Twilio 是一個很流行的 API,它可以讓你用任何編程語言只需幾行代碼就可以發送短信。當然,如果你更喜歡使用其它短信服務提供商,也可以。我和 Twilio 並沒有利益關係。它只是我想到的第一個工具而已。

要使用 Twilio,你需要註冊一個試用賬戶,創建兩個 Twilio 電話號碼,然後認證賬戶。然後,你需要安裝 Twilio Python 客戶端。

pip3 install twilio

安裝完成後,這是用 Python 發送短信的完整代碼(只需用你自己的帳戶詳細信息替換這些值即可):

from twilio.rest import Client
# Twilio account details
twilio_account_sid = Your Twilio SID here
twilio_auth_token = Your Twilio Auth Token here
twilio_source_phone_number = Your Twilio phone number here
# Create a Twilio client object instance
client = Client(twilio_account_sid, twilio_auth_token)
# Send an SMS
message = client.messages.create(
body="This is my SMS message!",
from_=twilio_source_phone_number,
to="Destination phone number here"
)

為了在腳本中添加短信發送功能,我們可以把這些代碼丟進去。但需要注意的是,我們並不需要在每一個有空閒車位的新視頻幀中發送短信。所以我們需要一個標誌來跟蹤是否已經發過短信了,這是為了保證不會在短期內再次發送或者在新車位空出來之前再次發送。

總結

將以上流程中的所有步驟整合在一起,構成一個獨立的 Python 腳本,完整代碼如下所示:

import os
import numpy as np
import cv2
import mrcnn.config
import mrcnn.utils
from mrcnn.model import MaskRCNN
from pathlib import Path
from twilio.rest import Client
# Configuration that will be used by the Mask-RCNN library
class MaskRCNNConfig(mrcnn.config.Config):
NAME = "coco_pretrained_model_config"
IMAGES_PER_GPU = 1
GPU_COUNT = 1
NUM_CLASSES = 1 + 80 # COCO dataset has 80 classes + one background class
DETECTION_MIN_CONFIDENCE = 0.6
# Filter a list of Mask R-CNN detection results to get only the detected cars / trucks
def get_car_boxes(boxes, class_ids):
car_boxes = []
for i, box in enumerate(boxes):
# If the detected object isn t a car / truck, skip it
if class_ids[i] in [3, 8, 6]:
car_boxes.append(box)
return np.array(car_boxes)
# Twilio config
twilio_account_sid = YOUR_TWILIO_SID
twilio_auth_token = YOUR_TWILIO_AUTH_TOKEN
twilio_phone_number = YOUR_TWILIO_SOURCE_PHONE_NUMBER
destination_phone_number = THE_PHONE_NUMBER_TO_TEXT
client = Client(twilio_account_sid, twilio_auth_token)
# Root directory of the project
ROOT_DIR = Path(".")
# Directory to save logs and trained model
MODEL_DIR = os.path.join(ROOT_DIR, "logs")
# Local path to trained weights file
COCO_MODEL_PATH = os.path.join(ROOT_DIR, "mask_rcnn_coco.h5")
# Download COCO trained weights from Releases if needed
if not os.path.exists(COCO_MODEL_PATH):
mrcnn.utils.download_trained_weights(COCO_MODEL_PATH)
# Directory of images to run detection on
IMAGE_DIR = os.path.join(ROOT_DIR, "images")
# Video file or camera to process - set this to 0 to use your webcam instead of a video file
VIDEO_SOURCE = "test_images/parking.mp4"
# Create a Mask-RCNN model in inference mode
model = MaskRCNN(mode="inference", model_dir=MODEL_DIR, config=MaskRCNNConfig())
# Load pre-trained model
model.load_weights(COCO_MODEL_PATH, by_name=True)
# Location of parking spaces
parked_car_boxes = None
# Load the video file we want to run detection on

video_capture = cv2.VideoCapture(VIDEO_SOURCE)
# How many frames of video we ve seen in a row with a parking space open
free_space_frames = 0
# Have we sent an SMS alert yet?
sms_sent = False
# Loop over each frame of video
while video_capture.isOpened():
success, frame = video_capture.read()
if not success:
break
# Convert the image from BGR color (which OpenCV uses) to RGB color
rgb_image = frame[:, :, ::-1]
# Run the image through the Mask R-CNN model to get results.
results = model.detect([rgb_image], verbose=0)
# Mask R-CNN assumes we are running detection on multiple images.
# We only passed in one image to detect, so only grab the first result.
r = results[0]
# The r variable will now have the results of detection:
# - r[ rois ] are the bounding box of each detected object
# - r[ class_ids ] are the class id (type) of each detected object
# - r[ scores ] are the confidence scores for each detection
# - r[ masks ] are the object masks for each detected object (which gives you the object outline)
if parked_car_boxes is None:
# This is the first frame of video - assume all the cars detected are in parking spaces.
# Save the location of each car as a parking space box and go to the next frame of video.
parked_car_boxes = get_car_boxes(r[ rois ], r[ class_ids ])
else:
# We already know where the parking spaces are. Check if any are currently unoccupied.
# Get where cars are currently located in the frame
car_boxes = get_car_boxes(r[ rois ], r[ class_ids ])
# See how much those cars overlap with the known parking spaces
overlaps = mrcnn.utils.compute_overlaps(parked_car_boxes, car_boxes)
# Assume no spaces are free until we find one that is free
free_space = False
# Loop through each known parking space box
for parking_area, overlap_areas in zip(parked_car_boxes, overlaps):
# For this parking space, find the max amount it was covered by any
# car that was detected in our image (doesn t really matter which car)
max_IoU_overlap = np.max(overlap_areas)
# Get the top-left and bottom-right coordinates of the parking area
y1, x1, y2, x2 = parking_area
# Check if the parking space is occupied by seeing if any car overlaps
# it by more than 0.15 using IoU
if max_IoU_overlap < 0.15:
# Parking space not occupied! Draw a green box around it
cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 3)
# Flag that we have seen at least one open space
free_space = True
else:
# Parking space is still occupied - draw a red box around it

cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 0, 255), 1)
# Write the IoU measurement inside the box
font = cv2.FONT_HERSHEY_DUPLEX
cv2.putText(frame, f"{max_IoU_overlap:0.2}", (x1 + 6, y2 - 6), font, 0.3, (255, 255, 255))
# If at least one space was free, start counting frames
# This is so we don t alert based on one frame of a spot being open.
# This helps prevent the/> if free_space:
free_space_frames += 1
else:
# If no spots are free, reset the count
free_space_frames = 0
# If a space has been free for several frames, we are pretty sure it is really free!
if free_space_frames > 10:
# Write SPACE AVAILABLE!! at the top of the screen
font = cv2.FONT_HERSHEY_DUPLEX
cv2.putText(frame, f"SPACE AVAILABLE!", (10, 150), font, 3.0, (0, 255, 0), 2, cv2.FILLED)
# If we haven t sent an SMS yet, sent it!
if not sms_sent:
print("SENDING SMS!!!")
message = client.messages.create(
body="Parking space open - go go go!",
from_=twilio_phone_number,
to=destination_phone_number
)
sms_sent = True
# Show the frame of video on the screen
cv2.imshow( Video , frame)
# Hit q to quit
if cv2.waitKey(1) & 0xFF == ord( q ):
break
# Clean up everything when finished
video_capture.release()
cv2.destroyAllWindows()

要運行這份代碼,你首先需要安裝 python 3.6+,Matterport Mask R-CNN 以及 OpenCV。

我特意保留了比較簡單的代碼。例如,它只是假設第一幀視頻中出現的任何車輛都是停放的汽車。試用一下,看看你是否能夠提升它的可用性。

不必擔心為了在其它場景中使用而修改代碼。僅僅改變模型尋找的目標 ID,你就能夠將這份代碼完全轉換成另一個東西。例如,假設你在滑雪場工作。經過一些調整,你就可以將這份腳本轉換為一個系統,它可以自動檢測滑雪板從斜坡上跳越,並創建出很酷的滑雪板跳越路線。或者如果你在野生動物保護區工作,你可以將這份代碼轉換成一個統計野生斑馬數量的系統。唯一的限制只是你的想象力。祝你玩得開心!


分享到:


相關文章: