文章目錄
- 項目目标
- 項目整體流程
- 項目背景
- 項目詳細講解
項目目标
我們想要在一個實時的停車場監控視訊中,看看要有多少個車以及有多少個空缺車位。然後我們可以标記空的,然後來車之後,實時告訴應該停在那裡最友善、最近!!!實作現代的智能無人停車場!
項目整體流程
采用基于OpenCV的圖像處理方法來解決停車場空車位實時監測和精準定位問題。首先,将實時監控視訊錄像資訊轉化成圖像資訊。對圖像進行形态學處理,然後定位停車場關鍵點,使用掩碼圖像與原始圖像融合對停車位區域進行背景去除,處理之後采用霍夫直線檢測的方法來檢測停車位标記線,在畫好線的圖像中進行分割,分割出每一個停車位并編号。最後利用Keras神經網絡對有車車位和空車位進行訓練,對目前圖像中車位是否為空閑進行判斷并且實時更新,再以圖像流輸出,完成實時監測空車位的任務。
項目背景
由于汽車工業的發展迅速以及人們生活水準的提高,大陸汽車的保有量不斷增長,而停車位的數量有限,進而出現停車困難以及停車效率低,浪費時間甚至造成擁堵、事故等。實時檢測并掌握停車位的數目和空車位的位置資訊可以避免資源以及時間的浪費,提高效率,也更便于停車位的管理。是以停車位可視化尤為重要。傳統的基于視覺的停車位檢測方法具有檢測精度不高、場景環境要求高等問題。本文旨在通過邊緣檢測來進行停車位劃分,對圖像背景過濾再定位提取有用區域,進行車位是否空閑的判斷并實時更新,再以圖像流輸出。
項目詳細講解
首先我們需要了解的就是對于一個視訊來說,它是由一幀一幀的圖像構成的,是以對于一段視訊的處理就相當于對于圖像進行處理,前一幀圖像和後一幀處理圖像的銜接那麼就是一個視訊處理的結果。
我們對于一個圖像的處理,首先我們利用視訊中的一張圖來看一下停車場的某一幀圖像。
這裡就是一個停車場的其中一幀的一個圖像,其實他這裡有很多車,如果沒有車的話,也就是說是一個空車場他的檢測效果會非常的好。我們首先要對圖像進行幾個形态學的操作。其中包括灰階空間轉換、圖像二值化、以及邊緣檢測、輪廓檢測以及掩碼等等操作,下面我們一一介紹一下。
首先我們定義一個圖像展示函數:
def cv_show(self,name,img):
cv2.imshow(name, img)
cv2.waitKey(0)
cv2.destroyAllWindows()
1234
這裡不多過多解釋,就是一個把圖像展示出來的函數。
def convert_gray_scale(self,image):
return cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
12
這裡是将圖像轉化為灰階圖。
def select_rgb_white_yellow(self,image):
#過濾掉背景
lower = np.uint8([120, 120, 120])
upper = np.uint8([255, 255, 255])
white_mask = cv2.inRange(image, lower, upper)
self.cv_show('white_mask',white_mask)
masked = cv2.bitwise_and(image, image, mask = white_mask)
self.cv_show('masked',masked)
return masked
12345678910
lower_red和高于upper_red的部分分别變成0,lower_red~upper_red之間的值變成255,相當于過濾背景相當于過濾掉一些無用的東西,就是說把灰階級低于120或者高于255的都設定成0,0也就是黑色,把120-255中間的都設定成白色。相當于一個二值化圖像的操作。處理之後的圖像如下圖。
然後我們進行了一下邊緣檢測。都是OpenCV的一些形态學操作。
def detect_edges(self,image, low_threshold=50, high_threshold=200):
return cv2.Canny(image, low_threshold, high_threshold)
12
這裡是進行了一個邊緣檢測的結果。因為這裡我們需要得到中間停車場的局部區域進行操作,是以我們需要進行一個提取感興趣區間的一個操作。
def select_region(self,image):
rows, cols = image.shape[:2]
pt_1 = [cols*0.05, rows*0.90]
pt_2 = [cols*0.05, rows*0.70]
pt_3 = [cols*0.30, rows*0.55]
pt_4 = [cols*0.6, rows*0.15]
pt_5 = [cols*0.90, rows*0.15]
pt_6 = [cols*0.90, rows*0.90]
vertices = np.array([[pt_1, pt_2, pt_3, pt_4, pt_5, pt_6]], dtype=np.int32)
point_img = image.copy()
point_img = cv2.cvtColor(point_img, cv2.COLOR_GRAY2RGB)
for point in vertices[0]:
cv2.circle(point_img, (point[0],point[1]), 10, (0,0,255), 4)
self.cv_show('point_img',point_img)
return self.filter_region(image, vertices)
12345678910111213141516
這裡這幾個點是根據自己的項目而言的,我們目的就是用這六個點把整個停車場框起來,然後對框出來的圖像進行一個提取。也稱之為一個ROI區域。結果是這樣。
這裡的坐标我們自己進行定位操作,然後我們制造一個掩碼圖像,就是把标記的這六個點規劃成一個區域ROI region,然後把區域内設定成白色像素值,把區域外設定成全黑像素值。然後做一個相當于圖像和掩碼的與操作。得到的結果就是:
最後得到的ROI區域就是:
這裡我們就得到了一個停車場的大緻輪廓,然後我們開始對停車場車位進行具體操作,首先我們先要檢測一個停車場直線的操作,使用霍夫直線檢測來做這個項目。
def hough_lines(self,image):
return cv2.HoughLinesP(image, rho=0.1, theta=np.pi/10, threshold=15, minLineLength=9, maxLineGap=4)
12
這裡霍夫直線檢測是定義好的一個模型,我們直接調用就可以。這裡的參數我們介紹一下。
image:表示要處理的圖像。
rho:表示處理的精度。精度越小檢測的直線越精确,精度值設定的數值越大,那麼檢測的線段就越少。
theta:檢測的直線角度,表示直線的角度不能超過哪個數值。如果超過這個門檻值,就不定義為一條直線。
threshold:線的點定義門檻值為15,這個要根據實施項目而定,構成線的像素點超過15才可以構成一條直線。
minLineLength:最小長度,這個不用過多解釋,線的長度最小就是9.
maxLineGap:線和線之間最大的間隔門檻值,離得多近的都認為是一條直線。
輸入的圖像需要是邊緣檢測後的結果,minLineLengh(線的最短長度,比這個短的都被忽略)和MaxLineCap(兩條直線之間的最大間隔,小于此值,認為是一條直線)。rho距離精度,theta角度精度,threshod超過設定門檻值才被檢測出線段。
def draw_lines(self,image, lines, color=[255, 0, 0], thickness=2, make_copy=True):
# 過濾霍夫變換檢測到直線
if make_copy:
image = np.copy(image)
cleaned = []
for line in lines:
for x1,y1,x2,y2 in line:
if abs(y2-y1) <=1 and abs(x2-x1) >=25 and abs(x2-x1) <= 55:
cleaned.append((x1,y1,x2,y2))
cv2.line(image, (x1, y1), (x2, y2), color, thickness)
print(" No lines detected: ", len(cleaned))
return image
123456789101112
這裡面對檢測到的霍夫直線繼續做一個過濾的操作,如果直線的長度大于25,小于55,我們就添加到清單當中,并且設定一條直線的左右端點坐标的內插補點不能超過1.這樣的直線我們通通過濾出來。
這裡檢測的結果如圖,這裡因為車廠裡有很多車,如果是一個空車場的話,檢測的結果會非常好。做完檢測之後,我們想要的是對于停車場的12列,我們對每一列都進行一個提取操作,比如我們得到12列之後,然後我們在對每一列分出具體的一個一個車位。然後對于第一列和第十二列這種單車位,和其他列的雙車位的處理方法還是不同的,具體的我們來看一下。
def identify_blocks(self,image, lines, make_copy=True):
if make_copy:
new_image = np.copy(image)
#Step 1: 過濾部分直線
cleaned = []
for line in lines:
for x1,y1,x2,y2 in line:
if abs(y2-y1) <=1 and abs(x2-x1) >=25 and abs(x2-x1) <= 55:
cleaned.append((x1,y1,x2,y2))
123456789
首先我們還是過濾掉一些直線。
import operator
list1 = sorted(cleaned, key=operator.itemgetter(0, 1))
12
對于這十二列,每一列的左上角的坐标點我們是可以得到x1-x12的我們要對這些列進行一次排序操作。讓計算機識别出哪一列是第一列,哪一列是第十二列。
clusters = {}
dIndex = 0
clus_dist = 10
for i in range(len(list1) - 1):
distance = abs(list1[i+1][0] - list1[i][0])
if distance <= clus_dist:
if not dIndex in clusters.keys(): clusters[dIndex] = []
clusters[dIndex].append(list1[i])
clusters[dIndex].append(list1[i + 1])
else:
dIndex += 1
12345678910111213
這裡就是做了一下對于所有排序好的直線進行了一個歸類操作,把哪些直線歸為一列。并且進行添加。直到把每一列都進行分出來。
rects = {}
i = 0
for key in clusters:
all_list = clusters[key]
cleaned = list(set(all_list))#一列中的所有直線的坐标資訊
if len(cleaned) > 5:
cleaned = sorted(cleaned, key=lambda tup: tup[1])#對直線進行排序
avg_y1 = cleaned[0][1]#這個對于一列來說是固定的
avg_y2 = cleaned[-1][1]#這個對于一列來說是固定的
avg_x1 = 0
avg_x2 = 0
for tup in cleaned:
avg_x1 += tup[0]
avg_x2 += tup[2]
avg_x1 = avg_x1/len(cleaned)
avg_x2 = avg_x2/len(cleaned)
rects[i] = (avg_x1, avg_y1, avg_x2, avg_y2)
i += 1
print("Num Parking Lanes: ", len(rects))
1234567891011121314151617181920
然後我們對每一列進行操作,把每一列的每一個車位的所有坐标資訊提取出來。然後再通過得到的坐标及進行畫出來這個矩形。
buff = 7#微調數值
for key in rects:
tup_topLeft = (int(rects[key][0] - buff), int(rects[key][1]))
tup_botRight = (int(rects[key][2] + buff), int(rects[key][3]))
cv2.rectangle(new_image, tup_topLeft,tup_botRight,(0,255,0),3)
return new_image, rects
123456
我們在這個期間又對矩形進行了手動微調。
def draw_parking(self,image, rects, make_copy = True, color=[255, 0, 0], thickness=2, save = True):
if make_copy:
new_image = np.copy(image)
gap = 15.5#車位間的差距是15.5
spot_dict = {} # 字典:一個車位對應一個位置
tot_spots = 0
#微調
adj_y1 = {0: 20, 1:-10, 2:0, 3:-11, 4:28, 5:5, 6:-15, 7:-15, 8:-10, 9:-30, 10:9, 11:-32}
adj_y2 = {0: 30, 1: 50, 2:15, 3:10, 4:-15, 5:15, 6:15, 7:-20, 8:15, 9:15, 10:0, 11:30}
adj_x1 = {0: -8, 1:-15, 2:-15, 3:-15, 4:-15, 5:-15, 6:-15, 7:-15, 8:-10, 9:-10, 10:-10, 11:0}
adj_x2 = {0: 0, 1: 15, 2:15, 3:15, 4:15, 5:15, 6:15, 7:15, 8:10, 9:10, 10:10, 11:0}
for key in rects:
tup = rects[key]
x1 = int(tup[0]+ adj_x1[key])
x2 = int(tup[2]+ adj_x2[key])
y1 = int(tup[1] + adj_y1[key])
y2 = int(tup[3] + adj_y2[key])
cv2.rectangle(new_image, (x1, y1),(x2,y2),(0,255,0),2)
num_splits = int(abs(y2-y1)//gap)
for i in range(0, num_splits+1):
y = int(y1 + i*gap)
cv2.line(new_image, (x1, y), (x2, y), color, thickness)
if key > 0 and key < len(rects) -1 :
#豎直線
x = int((x1 + x2)/2)
cv2.line(new_image, (x, y1), (x, y2), color, thickness)
# 計算數量
if key == 0 or key == (len(rects) -1):
tot_spots += num_splits +1
else:
tot_spots += 2*(num_splits +1)
# 字典對應好
if key == 0 or key == (len(rects) -1):
for i in range(0, num_splits+1):
cur_len = len(spot_dict)
y = int(y1 + i*gap)
spot_dict[(x1, y, x2, y+gap)] = cur_len +1
else:
for i in range(0, num_splits+1):
cur_len = len(spot_dict)
y = int(y1 + i*gap)
x = int((x1 + x2)/2)
spot_dict[(x1, y, x, y+gap)] = cur_len +1
spot_dict[(x, y, x2, y+gap)] = cur_len +2
print("total parking spaces: ", tot_spots, cur_len)
if save:
filename = 'with_parking.jpg'
cv2.imwrite(filename, new_image)
return new_image, spot_dict
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152
處理的結果是:
這裡我們把所有車位都劃分出來了。
然後我們想要通過使用keras神經網絡對車位有沒有車進行一個學習!讓神經網絡預測到底車位到底有沒有車。整個keras神經網絡的訓練過程如下。我們使用的是VGG16網絡進行訓練做一個二分類的任務,也就是車位有沒有車。對于車位的訓練圖像我們可以看一下。通過這一代碼我們對車位有無車進行提取。
def save_images_for_cnn(self,image, spot_dict, folder_name ='cnn_data'):
for spot in spot_dict.keys():
(x1, y1, x2, y2) = spot
(x1, y1, x2, y2) = (int(x1), int(y1), int(x2), int(y2))
#裁剪
spot_img = image[y1:y2, x1:x2]
spot_img = cv2.resize(spot_img, (0,0), fx=2.0, fy=2.0)
spot_id = spot_dict[spot]
filename = 'spot' + str(spot_id) +'.jpg'
print(spot_img.shape, filename, (x1,x2,y1,y2))
cv2.imwrite(os.path.join(folder_name, filename), spot_img)
12345678910111213
這裡是車位沒有車,那麼有車的如下。
files_train = 0
files_validation = 0
cwd = os.getcwd()
folder = 'train_data/train'
for sub_folder in os.listdir(folder):
path, dirs, files = next(os.walk(os.path.join(folder,sub_folder)))
files_train += len(files)
folder = 'train_data/test'
for sub_folder in os.listdir(folder):
path, dirs, files = next(os.walk(os.path.join(folder,sub_folder)))
files_validation += len(files)
print(files_train,files_validation)
img_width, img_height = 48, 48
train_data_dir = "train_data/train"
validation_data_dir = "train_data/test"
nb_train_samples = files_train
nb_validation_samples = files_validation
batch_size = 32
epochs = 15
num_classes = 2
model = applications.VGG16(weights='imagenet', include_top=False, input_shape = (img_width, img_height, 3))
for layer in model.layers[:10]:
layer.trainable = False
x = model.output
x = Flatten()(x)
predictions = Dense(num_classes, activation="softmax")(x)
model_final = Model(input = model.input, output = predictions)
model_final.compile(loss = "categorical_crossentropy",
optimizer = optimizers.SGD(lr=0.0001, momentum=0.9),
metrics=["accuracy"])
train_datagen = ImageDataGenerator(
rescale = 1./255,
horizontal_flip = True,
fill_mode = "nearest",
zoom_range = 0.1,
width_shift_range = 0.1,
height_shift_range=0.1,
rotation_range=5)
test_datagen = ImageDataGenerator(
rescale = 1./255,
horizontal_flip = True,
fill_mode = "nearest",
zoom_range = 0.1,
width_shift_range = 0.1,
height_shift_range=0.1,
rotation_range=5)
train_generator = train_datagen.flow_from_directory(
train_data_dir,
target_size = (img_height, img_width),
batch_size = batch_size,
class_mode = "categorical")
validation_generator = test_datagen.flow_from_directory(
validation_data_dir,
target_size = (img_height, img_width),
class_mode = "categorical")
checkpoint = ModelCheckpoint("car1.h5", monitor='val_acc', verbose=1, save_best_only=True, save_weights_only=False, mode='auto', period=1)
early = EarlyStopping(monitor='val_acc', min_delta=0, patience=10, verbose=1, mode='auto')
history_object = model_final.fit_generator(
train_generator,
samples_per_epoch = nb_train_samples,
epochs = epochs,
validation_data = validation_generator,
nb_val_samples = nb_validation_samples,
callbacks = [checkpoint, early])
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
這裡我們使用了卷積神經網絡對有無車位進行訓練,通過神經網絡的訓練我們就開始對一幀圖像進行判斷。得到的結果是:
def make_prediction(self,image,model,class_dictionary):#預測
#預處理
img = image/255.
#轉換成4D tensor
image = np.expand_dims(img, axis=0)
# 用訓練好的模型進行訓練
class_predicted = model.predict(image)
inID = np.argmax(class_predicted[0])
label = class_dictionary[inID]
return label
def predict_on_image(self,image, spot_dict , model,class_dictionary,make_copy=True, color = [0, 255, 0], alpha=0.5):
if make_copy:
new_image = np.copy(image)
overlay = np.copy(image)
self.cv_show('new_image',new_image)
cnt_empty = 0
all_spots = 0
for spot in spot_dict.keys():
all_spots += 1
(x1, y1, x2, y2) = spot
(x1, y1, x2, y2) = (int(x1), int(y1), int(x2), int(y2))
spot_img = image[y1:y2, x1:x2]
spot_img = cv2.resize(spot_img, (48, 48))
label = self.make_prediction(spot_img,model,class_dictionary)
if label == 'empty':
cv2.rectangle(overlay, (int(x1),int(y1)), (int(x2),int(y2)), color, -1)
cnt_empty += 1
cv2.addWeighted(overlay, alpha, new_image, 1 - alpha, 0, new_image)
cv2.putText(new_image, "Available: %d spots" %cnt_empty, (30, 95),
cv2.FONT_HERSHEY_SIMPLEX,
0.7, (255, 255, 255), 2)
cv2.putText(new_image, "Total: %d spots" %all_spots, (30, 125),
cv2.FONT_HERSHEY_SIMPLEX,
0.7, (255, 255, 255), 2)
save = False
if save:
filename = 'with_marking.jpg'
cv2.imwrite(filename, new_image)
self.cv_show('new_image',new_image)
return new_image
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
這裡做了一個在圖像中訓練的結果,我們來看一下。
預測結果是一共檢測到555個車位,目前空閑車位一共有113個。然後我們對視訊進行相同的操作,主要就是把視訊進行分割成一幀一幀的圖像,然後對每一幀圖像進行下面對于圖檔的操作。這樣我們就可以以視訊流的形式進行輸出了!這就是整個項目的流程。
這裡就是利用keras卷積神經網絡一直對圖像進行訓練測試,得到實時的車位資訊。至此我們的這個項目就結束了。針對車來車往的停車場内停車效率問題提出了基于OpenCV的停車位空閑狀态檢測的方法,以視訊中的每幀圖像為機關,使用灰階化、霍夫直線檢測等方法對資料進行預處理、最後将處理完的資料利用Keras神經網絡模型訓練和預測,來判斷停車位中是否空閑。測試結果顯示此方法可以快速完成實時監測停車場内車位狀态的任務。來提高停車場内停車的效率,但由于停車場内的停車标位線存在維護不及時,仍然會存在停車位标線不清晰、遮擋嚴重等問題,會影響檢測精度。雖然在圖像預處理已經減少了計算量,但計算次數多、程式處理耗時長,後續将針對文中的不足進行進一步的研究與改進。在未來的研究工作中可以在圖像預處理程序中計算量大的問題上嘗試使用更快速的算法來進一步提高此方法耗時長的問題。