5.4.5 车牌提取
在这一步中,将介绍如何使用数字图像处理技术从夜间交通帧中提取车牌,并确保在不同条件下的稳定性和准确性。定义函数 extract_license_plate,用于从给定的图像帧中提取车牌。首先,它将图像转换为灰度图并应用CLAHE直方图均衡化以增强对比度。然后,通过腐蚀操作去除噪声,并找到非黑色像素的边界框。接下来,它在边界框内使用Haar级联分类器检测潜在的车牌位置,并在原始图像上绘制矩形来标记检测到的车牌位置。最后,它将检测到的车牌裁剪并存储在列表中,同时返回带有标记车牌位置的原始图像。
def extract_license_plate(frame, mask_line):
# 将图像转换为灰度图(Haar级联通常在灰度图像上进行训练)
gray = cv2.cvtColor(mask_line, cv2.COLOR_BGR2GRAY)
# 应用CLAHE来均衡直方图
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
gray = clahe.apply(gray)
# 使用2x2的核来腐蚀图像以去除噪声
kernel = np.ones((2, 2), np.uint8)
gray = cv2.erode(gray, kernel, iterations=1)
# 查找非黑色像素的边界框
non_black_points = cv2.findNonZero(gray)
x, y, w, h = cv2.boundingRect(non_black_points)
# 计算边界框的新宽度,右侧排除30%
w = int(w * 0.7)
# 将图像裁剪到边界框
cropped_gray = gray[y:y+h, x:x+w]
# 在图像中检测车牌(这会返回一个矩形列表)
license_plates = license_plate_cascade.detectMultiScale(cropped_gray, scaleFactor=1.07, minNeighbors=15, minSize=(20, 20))
# 用于保存裁剪的车牌图像的列表
license_plate_images = []
# 遍历车牌
for (x_plate, y_plate, w_plate, h_plate) in license_plates:
# 在原始帧中的车牌周围绘制矩形(这里需要原始坐标)
cv2.rectangle(frame, (x_plate + x, y_plate + y), (x_plate + x + w_plate, y_plate + y + h_plate), (0, 255, 0), 3)
# 裁剪车牌并将其附加到列表中(这里x_plate和y_plate相对于cropped_gray)
license_plate_image = cropped_gray[y_plate:y_plate+h_plate, x_plate:x_plate+w_plate]
license_plate_images.append(license_plate_image)
return frame, license_plate_images
上述代码的实现流程如下所示:
(1)预处理:从基本的预处理步骤开始,包括将图像转换为灰度图像以使用单通道强度值,并应用CLAHE来均衡直方图,从而增强图像对比度。此外,使用腐蚀来最小化噪声,为更准确的检测铺平道路。
(2)隔离和裁剪感兴趣区域(ROI):在前面步骤实现的掩码上进行处理,其中线上方的像素被涂成黑色,代码识别了表示停车线下方道路的非黑色像素。计算该区域的边界框,然后裁剪它,另外还稍微排除了属于对向车道的左侧一部分。这个集中的裁剪区域成为车牌检测的主要ROI,因为__Haar级联分类器__在较小、定义明确的ROI上执行效果更好。
(3)Haar级联分类器:将裁剪的灰度图像传递给Haar级联分类器,这是一种经典的非深度学习方法,擅长于模式识别。该分类器经过预训练以识别车牌模式,并以矩形的形式返回潜在车牌的位置。
(4)提取车牌并最终输出:将检测到的车牌在原始帧上用矩形标记。此外,还从灰度区域中裁剪出潜在的车牌,并将其存储在列表中。该函数通过返回带有车牌边界的原始帧和潜在检测到的车牌来结束。
函数extract_license_plate 是一个简化且组织良好的函数,对于需要车牌识别的应用程序至关重要。它利用经典的计算机视觉技术有效地提取所需的有价值信息。
5.4.6 车牌号码的文本识别
在这一步中,将使用 PyTesseract OCR对提取的车牌图像进行文本识别处理,以获取具体的车牌号码。定义一个名为apply_ocr_to_image的函数,该函数旨在利用PyTesseract OCR从给定的车牌图像中识别和提取文本信息(如数字和字符)。
def apply_ocr_to_image(license_plate_image):
# 图像阈值化
_, img = cv2.threshold(license_plate_image, 120, 255, cv2.THRESH_BINARY)
# 将OpenCV图像格式转换为PIL图像格式以供Pytesseract使用
pil_img = Image.fromarray(img)
# 使用Pytesseract从图像中提取文本
full_text = pytesseract.image_to_string(pil_img, config='--psm 6')
return full_text.strip() # 移除识别文本两端的额外空白
上述代码的实现流程如下所示:
(1)图像阈值化:对输入的车牌图像进行阈值化处理,将其转换为二进制格式,增强文本与背景的对比度。
(2)格式转换:将阈值化处理后的图像,初始为OpenCV格式,转换为PyTesseract所需的PIL图像格式,以便进行OCR处理。
(3)使用经典OCR提取文本:利用PyTesseract的image_to_string函数从转换后的图像中提取文本。使用OCR配置--psm 6处理典型车牌上稀疏背景的稀疏文本。
(4)文本清理:在文本提取后,利用strip()方法移除识别文本开头或结尾的任何多余空白,以确保返回的文本整洁紧凑。
函数apply_ocr_to_image有助于高效地从车牌图像中提取文本信息,充分利用PyTesseract提供的传统OCR技术功能。
5.4.7 显示处罚车牌
这一步将介绍如何将违规行为的车牌号码动态显示在视频帧上,以便实现可视化展示功能。下面的draw_penalized_text函数被巧妙地用于在视频帧上显示受罚的车牌,这种动态显示效果确保了每个被罚款的车牌按其识别顺序显示。无论视频源是来自离线录制还是实时监控,被罚车牌的列表都会动态更新在车辆监控屏幕上。
def draw_penalized_text(frame, penalized_plates):
# 在视频帧上绘制受罚车牌的文本
for idx, plate in enumerate(penalized_plates):
# 设置文本的位置和样式
text = f"Penalized Plate: {plate}"
font = cv2.FONT_HERSHEY_SIMPLEX
font_scale = 1
font_thickness = 2
text_color = (0, 0, 255) # 红色
bg_color = (255, 255, 255) # 白色
# 获取文本的宽度和高度
(text_width, text_height), _ = cv2.getTextSize(text, font, font_scale, font_thickness)
# 计算文本位置
text_x = int((frame.shape[1] - text_width) / 2)
text_y = 40 + idx * (text_height + 10) # 每个车牌的文本位置间隔
# 绘制文本的背景框
bg_rect_start = (text_x, text_y - text_height)
bg_rect_end = (text_x + text_width, text_y + int(text_height * 0.25))
cv2.rectangle(frame, bg_rect_start, bg_rect_end, bg_color, cv2.FILLED)
# 在视频帧上绘制文本
cv2.putText(frame, text, (text_x, text_y), font, font_scale, text_color, font_thickness)
return frame
通过函数draw_penalized_text,使用者可以在车辆监控屏幕上实时跟踪和查看受罚的车牌,使其成为任何车辆监控或交通管理系统中不可或缺的工具。
5.4.8 将违规车牌记录到 MySQL 中
在这一步中,将产生违规行为的车牌号码记录到 MySQL 数据库中,以便进一步处理和管理。在这一部分将提供一些函数,这些函数共同与MySQL数据库集成,确保所有被罚款的车牌都被有效记录。这些函数提供了根据需要创建、更新、查看和清除车牌数据的能力,共同实现了对车牌违规行为的高效管理,确保了数据的准确性和易于访问。鉴于某些函数是可选的,开发人员可以根据应用程序的需求选择部署它们中觉得必要的部分。
(1)数据库和表的初始化
函数create_database_and_table()用于创建一个新的 MySQL 数据库,并在该数据库中创建一个名为 license_plates 的表格,用于记录车牌号和相应的违规计数。
def create_database_and_table(host, user, password, database):
try:
# 创建一个连接
connection = mysql.connector.connect(
host=host,
user=user,
password=password
)
if connection.is_connected():
# 创建一个新的数据库游标
cursor = connection.cursor()
# 使用提供的名称创建一个新数据库
cursor.execute(f"CREATE DATABASE IF NOT EXISTS {database}")
print(f"数据库 {database} 创建成功!")
# 使用新创建的数据库
cursor.execute(f"USE {database}")
# 创建一个新的表
cursor.execute("""
CREATE TABLE IF NOT EXISTS license_plates (
id INT AUTO_INCREMENT PRIMARY KEY,
plate_number VARCHAR(255) NOT NULL UNIQUE,
violation_count INT DEFAULT 1
)
""")
print("表格创建成功!")
cursor.close()
except Error as e:
print("连接到 MySQL 时出错", e)
finally:
if connection.is_connected():
connection.close()
对上述代码的具体说明如下所示:
- 使用提供的凭据建立与MySQL的连接。
- 如果数据库不存在,则创建一个新的数据库,并选择它进行后续操作。
- 构建一个名为license_plates的表,其中包含每个车牌及其相应违规计数的记录。
- 需要注意的是,一旦数据库设置好了,这个函数可能是可选的,因为没有必要重复创建结构。
(2)函数update_database_with_violation()用于更新 MySQL 数据库中的违规记录。首先,连接到指定的 MySQL 数据库。然后,检查车牌号是否已存在于数据库中。如果车牌号已存在,则将其对应的违规计数加一;如果不存在,则添加一个新的记录,违规计数默认为1。
def update_database_with_violation(plate_number, host, user, password, database):
try:
connection = mysql.connector.connect(
host=host,
user=user,
password=password,
database=database
)
if connection.is_connected():
cursor = connection.cursor()
# 检查车牌是否已存在于表中
cursor.execute(f"SELECT violation_count FROM license_plates WHERE plate_number='{plate_number}'")
result = cursor.fetchone()
if result:
# 如果车牌已存在,则将违规次数加1
cursor.execute(f"UPDATE license_plates SET violation_count=violation_count+1 WHERE plate_number='{plate_number}'")
else:
# 如果车牌不存在,则插入新记录,默认违规次数为1
cursor.execute(f"INSERT INTO license_plates (plate_number) VALUES ('{plate_number}')")
connection.commit()
cursor.close()
except Error as e:
print("连接到MySQL时出错", e)
finally:
if connection.is_connected():
connection.close()
(3)下面的代码定义了函数 print_all_violations,用于连接到指定的 MySQL 数据库并打印其中记录的所有交通违规信息。首先连接到数据库,然后执行查询以检索所有车牌号和对应的违规次数,并按违规次数降序排序。最后,它将结果打印出来,显示车牌号和违规次数。
def print_all_violations(host, user, password, database):
try:
connection = mysql.connector.connect(
host=host,
user=user,
password=password,
database=database
)
if connection.is_connected():
cursor = connection.cursor()
# 从数据库中提取所有违规记录
cursor.execute("SELECT plate_number, violation_count FROM license_plates ORDER BY violation_count DESC")
result = cursor.fetchall()
print("\n")
print("-"*66)
print("\n数据库中所有已注册的交通违规记录:\n")
for record in result:
print(f"车牌号码:{record[0]}, 违规次数:{record[1]}")
cursor.close()
except Error as e:
print("连接到MySQL时出错", e)
finally:
if connection.is_connected():
connection.close()
(4)函数 clear_license_plates用于连接到指定的 MySQL 数据库,并清空其中存储的所有车牌信息。首先连接到数据库,然后执行一个 SQL 查询,删除 license_plates 表中的所有记录。最后,函数clear_license_plates提交事务并关闭数据库连接。
def clear_license_plates(host, user, password, database):
# 尝试连接到 MySQL 数据库并清空所有车牌信息
try:
connection = mysql.connector.connect(
host=host,
user=user,
password=password,
database=database
)
# 如果连接成功
if connection.is_connected():
cursor = connection.cursor()
# 从表中删除所有记录
cursor.execute("DELETE FROM license_plates")
# 提交事务
connection.commit()
cursor.close()
# 如果连接过程中出现错误,则打印错误信息
except Error as e:
print("连接到 MySQL 数据库时出错", e)
# 最终无论如何都要确保关闭数据库连接
finally:
if connection.is_connected():
connection.close()
5.4.9 运行交通违规监控系统
这一步将展示如何执行整个交通违规监控系统,包括实时监测、记录违规行为并进行必要的处罚。
(1)定义函数main,该函数封装了从视频流中检测车牌违规的整个流程。函数main基于一段交通的离线视频,逐帧读取视频,处理每一帧以检测任何交通违规行为。
def main():
# 确保数据库和表存在
create_database_and_table(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME)
# 清除上次运行的车牌信息。(如果需要可以注释掉这行!)
clear_license_plates(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME)
# 打开视频文件
vid = cv2.VideoCapture('traffic_video_modified.mp4')
# 创建检测器对象
detector = LineDetector()
# 遍历视频中的每一帧
while True:
# 读取帧
ret, frame = vid.read()
# 如果没有返回帧,则退出循环
if not ret:
break
# 假设rect是交通信号灯所在的矩形区域
rect = (1700, 40, 100, 250)
# 检测交通信号灯的颜色
frame, color = detect_traffic_light_color(frame, rect)
# 检测白色线条
frame, mask_line = detector.detect_white_line(frame, color)
# 如果信号灯为红色,则处理该帧
if color == 'red':
# 提取车牌
frame, license_plate_images = extract_license_plate(frame, mask_line)
# 处理每个检测到的车牌
for license_plate_image in license_plate_images:
# 对车牌图像应用OCR
text = apply_ocr_to_image(license_plate_image)
# 如果文本不为空且匹配了预定义模式,并且不在已罚款文本列表中,则添加到列表中
if text is not None and re.match("^[A-Z]{2}\s[0-9]{3,4}$", text) and text not in penalized_texts:
penalized_texts.append(text)
print(f"\n罚款车牌: {text}")
# 绘制车牌图像
plt.figure()
plt.imshow(license_plate_image, cmap='gray')
plt.axis('off')
plt.show()
# 更新数据库中的车牌违规记录
update_database_with_violation(text, DB_HOST, DB_USER, DB_PASSWORD, DB_NAME)
# 如果有罚款车牌,则在帧上绘制
if penalized_texts:
draw_penalized_text(frame)
# 显示帧
cv2.imshow('frame', frame)
# 如果按下ESC键,则退出(在支持GUI的本地系统上运行时取消下面一行的注释)
if cv2.waitKey(1) == 27:
break
# 释放视频
vid.release()
# 关闭所有OpenCV窗口(在支持GUI的本地系统上运行时取消下面一行的注释)
cv2.destroyAllWindows()
# 打印数据库中的所有违规记录
print_all_violations(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME)
在上述代码中,函数main实现了一个完整的交通违规检测系统,包括车牌识别、违规记录、数据库更新和显示输出等功能。具体实现流程如下所示。
- 确保数据库和表的存在。
- 清除上次运行时记录的车牌信息。
- 打开视频文件并创建检测器对象。
- 逐帧读取视频,检测交通信号灯颜色和白线,并在红灯状态下处理帧。
- 提取检测到的车牌并应用光学字符识别(OCR)。
- 更新违规车牌信息到数据库,并在匹配条件下添加到已罚款车牌列表。
- 在帧上绘制已罚款车牌信息。
- 显示处理后的帧,并按 ESC 键退出。
- 打印输出数据库中保存的所有违规记录。
(2)在本项目的车牌检测机制中,我们使用了一个 Haar 级联分类器。在调试运行,首先从 GitHub 仓库下载我们预先训练好的模型 haarcascade_russian_plate_number.xml。这是一个专门用于检测车牌的模型,对于在视频帧中识别车牌至关重要。通过初始化 Haar 级联对象,确保我们的程序不会因为多次初始化而遭受重复的延迟。
在这之后,初始化了一个名为 penalized_texts 的列表。这个列表作为一个存储库,记录了交通违规中涉及的车牌文本。对这个列表的全局定义确保了主函数和 draw_penalized_text 函数都可以访问和更新其内容而不会出现问题。
# 从GitHub存储库下载经过训练的 Haar Cascade 模型
url = "https://raw.githubusercontent.com/FarzadNekouee/Traffic-Violation-Detection/master/haarcascade_russian_plate_number.xml"
response = requests.get(url)
with open('haarcascade_russian_plate_number.xml', 'wb') as file:
file.write(response.content)
# 加载经过训练的 Haar Cascade 模型
license_plate_cascade = cv2.CascadeClassifier('haarcascade_russian_plate_number.xml')
# 创建一个列表,用于存储唯一的受罚车牌文本信息
penalized_texts = []
(3)通过如下代码启动主程序,一旦启动,函数main就会开始其复杂的过程,识别视频中的车牌违规行为,并详细记录任何违规行为。
if __name__ == "__main__":
main()
执行效果如图5-8所示。
图5-8 执行效果