• 基于OpenCV与Keras的停车场车位自动识别系统


    本项目旨在利用计算机视觉技术和深度学习算法,实现对停车场车位状态的实时自动识别。通过摄像头监控停车场内部,系统能够高效准确地辨认车位是否被占用,为车主提供实时的空闲车位信息,同时为停车场管理者提供智能化的车位管理工具。该系统结合了OpenCV的强大图像处理能力与Keras的易用性,便于快速构建和部署。

    技术栈:

    • OpenCV:用于图像的预处理,包括视频捕获、图像处理(如灰度转换、滤波、边缘检测等)以及ROI(感兴趣区域)的选取,为深度学习模型提供高质量的输入。
    • Keras:基于TensorFlow的高级API,用于搭建和训练深度学习模型。项目中,可能采用预训练模型(如VGGNet、ResNet等)进行迁移学习,通过微调模型来适应车位识别任务,或者从零开始构建卷积神经网络(CNN)模型进行车位状态分类。

    项目流程:

    1. 数据收集与预处理:首先,通过摄像头录制停车场视频,从中截取包含车位的画面,人工标注车位状态(如空闲或占用)。接着,对图像进行归一化、增强等预处理,创建训练和验证数据集。

    2. 模型训练:使用Keras构建深度学习模型,加载预处理后的数据集进行训练。训练过程中,可能涉及调整超参数、优化器选择、损失函数配置等,以达到理想的分类性能。

    3. 模型验证与优化:在验证集上评估模型性能,根据准确率、召回率等指标调整模型结构或参数,进行模型优化。

    4. 实时检测与应用:将训练好的模型集成到OpenCV中,实现实时视频流处理。系统不断捕获停车场的视频帧,进行图像处理后,通过模型预测车位状态。识别结果以可视化方式展示,如在视频流中标记车位为空闲或占用,并可进一步集成到停车场管理系统,实现车位引导、计费等功能。

    特色与优势:

    • 实时性:系统能够实时监控车位状态,及时更新信息,提高停车场的运营效率。
    • 准确性:深度学习模型具有强大的特征学习能力,即使在复杂光照、遮挡等条件下也能保持较高识别准确率。
    • 易部署与扩展:基于OpenCV和Keras的方案易于开发和调试,且模型可根据新数据持续优化,便于后续维护和功能升级。
    • 智能化管理:为停车场管理者提供精准的车位占用情况,有助于优化停车资源分配,提升用户体验。

    总结: 此项目通过融合OpenCV的图像处理能力和Keras构建的深度学习模型,实现了对停车场车位状态的自动识别,是智能交通系统和智慧城市应用中的一个重要组成部分,具有广泛的应用前景和社会价值。

    1. from __future__ import division # 改变 Python 2 中除法操作符 / 的默认行为,使其表现得像 Python 3 中的除法操作符,结果会保留小数部分
    2. import matplotlib.pyplot as plt # 用于创建图表和可视化数据的 Python 库
    3. import cv2
    4. import os, glob # glob文件名匹配的模块
    5. import numpy as np
    6. from PIL import Image
    7. from keras.applications.imagenet_utils import preprocess_input
    8. from keras.models import load_model
    9. from keras.preprocessing import image
    10. from Parking import Parking
    11. import pickle # 序列化和反序列化对象的标准模块
    12. cwd = os.getcwd() # 获取当前工作目录
    13. def img_process(test_images, park):
    14. # 过滤背景,低于lower_red和高于upper_red的部分分别编程0,lower_red~upper_red之间的值编程255
    15. # map 函数用于将一个函数应用到可迭代对象的每个元素,并返回结果
    16. # 通过 list 函数将其转换为列表
    17. white_yellow_images = list(map(park.select_rgb_white_yellow,test_images))
    18. park.show_images(white_yellow_images)
    19. # 转灰度图
    20. gray_images = list(map(park.convert_gray_scale, white_yellow_images))
    21. park.show_images(gray_images)
    22. # 进行边缘检测
    23. edge_images = list(map(lambda image: park.detect_edges(image),gray_images))
    24. park.show_images(edge_images)
    25. # 根据需要设定屏蔽区域
    26. roi_images = list(map(park.select_region, edge_images))
    27. park.show_images(roi_images)
    28. # 霍夫变换,得出直线
    29. list_of_lines= list(map(park.hough_line, roi_images))
    30. # zip 函数来同时迭代 test_images 和 list_of_lines 中的元素
    31. line_images = []
    32. for image,lines in zip(test_images,list_of_lines):
    33. line_images.append(park.draw_lines(image,lines))
    34. park.show_images(line_images)
    35. rect_images = []
    36. rect_coords = [] # 列矩形
    37. for image,lines in zip(test_images, list_of_lines):
    38. # 过滤部分直线,对直线进行排序,得出每一列的起始点和终止点,并将列矩形画出来
    39. new_image,rects = park.identify_blocks(image,lines)
    40. rect_images.append(new_image)
    41. rect_coords.append(rects)
    42. park.show_images(rect_images)
    43. delineated = []
    44. spot_pos = []
    45. for image,rects in zip(test_images, rect_coords):
    46. # 在图上将停车位画出来,并返回字典{坐标:车位序号}
    47. new_image,spot_dict = park.draw_parking(image,rects)
    48. delineated.append(new_image)
    49. spot_pos.append(spot_dict)
    50. park.show_images(delineated)
    51. final_spot_dict = spot_pos[1]
    52. print(len(final_spot_dict))
    53. with open('spot_dict.pickle','wb') as handle:
    54. pickle.dump(final_spot_dict,handle,property==pickle.HIGHEST_PROTOCOL)
    55. park.save_images_for_cnn(test_images[0],final_spot_dict)
    56. return final_spot_dict
    57. def keras_model(weights_path):
    58. model = load_model(weights_path)
    59. return model
    60. def img_test(test_image,final_spot_dict,model,class_dictionary):
    61. for i in range (len(test_images)):
    62. predicted_images = park.predict_on_image(test_images[i],final_spot_dict,model,class_dictionary)
    63. def video_test(video_name,final_spot_dict,model,class_dictionary):
    64. name = video_name
    65. cap = cv2.VideoCapture(name)
    66. park.predict_on_video(name,final_spot_dict,model,class_dictionary,ret=True)
    67. if __name__ == '__main__':
    68. test_images = [plt.imread(path) for path in glob.glob('test_images/*.jpg')]
    69. weights_path = 'car1.h5'
    70. video_name = 'parking_video.mp4'
    71. class_dictionary = {}
    72. class_dictionary[0] = 'empty'
    73. class_dictionary[1] = 'occupied'
    74. park = Parking()
    75. park.show_image(test_images)
    76. final_spot_dict = img_process(test_images, park)
    77. model = keras_model(weights_path)
    78. img_test(test_images,final_spot_dict,model,class_dictionary)
    79. video_test(video_name,final_spot_dict,model,class_dictionary)

    parking py

    1. import matplotlib.pyplot as plt
    2. import cv2
    3. import os,glob
    4. import numpy as np
    5. class Parking:
    6. def show_images(self, images, cmap=None):
    7. cols = 2
    8. rows = (len(images) + 1)//cols # //为整除运算符
    9. plt.figure(figsize=(15,12)) # 创建一个图形窗口,并指定其大小为 15x12 英寸
    10. for i,image in enumerate(images):
    11. plt.subplot(rows, cols, i+1) # 在当前图形窗口中创建一个子图,i+1 是因为子图的编号是从 1 开始的
    12. # 检查图像的维度,如果图像是二维的(灰度图像),则将颜色映射设置为灰度,否则保持传入的 cmap 参数不变
    13. cmap = 'gray' if len(image.shape)==2 else cmap
    14. plt.imshow(image, cmap=cmap)
    15. plt.xticks([]) # 去除 x 轴和 y 轴的刻度标签
    16. plt.yticks([])
    17. plt.tight_layout(pad=0,h_pad=0,w_pad=0) # 调整子图之间的间距
    18. plt.show()
    19. def cv_show(self, name, img):
    20. cv2.imshow(name, img)
    21. cv2.waitKey(0)
    22. cv2.destroyAllWindows()
    23. def select_rgb_white_yellow(self,image):
    24. # 过滤掉背景
    25. lower = np.uint8([120,120,120])
    26. upper = np.uint8([255,255,255])
    27. # 低于lower_red和高于upper_red的部分分别编程0,lower_red~upper_red之间的值编程255,相当于过滤背景
    28. white_mask = cv2.inRange(image,lower,upper)
    29. self.cv_show('white_mask',white_mask)
    30. # 与操作
    31. masked = cv2.bitwise_and(image, image, mask=white_mask)
    32. self.cv_show('masked',masked)
    33. return masked
    34. def convert_gray_scale(selfself,image):
    35. return cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    36. # 提取图像中的边缘信息
    37. # 返回的是一个二值图像,其中边缘点被标记为白色(255),而非边缘点被标记为黑色(0
    38. def detect_edges(self, image, low_threshole=50, high_threshold=200):
    39. return cv2.Canny(image, low_threshole, high_threshold)
    40. def filter_region(self, image, vertices):
    41. # 剔除掉不需要的地方
    42. mask = np.zeros_like(image) # 创建和原图一样大的图,置零
    43. if len(mask.shape)==2: # 是否为一张灰度图
    44. cv2.fillPoly(mask, vertices, 255) # 使用顶点vertices在mask上填充多边形,并置为255白色
    45. self.cv_show('mask',mask)
    46. return cv2.bitwise_and(image,mask)
    47. def select_region(self, image):
    48. # 手动选择区域
    49. # 首先,通过顶点定义多边形。
    50. rows, cols = image.shape[:2] # h和w
    51. pt_1 = [cols*0.05, rows*0.09]
    52. pt_2 = [cols*0.05, rows*0.70]
    53. pt_3 = [cols*0.30, rows*0.55]
    54. pt_4 = [cols*0.6, rows*0.15]
    55. pt_5 = [cols*0.90, rows*0.15]
    56. pt_6 = [cols*0.90, rows*0.90]
    57. vertices = np.array([[pt_1, pt_2, pt_3, pt_4, pt_5, pt_6]],dtype=np.int32)
    58. point_img = image.copy()
    59. point_img = cv2.cvtColor(point_img, cv2.COLOR_BGR2GRAY)
    60. for point in vertices[0]:
    61. cv2.circle(point_img,(point[0], point[1]), 10, (0,0,255), 4)
    62. self.cv_show('point_img',point_img)
    63. return self.filter_region(image, vertices)
    64. # 霍夫变换,得出直线
    65. def hough_line(self,image):
    66. # 检测输入图像中的直线,并返回检测到的直线的端点坐标
    67. # 输入的图像需要是边缘检测后的结果
    68. # minLineLength(线的最短长度,比这个短的都被忽略)和MaxLineCap(两条直线之间的最大间隔,小于辞职,认为是一条直线)
    69. # rho以像素为单位的距离分辨率,通常设置为 1 像素
    70. # thrta角度精度
    71. # threshod直线交点数量阈值。只有累加器中某个点的投票数高于此阈值,才被认为是一条直线。
    72. return cv2.HoughLinesP(image, rho=0.1, thrta=np.pi/10, threshold=15,minLineLength=9,maxLineGap=4)
    73. # 过滤霍夫变换检测到的直线
    74. def draw_lines(self, image, lines, color=[255,0,0], thickness=2, make_copy=True):
    75. if make_copy:
    76. image = np.copy(image)
    77. cleaned = []
    78. for line in lines:
    79. for x1,y1,x2,y2 in line:
    80. if abs(y2-y1) <= 1 and abs(x2-x1) >= 25 and abs(x2-x1) <= 55:
    81. cleaned.append((x1,y1,x2,y2))
    82. cv2.line(image, (x1,y1), (x2,y2), color, thickness)
    83. print(" No lines detected: ", len(cleaned))
    84. return image
    85. # 过滤部分直线,对直线进行排序,得出每一列的起始点和终止点,并将列矩形画出来
    86. def identify_blocks(self, image, lines, make_copy=True):
    87. if make_copy:
    88. new_image = np.copy(image)
    89. # step1: 过滤部分直线
    90. cleaned = []
    91. for line in lines:
    92. for x1,y1,x2,y2 in line:
    93. if abs(y2-y1) <= 1 and abs(x2-x1) >= 25 and abs(x2-x1)<= 55:
    94. cleaned.append((x1,y1,x2,y2))
    95. # step2: 对直线按照 起始点的x和y坐标 进行排序
    96. import operator # 可以使用其中的各种函数来进行操作,例如比较、算术
    97. list1 = sorted(cleaned, key=operator.itemgetter(0,1)) # 从列表的每个元素中获取索引为01的值,然后将这些值用作排序的依据
    98. # step3: 找到多个列,相当于每列是一排车
    99. clusters = {} # 列数:对应该列有哪些车位线
    100. dIndex = 0
    101. clus_dist = 10
    102. for i in range(len(list1) - 1):
    103. distance = abs(list1[i+1][0] - list1[i][0]) # 根据前后两组车位线的x1距离
    104. if distance <= clus_dist:
    105. if not dIndex in clusters.keys(): clusters[dIndex] = []
    106. clusters[dIndex].append(list1[i])
    107. clusters[dIndex].append(list1[i + 1])
    108. else:
    109. dIndex += 1
    110. # step4: 得到每一列的四个坐标
    111. rects = {} # 每一列的四个角的坐标
    112. i = 0
    113. for key in clusters:
    114. all_list = clusters[key]
    115. # 将列表 all_list 转换为一个集合set,去重
    116. # {(10, 20, 30, 40), (20, 30, 40, 50)} 转为 [(10, 20, 30, 40), (20, 30, 40, 50)]
    117. cleaned = list(set(all_list))
    118. if len(cleaned) > 5:
    119. cleaned = sorted(cleaned, key=lambda tup: tup[1]) # 按y1进行排序
    120. avg_y1 = cleaned[0][1] # 第一条线段的起始点 y 坐标
    121. avg_y2 = cleaned[-1][1] # 最后一条线段的起始点 y 坐标,即整个区域的上下边界
    122. avg_x1 = 0
    123. avg_x2 = 0
    124. for tup in cleaned: # 累加起始点和结束点的 x 坐标
    125. avg_x1 += tup[0]
    126. avg_x2 += tup[2]
    127. avg_x1 = avg_x1/len(cleaned) # 取平均起始点和结束点x坐标值
    128. avg_x2 = avg_x2/len(cleaned)
    129. rects[i] = (avg_x1, avg_y1,avg_x2,avg_y2)
    130. i += 1
    131. print("Num Parking Lanes:", len(rects))
    132. # step5: 把列矩形画出来
    133. buff = 7
    134. for key in rects:
    135. tup_topLeft = (int(rects[key][0] - buff), int(rects[key][1])) # x1-buff, y1
    136. tup_botRight = (int(rects[key][2] + buff), int(rects[key][3])) # x2+buff, y2
    137. cv2.rectangle(new_image, tup_topLeft, tup_botRight,(0,255,0),3)
    138. return new_image,rects
    139. # 在图上将停车位画出来,并返回字典{坐标:车位序号}
    140. def draw_parking(self, image, rects, make_copy=True, color=[255,0,0], thickness=2, save=True):
    141. if make_copy:
    142. new_image = np.copy(image)
    143. gap = 15.5 # 一个车位大致高度
    144. spot_dict = {} # 字典:一个车位对应一个位置
    145. tot_spots = 0 # 总车位
    146. # 微调
    147. 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}
    148. 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}
    149. 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}
    150. 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}
    151. for key in rects:
    152. tup = rects[key]
    153. x1 = int(tup[0] + adj_x1[key])
    154. x2 = int(tup[2] + adj_x2[key])
    155. y1 = int(tup[1] + adj_y1[key])
    156. y2 = int(tup[3] + adj_y2[key])
    157. cv2.rectangle(new_image,(x1,y1), (x2,y2), (0,255,0), 2)
    158. num_splits = int(abs(y2-y1)//gap) # 一列总共有多少个车位
    159. for i in range (0,num_splits+1): # 画车位框
    160. y = int(y1 + i*gap)
    161. cv2.rectangle(new_image, (x1,y), (x2,y2), (0,255,0), 2)
    162. if key > 0 and key < len(rects)-1:
    163. # 竖直线
    164. x = int((x1+x2)/2)
    165. cv2.line(new_image,(x,y1),(x,y2),color,thickness)
    166. # 计算数量
    167. if key == 0 or key == (len(rects) - 1): # 对于第一列和最后一列(只有一排车位)
    168. tot_spots += num_splits + 1
    169. else:
    170. tot_spots += 2*(num_splits + 1) # 一列有两排车位
    171. # 字典对应好
    172. if key == 0 or key == (len(rects) - 1): # 对于第一列和最后一列(只有一排车位)
    173. for i in range(0, num_splits+1):
    174. cur_len = len(spot_dict)
    175. y = int(y1 + i*gap)
    176. spot_dict[(x1,y,x2,y+gap)] = cur_len + 1
    177. else:
    178. for i in range(0, num_splits+1):
    179. cur_len = len(spot_dict)
    180. y = int(y1 + i*gap)
    181. x = int((x1+x2)/2)
    182. spot_dict[(x1,y,x,y+gap)] = cur_len + 1
    183. spot_dict[(x,y,x2,y+gap)] = cur_len + 2
    184. print("total parking spaces: ", tot_spots, cur_len)
    185. if save:
    186. filename = 'with_parking.jpg'
    187. cv2.imwrite(filename, new_image)
    188. return new_image, spot_dict
    189. # 根据传入的起始点和终止点坐标列表画框
    190. def assign_spots_map(self, image, spot_dict, make_copy= True, color=[255,0,0], thickness=2):
    191. if make_copy:
    192. new_image = np.copy(image)
    193. for spot in spot_dict.keys():
    194. (x1,y1,x2,y2) = spot
    195. cv2.rectangle(new_image,(int(x1),int(y1)), (int(x2),int(y2)), color, thickness)
    196. return new_image
    197. # 遍历字典{坐标,车位号}在图片中截取对应坐标的图像,按车位号保存下来
    198. def save_images_for_cnn(self, image, spot_dict, folder_name= 'cnn_data'):
    199. for spot in spot_dict.keys():
    200. (x1,y1,x2,y2) = spot
    201. (x1,y1,x2,y2) = (int(x1),int(y1),int(x2),int(y2))
    202. # 裁剪
    203. spot_img= image[y1:y2, x1:x2]
    204. spot_img = cv2.resize(spot_img, (0,0), fx=2.0, fy=2.0)
    205. spot_id = spot_dict[spot]
    206. filename = 'spot' + str(spot_id) + '.jpg'
    207. print(spot_img.shape, filename, (x1,x2,y1,y2))
    208. cv2.imwrite(os.path.join(folder_name, filename), spot_img)
    209. # 将图像进行归一化,并将其转换成一个符合深度学习模型输入要求的四维张量,进行训练
    210. def make_prediction(self, image, model, class_dictionary):
    211. # 预处理
    212. img = image/255. # 将图像的像素值归一化到 [0, 1] 的范围内
    213. # 将图像转换成一个四维张量
    214. image = np.expend_dims(img, axis = 0)
    215. # 将图片调用keras算法进行预测
    216. class_predicted = model.predict(image) # 得出预测结果
    217. inID = np.argmax(class_predicted[0]) # 找到数组中最大值所在的索引
    218. label = class_dictionary[inID]
    219. return label
    220. def predict_on_image(self, image, spot_dict, model, class_dictionary,
    221. make_copy=True, color=[0,255,0], alpha=0.5):
    222. if make_copy:
    223. new_image = np.copy(image)
    224. overlay = np.copy(image)
    225. self.cv_show('new_image',new_image)
    226. cnt_empty = 0
    227. all_spots = 0
    228. for spot in spot_dict.keys():
    229. all_spots += 1
    230. (x1, y1, x2, y2) = spot
    231. (x1, y1, x2, y2) = (int(x1), int(y1), int(x2), int(y2))
    232. spot_img = image[y1:y2, x1:x2]
    233. spot_img = cv2.resize(spot_img, (48,48))
    234. label = self.make_prediction(spot_img, model, class_dictionary)
    235. if label== 'empty':
    236. cv2.rectangle(overlay, (int(x1), int(y1)), (int(x2), int(y2)), color, -1)
    237. cnt_empty += 1
    238. cv2.addWeighted(overlay, alpha, new_image, 1-alpha, 0, new_image)
    239. cv2.putText(new_image, "Available: %d spots" %cnt_empty, (30,95),
    240. cv2.FONT_HERSHEY_SIMPLEX,0.7,(255,255,255),2)
    241. cv2.putText(new_image, "Total: %d spots" %all_spots, (30,125),
    242. cv2.FONT_HERSHEY_SIMPLEX, 0.7,(255,255,255),2)
    243. save = False
    244. if save:
    245. filename = 'with_parking.jpg'
    246. cv2.imwrite(filename, new_image)
    247. self.cv_show('new_image',new_image)
    248. return new_image
    249. def predict_on_video(self, video_name, final_spot_dict, model, class_dictionary, ret=True):
    250. cap= cv2.VideoCapture(video_name)
    251. count = 0
    252. while ret:
    253. ret, image = cap.read()
    254. count += 1
    255. if count == 5:
    256. count == 0
    257. new_image = np.copy(image)
    258. overlay = np.copy(image)
    259. cnt_empty = 0
    260. all_spots = 0
    261. color = [0,255,0]
    262. alpha = 0.5
    263. for spot in final_spot_dict.keys():
    264. all_spots += 1
    265. (x1,y1,x2,y2) = spot
    266. (x1,y1,x2,y2) = (int(x1), int(y1), int(x2), int(y2))
    267. spot_img = image[y1:y2, x1:x2]
    268. spot_img = cv2.resize(spot_img, (48,48))
    269. label = self.make_prediction(spot_img, model, class_dictionary)
    270. if label == 'empty':
    271. cv2.rectangle(overlay, (int(x1),int(y1)), (int(x2),int(y2)), color, -1)
    272. cnt_empty += 1
    273. cv2.addWeighted(overlay, alpha, new_image, 1-alpha, 0, new_image)
    274. cv2.putText(new_image,"Available: %d spots" % cnt_empty,(30,95),
    275. cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255,255,255),2)
    276. cv2.putText(new_image, "Total: %d spots" %all_spots, (30,125),
    277. cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255,255,255), 2)
    278. cv2.imshow('frame',new_image)
    279. # 检测用户是否按下了 'q'
    280. if cv2.waitKey(10) & 0xFF == ord('q'): # 通过 & 0xFF 操作,可以确保只获取ASCII码的最后一个字节
    281. break
    282. cv2.destroyWindow()
    283. cap.release()

  • 相关阅读:
    冰冰学习笔记:Linux下的权限理解
    JSD-2204-创建Spring项目-Day19
    怎么压缩pdf文件大小?详细压缩步骤
    指针相关面试题目
    性能测试工具有哪些?原理是什么?怎么选择适合的工具?
    【Hadoop】使用Metorikku框架读取hive数据统计分析写入mysql
    UML 的工厂方法设计模式 策略设计模式 抽象工厂设计模式 观察者设计模式
    集合类中的反常规特性
    PHP 利用getid3 获取mp3、mp4、wav等媒体文件时长等数据
    js播放mp3,并且读取srt字幕文件,展示字幕
  • 原文地址:https://blog.csdn.net/2301_78240361/article/details/140120046