由于項目需要進行數(shù)據(jù)采集赏表,從攝像頭讀取后匈仗,將讀取的每幀數(shù)據(jù)進行保存視頻。在使用過程中悠轩,發(fā)現(xiàn)采集的生成的視頻達不到指定的fps,存在丟幀現(xiàn)象鉴象。經(jīng)過排查,在寫視頻write(frame)存在IO耗時偏高牛欢,每幀處理耗時長了傍睹,導(dǎo)致掉幀犹菱。特別在1080p,30fps的寫入視頻對CPU占用都較高腊脱。
為了解決這個問題陕凹,將單線程改為多線程進行 讀寫分離 處理數(shù)據(jù)幀杜耙,將讀出的視頻幀泥技,放入FIFO隊列,寫視頻線程從隊列中讀取數(shù)據(jù)幀珊豹,按照指定幀率進行寫入保存視頻店茶。
代碼如下:
import os
import time
import cv2
import datetime
import configparser
import csv
import queue
import threading
thread_exit = False
ready = False
q = queue.Queue()
class myThread(threading.Thread):
def __init__(self, camera_id, img_width, img_height, fps):
super(myThread, self).__init__()
self.camera_id = camera_id
self.img_width = img_width
self.img_height = img_height
self.fps = fps
self.cap = cv2.VideoCapture(self.camera_id)
print("攝像頭默認幀率:{}".format(self.cap.get(cv2.CAP_PROP_FPS)))
print("攝像頭設(shè)置幀率:{}".format(self.fps))
ret = self.cap.set(5, self.fps) # 設(shè)置視頻讀取幀率
print("攝像頭設(shè)置后幀率:{},{}".format(ret, self.cap.get(cv2.CAP_PROP_FPS)))
self.cap.set(3, self.img_width) # 設(shè)置視頻的寬
self.cap.set(4, self.img_height) # 設(shè)置視頻的高
print("攝像頭默認編解碼:{}".format(self.cap.get(cv2.CAP_PROP_FOURCC)))
fourcc = cv2.VideoWriter_fourcc('M', 'J', 'P', 'G')
self.cap.set(6, fourcc) # 設(shè)置解碼格式
print("攝像頭設(shè)置后編解碼:{}".format(self.cap.get(cv2.CAP_PROP_FOURCC)))
self.img_width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH)) # 重新獲取最大的分辨率寬
self.img_height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) # 重新獲取最大的分辨率高
self.fps = int(self.cap.get(cv2.CAP_PROP_FPS)) # 獲取支持的fps
def run(self):
global thread_exit
global ready
global q
frame_count = 0
str_time = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
f = open(str_time + '.csv', 'w', encoding='utf-8', newline='')
header = ['Frame', 'DateTime']
writer = csv.writer(f)
writer.writerow(header)
while not thread_exit:
ret, frame = self.cap.read()
if ret:
print(self.cap.get(cv2.CAP_PROP_POS_MSEC))
font = cv2.FONT_HERSHEY_SIMPLEX
text = 'Width: ' + str(self.img_width) + ' Height:' + str(self.img_height)
datet = str(datetime.datetime.now())
str_date = datet.replace('.', ' ') # .分隔會導(dǎo)致csv文件格式顯示不對,需要格式處理
# datet = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S %f')[:-3] # .分隔會導(dǎo)致csv文件格式顯示不對需要格式處理
frame = cv2.putText(frame, text, (10, 50), font, 0.5,
(0, 255, 255), 2, cv2.LINE_AA)
frame = cv2.putText(frame, datet, (10, 100), font, 1,
(0, 255, 255), 2, cv2.LINE_AA)
my_list = [frame_count, str_date]
writer.writerow(my_list)
q.put(frame)
ready = True
frame_count = frame_count + 1
else:
f.close()
thread_exit = True
self.cap.release()
def main():
# 默認設(shè)置
id = 0
width = 640
height = 480
fps = 30
status = os.path.exists("config.ini")
if status == True:
config = configparser.ConfigParser()
config.read("config.ini")
config.sections()
config.options("camera")
id = config.getint("camera", "id")
width = config.getint("camera", "width")
height = config.getint("camera", "height")
fps = config.getint("camera", "fps")
print("配置文件中讀取配置")
global thread_exit
global ready
global q
thread = myThread(id, width, height, fps)
thread.start()
tmp_list = []
# 構(gòu)建視頻保存的對象
fourcc = cv2.VideoWriter_fourcc('M', 'J', 'P', 'G')
str_time = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
out = cv2.VideoWriter(str_time + ".avi", fourcc, thread.fps, (thread.img_width, thread.img_height))
interval = 1 / thread.fps # 毫秒為單位
print(interval)
while not thread_exit:
if ready:
start = time.time()
print(start)
print(q.qsize())
q_start = time.time()
frame = q.get()
q_end = time.time()
qt = (round(q_end * 1000) - round(q_start * 1000)) / 1000
print("隊列獲取數(shù)據(jù)耗時:{}".format(qt))
cv2.imshow('Video', frame)
start_time = time.time()
out.write(frame) # 保存視頻
end_time = time.time()
tt = (round(end_time * 1000) - round(start_time * 1000)) / 1000
print("寫耗時:{}".format(tt))
end = time.time()
print(end)
t = (round(end * 1000) - round(start * 1000)) / 1000
print(t)
# if t < interval:
# time.sleep(interval-t)
if cv2.waitKey(1) & 0xFF == ord('q'):
thread_exit = True
out.release()
cv2.destroyAllWindows()
else:
continue
thread.join()
if __name__ == "__main__":
main()