目錄
目標(biāo)
整體流程
背景
詳細(xì)講解
前言
前些天發(fā)現(xiàn)了一個(gè)巨牛的人工智能學(xué)習(xí)網(wǎng)站,通俗易懂,風(fēng)趣幽默,忍不住分享一下給大家。點(diǎn)擊跳轉(zhuǎn)到網(wǎng)站
目標(biāo)
我們想要在一個(gè)實(shí)時(shí)的停車場監(jiān)控視頻中,看看要有多少個(gè)車以及有多少個(gè)空缺車位。然后我們可以標(biāo)記空的,然后來車之后,實(shí)時(shí)告訴應(yīng)該停在那里最方便、最近?。。?shí)現(xiàn)現(xiàn)代的智能無人停車場!
整體流程
采用基于OpenCV的圖像處理方法來解決停車場空車位實(shí)時(shí)監(jiān)測和精準(zhǔn)定位問題。首先,將實(shí)時(shí)監(jiān)控視頻錄像信息轉(zhuǎn)化成圖像信息。對(duì)圖像進(jìn)行形態(tài)學(xué)處理,然后定位停車場關(guān)鍵點(diǎn),使用掩碼圖像與原始圖像融合對(duì)停車位區(qū)域進(jìn)行背景去除,處理之后采用霍夫直線檢測的方法來檢測停車位標(biāo)記線,在畫好線的圖像中進(jìn)行分割,分割出每一個(gè)停車位并編號(hào)。最后利用Keras神經(jīng)網(wǎng)絡(luò)對(duì)有車車位和空車位進(jìn)行訓(xùn)練,對(duì)當(dāng)前圖像中車位是否為空閑進(jìn)行判斷并且實(shí)時(shí)更新,再以圖像流輸出,完成實(shí)時(shí)監(jiān)測空車位的任務(wù)。
背景
由于汽車工業(yè)的發(fā)展迅速以及人們生活水平的提高,我國汽車的保有量不斷增長,而停車位的數(shù)量有限,從而出現(xiàn)停車?yán)щy以及停車效率低,浪費(fèi)時(shí)間甚至造成擁堵、事故等。實(shí)時(shí)檢測并掌握停車位的數(shù)目和空車位的位置信息可以避免資源以及時(shí)間的浪費(fèi),提高效率,也更便于停車位的管理。因此停車位可視化尤為重要。傳統(tǒng)的基于視覺的停車位檢測方法具有檢測精度不高、場景環(huán)境要求高等問題。本文旨在通過邊緣檢測來進(jìn)行停車位劃分,對(duì)圖像背景過濾再定位提取有用區(qū)域,進(jìn)行車位是否空閑的判斷并實(shí)時(shí)更新,再以圖像流輸出。
詳細(xì)講解
首先我們需要了解的就是對(duì)于一個(gè)視頻來說,它是由一幀一幀的圖像構(gòu)成的,所以對(duì)于一段視頻的處理就相當(dāng)于對(duì)于圖像進(jìn)行處理,前一幀圖像和后一幀處理圖像的銜接那么就是一個(gè)視頻處理的結(jié)果。
我們對(duì)于一個(gè)圖像的處理,首先我們利用視頻中的一張圖來看一下停車場的某一幀圖像。
這里就是一個(gè)停車場的其中一幀的一個(gè)圖像,其實(shí)他這里有很多車,如果沒有車的話,也就是說是一個(gè)空車場他的檢測效果會(huì)非常的好。我們首先要對(duì)圖像進(jìn)行幾個(gè)形態(tài)學(xué)的操作。其中包括灰度空間轉(zhuǎn)換、圖像二值化、以及邊緣檢測、輪廓檢測以及掩碼等等操作,下面我們一一介紹一下。
首先我們定義一個(gè)圖像展示函數(shù):
def cv_show(self,name,img):
cv2.imshow(name, img)
cv2.waitKey(0)
cv2.destroyAllWindows()
這里不多過多解釋,就是一個(gè)把圖像展示出來的函數(shù)。
def convert_gray_scale(self,image):
return cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
這里是將圖像轉(zhuǎn)化為灰度圖。
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
lower_red和高于upper_red的部分分別變成0,lower_red~upper_red之間的值變成255,相當(dāng)于過濾背景相當(dāng)于過濾掉一些無用的東西,就是說把灰度級(jí)低于120或者高于255的都設(shè)置成0,0也就是黑色,把120-255中間的都設(shè)置成白色。相當(dāng)于一個(gè)二值化圖像的操作。處理之后的圖像如下圖。
然后我們進(jìn)行了一下邊緣檢測。都是OpenCV的一些形態(tài)學(xué)操作。
def detect_edges(self,image, low_threshold=50, high_threshold=200):
return cv2.Canny(image, low_threshold, high_threshold)
這里是進(jìn)行了一個(gè)邊緣檢測的結(jié)果。因?yàn)檫@里我們需要得到中間停車場的局部區(qū)域進(jìn)行操作,所以我們需要進(jìn)行一個(gè)提取感興趣區(qū)間的一個(gè)操作。
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)
這里這幾個(gè)點(diǎn)是根據(jù)自己的項(xiàng)目而言的,我們目的就是用這六個(gè)點(diǎn)把整個(gè)停車場框起來,然后對(duì)框出來的圖像進(jìn)行一個(gè)提取。也稱之為一個(gè)ROI區(qū)域。結(jié)果是這樣。
這里的坐標(biāo)我們自己進(jìn)行定位操作,然后我們制造一個(gè)掩碼圖像,就是把標(biāo)記的這六個(gè)點(diǎn)規(guī)劃成一個(gè)區(qū)域ROI region,然后把區(qū)域內(nèi)設(shè)置成白色像素值,把區(qū)域外設(shè)置成全黑像素值。然后做一個(gè)相當(dāng)于圖像和掩碼的與操作。得到的結(jié)果就是:
最后得到的ROI區(qū)域就是:
這里我們就得到了一個(gè)停車場的大致輪廓,然后我們開始對(duì)停車場車位進(jìn)行具體操作,首先我們先要檢測一個(gè)停車場直線的操作,使用霍夫直線檢測來做這個(gè)項(xiàng)目。
def hough_lines(self,image):
return cv2.HoughLinesP(image, rho=0.1, theta=np.pi/10, threshold=15, minLineLength=9, maxLineGap=4)
這里霍夫直線檢測是定義好的一個(gè)模型,我們直接調(diào)用就可以。這里的參數(shù)我們介紹一下。
image:表示要處理的圖像。
rho:表示處理的精度。精度越小檢測的直線越精確,精度值設(shè)置的數(shù)值越大,那么檢測的線段就越少。
theta:檢測的直線角度,表示直線的角度不能超過哪個(gè)數(shù)值。如果超過這個(gè)閾值,就不定義為一條直線。
threshold:線的點(diǎn)定義閾值為15,這個(gè)要根據(jù)實(shí)施項(xiàng)目而定,構(gòu)成線的像素點(diǎn)超過15才可以構(gòu)成一條直線。
minLineLength:最小長度,這個(gè)不用過多解釋,線的長度最小就是9.
maxLineGap:線和線之間最大的間隔閾值,離得多近的都認(rèn)為是一條直線。
輸入的圖像需要是邊緣檢測后的結(jié)果,minLineLengh(線的最短長度,比這個(gè)短的都被忽略)和MaxLineCap(兩條直線之間的最大間隔,小于此值,認(rèn)為是一條直線)。rho距離精度,theta角度精度,threshod超過設(shè)定閾值才被檢測出線段。
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
這里面對(duì)檢測到的霍夫直線繼續(xù)做一個(gè)過濾的操作,如果直線的長度大于25,小于55,我們就添加到列表當(dāng)中,并且設(shè)定一條直線的左右端點(diǎn)坐標(biāo)的差值不能超過1.這樣的直線我們通通過濾出來。
這里檢測的結(jié)果如圖,這里因?yàn)檐噺S里有很多車,如果是一個(gè)空車場的話,檢測的結(jié)果會(huì)非常好。做完檢測之后,我們想要的是對(duì)于停車場的12列,我們對(duì)每一列都進(jìn)行一個(gè)提取操作,比如我們得到12列之后,然后我們在對(duì)每一列分出具體的一個(gè)一個(gè)車位。然后對(duì)于第一列和第十二列這種單車位,和其他列的雙車位的處理方法還是不同的,具體的我們來看一下。
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))
首先我們還是過濾掉一些直線。
import operator
list1 = sorted(cleaned, key=operator.itemgetter(0, 1))
對(duì)于這十二列,每一列的左上角的坐標(biāo)點(diǎn)我們是可以得到x1-x12的我們要對(duì)這些列進(jìn)行一次排序操作。讓計(jì)算機(jī)識(shí)別出哪一列是第一列,哪一列是第十二列。
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
這里就是做了一下對(duì)于所有排序好的直線進(jìn)行了一個(gè)歸類操作,把哪些直線歸為一列。并且進(jìn)行添加。直到把每一列都進(jìn)行分出來。
rects = {}
i = 0
for key in clusters:
all_list = clusters[key]
cleaned = list(set(all_list))#一列中的所有直線的坐標(biāo)信息
if len(cleaned) > 5:
cleaned = sorted(cleaned, key=lambda tup: tup[1])#對(duì)直線進(jìn)行排序
avg_y1 = cleaned[0][1]#這個(gè)對(duì)于一列來說是固定的
avg_y2 = cleaned[-1][1]#這個(gè)對(duì)于一列來說是固定的
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))
然后我們對(duì)每一列進(jìn)行操作,把每一列的每一個(gè)車位的所有坐標(biāo)信息提取出來。然后再通過得到的坐標(biāo)及進(jìn)行畫出來這個(gè)矩形。
buff = 7#微調(diào)數(shù)值
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
我們在這個(gè)期間又對(duì)矩形進(jìn)行了手動(dòng)微調(diào)。
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 = {} # 字典:一個(gè)車位對(duì)應(yīng)一個(gè)位置
tot_spots = 0
#微調(diào)
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)
# 計(jì)算數(shù)量
if key == 0 or key == (len(rects) -1):
tot_spots += num_splits +1
else:
tot_spots += 2*(num_splits +1)
# 字典對(duì)應(yīng)好
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
處理的結(jié)果是:
這里我們把所有車位都劃分出來了。
然后我們想要通過使用keras神經(jīng)網(wǎng)絡(luò)對(duì)車位有沒有車進(jìn)行一個(gè)學(xué)習(xí)!讓神經(jīng)網(wǎng)絡(luò)預(yù)測到底車位到底有沒有車。整個(gè)keras神經(jīng)網(wǎng)絡(luò)的訓(xùn)練過程如下。我們使用的是VGG16網(wǎng)絡(luò)進(jìn)行訓(xùn)練做一個(gè)二分類的任務(wù),也就是車位有沒有車。對(duì)于車位的訓(xùn)練圖像我們可以看一下。通過這一代碼我們對(duì)車位有無車進(jìn)行提取。
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)
這里是車位沒有車,那么有車的如下。
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])
這里我們使用了卷積神經(jīng)網(wǎng)絡(luò)對(duì)有無車位進(jìn)行訓(xùn)練,通過神經(jīng)網(wǎng)絡(luò)的訓(xùn)練我們就開始對(duì)一幀圖像進(jìn)行判斷。得到的結(jié)果是:
def make_prediction(self,image,model,class_dictionary):#預(yù)測
#預(yù)處理
img = image/255.
#轉(zhuǎn)換成4D tensor
image = np.expand_dims(img, axis=0)
# 用訓(xùn)練好的模型進(jìn)行訓(xùn)練
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
這里做了一個(gè)在圖像中訓(xùn)練的結(jié)果,我們來看一下。
預(yù)測結(jié)果是一共檢測到555個(gè)車位,目前空閑車位一共有113個(gè)。然后我們對(duì)視頻進(jìn)行相同的操作,主要就是把視頻進(jìn)行分割成一幀一幀的圖像,然后對(duì)每一幀圖像進(jìn)行下面對(duì)于圖片的操作。這樣我們就可以以視頻流的形式進(jìn)行輸出了!這就是整個(gè)項(xiàng)目的流程。
這里就是利用keras卷積神經(jīng)網(wǎng)絡(luò)一直對(duì)圖像進(jìn)行訓(xùn)練測試,得到實(shí)時(shí)的車位信息。至此我們的這個(gè)項(xiàng)目就結(jié)束了。針對(duì)車來車往的停車場內(nèi)停車效率問題提出了基于OpenCV的停車位空閑狀態(tài)檢測的方法,以視頻中的每幀圖像為單位,使用灰度化、霍夫直線檢測等方法對(duì)數(shù)據(jù)進(jìn)行預(yù)處理、最后將處理完的數(shù)據(jù)利用Keras神經(jīng)網(wǎng)絡(luò)模型訓(xùn)練和預(yù)測,來判斷停車位中是否空閑。測試結(jié)果顯示此方法可以快速完成實(shí)時(shí)監(jiān)測停車場內(nèi)車位狀態(tài)的任務(wù)。來提高停車場內(nèi)停車的效率,但由于停車場內(nèi)的停車標(biāo)位線存在維護(hù)不及時(shí),仍然會(huì)存在停車位標(biāo)線不清晰、遮擋嚴(yán)重等問題,會(huì)影響檢測精度。雖然在圖像預(yù)處理已經(jīng)減少了計(jì)算量,但計(jì)算次數(shù)多、程序處理耗時(shí)長,后續(xù)將針對(duì)文中的不足進(jìn)行進(jìn)一步的研究與改進(jìn)。在未來的研究工作中可以在圖像預(yù)處理進(jìn)程中計(jì)算量大的問題上嘗試使用更快速的算法來進(jìn)一步提高此方法耗時(shí)長的問題。
文章來源:http://www.zghlxwxcb.cn/news/detail-752049.html
如果覺得博主的文章還不錯(cuò)或者您用得到的話,可以免費(fèi)的關(guān)注一下博主,如果三連收藏支持就更好啦!這就是給予我最大的支持!文章來源地址http://www.zghlxwxcb.cn/news/detail-752049.html
到了這里,關(guān)于【opencv】計(jì)算機(jī)視覺:停車場車位實(shí)時(shí)識(shí)別的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!