使用python语言,基于OTSU算法写一段视频火灾检测的代码,要求如下:
1.对视频的每一帧先利用公式gray=1.6r-g-0.8b,得到相应的灰度图,再利用otsu算法进行二值化,对每一帧执行先腐蚀再膨胀去除噪声,最终得到二值化视频
2.对于二值化视频中的每一帧,合并距离较近的火灾区域,去除面积较小的火灾区域,同时在两个窗口分别展示未经过任何处理的原视频和最终的二值化视频
3.计算二值化视频中所检测到的火灾面积和火灾个数,并且把对应数值在两个视频上面进行实时展示
4.两个视频均实时显示FPs,即每秒可以处理视频的帧数,在两个视频中均实时显示每秒钟火灾面积的大小,即变化速度
5.计算当前帧火灾区域与100帧前的火灾区域的交集和并集的比值,并把结果在两个视频上面进行实时展示
6.增加一个静止的空白画布,画布的大小和视频帧的大小一致,每间隔1秒钟,计算二值化视频帧中所检测到的火灾形心(只有一个)坐标,并把当前帧火灾形心坐标添加画布的对应位置,也就是每1秒钟空白窗口上面多一个点
import cv2
import numpy as np
import time
def detect_fire_in_video(video_file):
cap = cv2.VideoCapture(video_file)
# 获取视频文件的FPS
original_FPS = cap.get(cv2.CAP_PROP_FPS)
previous_frame_fire_area = 0 # 前一帧的火灾面积
previous_time = time.time() # 开始时间
# 存储过去100帧的数据
past_frames = []
# 创建空白画布,大小和视频帧一致
blank_canvas = np.zeros((int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)), int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)), 3),
dtype=np.uint8)
while (cap.isOpened()):
# 计数器,记录火灾的个数和面积
fire_count = 0
fire_area = 0
ret, frame = cap.read()
if ret == True:
current_time = time.time()
# 初始化FPS和火灾面积变化速度
fps = 0
fire_area_change_rate = 0
# 获取处理开始时的时间
start = time.time()
# Get original frame for contour drawing
original_frame = frame.copy()
# 格式化像素值为 float 类型
frame = np.float32(frame)
# 根据给定公式创建灰度图像
gray = 1.6 * frame[:, :, 2] - frame[:, :, 1] - 0.8 * frame[:, :, 0]
# Normalize gray scale image and convert to uint8
gray = cv2.normalize(gray, None, alpha=0, beta=255, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8U)
# Using Otsu's binarization
_, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
# 使用腐蚀和膨胀去噪
kernel = np.ones((5, 5), np.uint8)
binary = cv2.erode(binary, kernel, iterations=1)
binary = cv2.dilate(binary, kernel, iterations=1)
# 当前帧添加进列表中
past_frames.append(binary)
# 如果累积的帧数大于100,移除最老的帧
if len(past_frames) > 100:
past_frames.pop(0)
# 计算交集和并集的比值
if len(past_frames) == 100:
intersection = cv2.bitwise_and(past_frames[-1], past_frames[0])
union = cv2.bitwise_or(past_frames[-1], past_frames[0])
intersection_over_union = np.sum(intersection) / np.sum(union) if np.sum(
union) != 0 else 0 # 如果 union 非0,算比值,否则比值为0。
# Find and filter small fire areas
contours, _ = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
for cnt in contours:
if cv2.contourArea(cnt) < 5000:
continue
fire_count += 1
fire_area += cv2.contourArea(cnt)
# 计算处理这一帧图像所用的时间和FPS
time_diff = time.time() - start
fps = 1 / time_diff if time_diff != 0 else original_FPS # 如果 time_diff 非0, fps = 1/time_diff,否则使用原视频的FPS
# 计算火灾面积的变化速度(每秒钟)
fire_area_change_rate = (fire_area - previous_frame_fire_area) / (current_time - previous_time)
# 在视频上展示火灾的个数、面积、FPS、火灾面积变化速度以及并交比值
cv2.putText(original_frame, "Fire Count: %d" % fire_count, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7,
(0, 0, 255), 2)
cv2.putText(original_frame, "Fire Area: %.2f" % fire_area, (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.7,
(0, 0, 255), 2)
cv2.putText(original_frame, "FPS: %.2f" % fps, (10, 90), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
cv2.putText(original_frame, "Fire Area Change Rate: %.2f" % fire_area_change_rate, (10, 120),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
if len(past_frames) == 100:
cv2.putText(original_frame, "Intersection Over Union: %.2f" % intersection_over_union, (10, 150),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
# 在画布上添加火灾形心坐标点
if current_time - previous_time >= 1: # 每隔1秒添加一个点
previous_time = current_time
if fire_count > 0:
# 计算火灾形心坐标
M = cv2.moments(binary)
center_x = int(M["m10"] / M["m00"])
center_y = int(M["m01"] / M["m00"])
cv2.circle(blank_canvas, (center_x, center_y), 3, (0, 255, 0), -1)
cv2.imshow('Frame', original_frame)
cv2.imshow('Binary Frame', binary)
cv2.imshow('Blank Canvas', blank_canvas)
# Update previous_frame_fire_area and previous_time for the next frame
# Note: we do it here to include time taken by cv2.imshow in frame_time
previous_frame_fire_area = fire_area
# Press 'q' key to exit
if cv2.waitKey(25) & 0xFF == ord('q'):
break
else:
break
cap.release()
cv2.destroyAllWindows()
detect_fire_in_video('7191.mp4')