1. 最开始拿到三个支持硬触发的工业相机,通过按键触发相机,读取摄像头数据
2. 和厂商交流后更换软触发相机,三个软触发相机接入能实现同步,继续采购5个相机
3. 发现电脑只能接入5个摄像头,多接入也带不动,需要使用USB3.0集线器(相机厂商称Hub),采购10孔的hub
4. 使用hub8个相机能够成功接入并运行,但不能同步,使用多线程方式可以同步,具体代码如下:
'''
Descripttion:
version:
Author: jhq
Date: 2022-05-24 22:19:46
LastEditors: jhq
LastEditTime: 2022-05-24 22:53:31
'''
import cv2
import numpy as np
import os
from threading import Thread, Event
from queue import Queue
import time
# Event
# Barrier
# Queue
event = Event()
def get_frame(cap, dir_name):
index = 1
while True:
event.wait()
cap.set(13, 1.0)
_, frame = cap.read()
cv2.imwrite(dir_name+str(index)+'.jpg', frame)
index += 1
if __name__=='__main__':
# cap_queue = Queue(maxsize=10)
cap_list = []
dir_list = []
cap0 = cv2.VideoCapture(0)
cap1 = cv2.VideoCapture(2)
cap2 = cv2.VideoCapture(4)
cap3 = cv2.VideoCapture(6)
cap4 = cv2.VideoCapture(8)
cap5 = cv2.VideoCapture(10)
cap6 = cv2.VideoCapture(12)
cap7 = cv2.VideoCapture(14)
flag0 = cap0.isOpened()
flag1 = cap1.isOpened()
flag2 = cap2.isOpened()
flag3 = cap3.isOpened()
flag4 = cap4.isOpened()
flag5 = cap5.isOpened()
flag6 = cap6.isOpened()
flag7 = cap7.isOpened()
cap0.set(cv2.CAP_PROP_FRAME_WIDTH, 640) #设置宽度
cap0.set(cv2.CAP_PROP_FRAME_HEIGHT, 360) #设置高度
cap1.set(cv2.CAP_PROP_FRAME_WIDTH, 640) #设置宽度
cap1.set(cv2.CAP_PROP_FRAME_HEIGHT, 360) #设置高度
cap2.set(cv2.CAP_PROP_FRAME_WIDTH, 640) #设置宽度
cap2.set(cv2.CAP_PROP_FRAME_HEIGHT, 360) #设置高度
cap3.set(cv2.CAP_PROP_FRAME_WIDTH, 640) #设置宽度
cap3.set(cv2.CAP_PROP_FRAME_HEIGHT, 360) #设置高度
cap4.set(cv2.CAP_PROP_FRAME_WIDTH, 640) #设置宽度
cap4.set(cv2.CAP_PROP_FRAME_HEIGHT, 360) #设置高度
cap5.set(cv2.CAP_PROP_FRAME_WIDTH, 640) #设置宽度
cap5.set(cv2.CAP_PROP_FRAME_HEIGHT, 360) #设置高度
cap6.set(cv2.CAP_PROP_FRAME_WIDTH, 640) #设置宽度
cap6.set(cv2.CAP_PROP_FRAME_HEIGHT, 360) #设置高度
cap7.set(cv2.CAP_PROP_FRAME_WIDTH, 640) #设置宽度
cap7.set(cv2.CAP_PROP_FRAME_HEIGHT, 360) #设置高度
cap_list.append(cap0)
cap_list.append(cap1)
cap_list.append(cap2)
cap_list.append(cap3)
cap_list.append(cap4)
cap_list.append(cap5)
cap_list.append(cap6)
cap_list.append(cap7)
# dir_path = '/home/hejiahui/jhq/data/img_0509/0'
file_path = os.getcwd()
dataset_path = os.path.join(file_path, 'dataset')
if not os.path.exists(dataset_path):
os.makedirs(dataset_path)
for i in range(len(cap_list)):
cam_path = dataset_path+ '0' + str(i) + '/'
if not os.path.exists(cam_path):
os.makedirs(cam_path)
dir_list.append(cam_path)
for i in range(len(cap_list)):
Thread(target=get_frame, args=(cap_list[i], dir_list[i])).start()
time.sleep(2)
while True:
event.clear()
val =input("please enter 's'\n")
if val == "s":
event.set()
5. 故保存单线程拍摄的相片观察,代码如下:
import cv2
import time
import numpy as np
import os
file_path = os.getcwd()
dataset_path = os.path.join(file_path, 'dataset')
if not os.path.exists(dataset_path):
os.makedirs(dataset_path)
cap_list = []
cap0 = cv2.VideoCapture(0) # 通过摄像头编号接入
cap_list.append(cap0)
cap1 = cv2.VideoCapture(2)
cap_list.append(cap1)
cap2 = cv2.VideoCapture(4)
cap_list.append(cap2)
cap3 = cv2.VideoCapture(6)
cap_list.append(cap3)
cap4 = cv2.VideoCapture(8)
cap_list.append(cap4)
cap5 = cv2.VideoCapture(10)
cap_list.append(cap5)
cap6 = cv2.VideoCapture(12)
cap_list.append(cap6)
cap7 = cv2.VideoCapture(14)
cap_list.append(cap7)
flag0 = cap0.isOpened() # 测试摄像头是否正常
flag1 = cap1.isOpened()
flag2 = cap2.isOpened()
flag3 = cap3.isOpened()
flag4 = cap4.isOpened()
flag5 = cap5.isOpened()
flag6 = cap6.isOpened()
flag7 = cap7.isOpened()
cap0.set(cv2.CAP_PROP_FRAME_WIDTH, 640) #设置宽度
cap0.set(cv2.CAP_PROP_FRAME_HEIGHT, 360) #设置高度
cap1.set(cv2.CAP_PROP_FRAME_WIDTH, 640) #设置宽度
cap1.set(cv2.CAP_PROP_FRAME_HEIGHT, 360) #设置高度
cap2.set(cv2.CAP_PROP_FRAME_WIDTH, 640) #设置宽度
cap2.set(cv2.CAP_PROP_FRAME_HEIGHT, 360) #设置高度
cap3.set(cv2.CAP_PROP_FRAME_WIDTH, 640) #设置宽度
cap3.set(cv2.CAP_PROP_FRAME_HEIGHT, 360) #设置高度
cap4.set(cv2.CAP_PROP_FRAME_WIDTH, 640) #设置宽度
cap4.set(cv2.CAP_PROP_FRAME_HEIGHT, 360) #设置高度
cap5.set(cv2.CAP_PROP_FRAME_WIDTH, 640) #设置宽度
cap5.set(cv2.CAP_PROP_FRAME_HEIGHT, 360) #设置高度
cap6.set(cv2.CAP_PROP_FRAME_WIDTH, 640) #设置宽度
cap6.set(cv2.CAP_PROP_FRAME_HEIGHT, 360) #设置高度
cap7.set(cv2.CAP_PROP_FRAME_WIDTH, 640) #设置宽度
cap7.set(cv2.CAP_PROP_FRAME_HEIGHT, 360) #设置高度
# pro = cap0.get(21)
# print(f'exposure:{pro}')
# _ = cap0.set(21, 3.0)
# print(f'_:{cap0.get(21)}')
# for i in range(47):
# print("camera.={} No.={} parameter={} camera.={} No.={} parameter={}".format('black_0',i,cap0.get(i),'light_1',i,cap1.get(i)))
for cap in cap_list:
res, frame = cap.read()
while res:
res, frame = cap.read()
index = 1
while index < 30:
cap0.set(13, 1.0) # 设置色调=1触发
cap1.set(13, 1.0)
cap2.set(13, 1.0)
cap3.set(13, 1.0)
cap4.set(13, 1.0)
cap5.set(13, 1.0)
cap6.set(13, 1.0)
cap7.set(13, 1.0)
res0, frame0 = cap0.read() # 读取摄像头数据
res1, frame1 = cap1.read()
res2, frame2 = cap2.read()
res3, frame3 = cap3.read()
res4, frame4 = cap4.read()
res5, frame5 = cap5.read()
res6, frame6 = cap6.read()
res7, frame7 = cap7.read()
if res0 and res1 and res2 and res3 and res4 and res5 and res6 and res7:
# if res0 and res1 and res2:
# frame_test = np.zeros((480, 640, 3))
frame_test = np.zeros(frame0.shape, dtype=np.uint8)
vs1 = np.hstack((frame0, frame1, frame2))
vs2 = np.hstack((frame3, frame4, frame5))
vs3 = np.hstack((frame6, frame7, frame_test)) # 合并摄像头数据
# vs1 = np.hstack((frame0, frame1))
# vs2 = np.hstack((frame2, frame3))
result = np.vstack((vs1, vs2, vs3))
# cv2.imwrite('camare:', result) # 显示数据
cv2.imwrite(os.path.join(dataset_path, str(index)+'.jpg'), result)
index += 1
else:
# print(f'res0:{res0},res1:{res1},res2:{res2}')
print(f'res0:{res0},res1:{res1},res2:{res2},res3:{res3},res4:{res4},res5:{res5},res6:{res6}, res7:{res7}')
break
# print(f'res0:{success0},res1:{success1},res2:{success2},res3:{success3},res4:{success4},res5:{success5},res6:{success6}, res7:{success7}')
cap0.release()
cap1.release()
cap2.release()
cap3.release()
cap4.release()
cap5.release()
cap6.release()
cap7.release()
6. 通过观察单线程保存图片,发现图片之间存在时间gap,比如某个图片的时间是另一个图片的下一个时间,进而怀疑是否是相机接入过程中存在数据缓存,导致读取的数据不同步。
7. 在触发之前将相机缓存数据读掉,使用多线程清数据,不然会很慢,代码如下:
import cv2
import numpy as np
import threading
from subprocess import PIPE,Popen
cam_list_ = [0, 2, 4, 6, 8, 10, 12, 14]
cap_list = []
def find_cam():
'''
根据设备名称获取相机设备号
使用前需要先安装v4l-utils
Installing v4l-utils (debian) gives one the handy v4l2-ctl command:
$ v4l2-ctl --list-devices
KS2A543: KS2A543 (usb-0000:00:14.0-2.1):
/dev/video2
/dev/video3
KS2A543: KS2A543 (usb-0000:00:14.0-2.2):
/dev/video4
/dev/video5
KS2A543: KS2A543 (usb-0000:00:14.0-2.3):
/dev/video6
/dev/video7
KS2A543: KS2A543 (usb-0000:00:14.0-2.4):
/dev/video8
/dev/video9
'''
cam_list = []
cmd = ["/usr/bin/v4l2-ctl", "--list-devices"]
out, err = Popen(cmd, stdout=PIPE, stderr=PIPE).communicate()
out = out.strip()
for dev_name, dev_idx1, dev_idx2 in [i.split(b'\n\t') for i in out.split(b'\n\n')]:
index = dev_idx1.decode('utf8').split('video')[1]
cam_list.append(index)
return cam_list
cam_list = find_cam()
print(cam_list)
for cam_num in cam_list: # 根据摄像头编号创建视频处理句柄
cap = cv2.VideoCapture(int(cam_num))
cap_list.append(cap)
flag = 0
for cap in cap_list: # 判断摄像头是否可用
if cap.isOpened():
flag += 1
def read_camera_cache(cap): # 清除摄像头缓存数据
success = cap.grab()
while success:
success = cap.grab()
threads = []
for cap in cap_list:
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) #设置宽度
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 360) #设置高度
t = threading.Thread(target=read_camera_cache, args=(cap,)) # 多线程清除摄像头缓存数据
threads.append(t)
t.start()
for t in threads: # 父进程等待清缓存的子进程执行完毕
t.join()
index = 1
while flag == 8:
frame_list = []
res_flag = 0
for cap in cap_list:
cap.set(13, 1.0) # 设置色调=1触发
for cap in cap_list:
res, frame = cap.read() # 读取摄像头数据
res_flag += 1
frame_list.append(frame)
if res_flag == 8:
# if res0 and res1 and res2:
# frame_test = np.zeros((480, 640, 3))
frame_test = np.zeros(frame_list[0].shape, dtype=np.uint8)
vs1 = np.hstack((frame_list[0], frame_list[1], frame_list[2]))
vs2 = np.hstack((frame_list[3], frame_list[4], frame_list[5]))
vs3 = np.hstack((frame_list[6], frame_list[7], frame_test)) # 合并摄像头数据
result = np.vstack((vs1, vs2, vs3))
cv2.imshow('camare:', result) # 显示数据
else:
# print(f'res0:{res0},res1:{res1},res2:{res2}')
print(f'res:{res_flag}')
# print(f'res0:{success0},res1:{success1},res2:{success2},res3:{success3},res4:{success4},res5:{success5},res6:{success6}, res7:{success7}')
k = cv2.waitKey(1) & 0xFF
if k == ord('q'):
break
if k == ord('s'): # 按下s键,显示暂停
# time.sleep(7)
cv2.waitKey(7000)
index += 1
cv2.destroyAllWindows()
for cap in cap_list:
cap.release() # 释放摄像头资源
8. 优化,清数据代码,通过装饰器中断执行太久的cap.read()操作,代码如下:
import cv2
import numpy as np
import threading
from subprocess import PIPE,Popen
# import eventlet # 没有效果
import time
import signal
cam_list_ = [0, 2, 4, 6, 8, 10, 12, 14]
cap_list = []
def set_timeout(num, callback): # 只能在linux系统上用,且只能运行在主线程
def wrap(func):
def handle(signum, frame): # 收到信号 SIGALRM 后的回调函数,第一个参数是信号的数字,第二个参数是the interrupted stack frame.
raise RuntimeError
def to_do(*args, **kwargs):
try:
signal.signal(signal.SIGALRM, handle) # 设置信号和回调函数
signal.alarm(num) # 设置 num 秒的闹钟
print('start clear camera cache.')
r = func(*args, **kwargs)
print('close alarm signal.')
signal.alarm(0) # 关闭闹钟
return r
except RuntimeError as e:
callback()
return to_do
return wrap
def after_timeout(): # 超时后的处理函数
print("end clear camera cache !")
def find_cam():
'''
根据设备名称获取相机设备号
使用前需要先安装v4l-utils
Installing v4l-utils (debian) gives one the handy v4l2-ctl command:
$ v4l2-ctl --list-devices
KS2A543: KS2A543 (usb-0000:00:14.0-2.1):
/dev/video2
/dev/video3
KS2A543: KS2A543 (usb-0000:00:14.0-2.2):
/dev/video4
/dev/video5
KS2A543: KS2A543 (usb-0000:00:14.0-2.3):
/dev/video6
/dev/video7
KS2A543: KS2A543 (usb-0000:00:14.0-2.4):
/dev/video8
/dev/video9
'''
cam_list = []
cmd = ["/usr/bin/v4l2-ctl", "--list-devices"]
out, err = Popen(cmd, stdout=PIPE, stderr=PIPE).communicate()
out = out.strip()
for dev_name, dev_idx1, dev_idx2 in [i.split(b'\n\t') for i in out.split(b'\n\n')]:
index = dev_idx1.decode('utf8').split('video')[1]
cam_list.append(index)
return cam_list
start_time = time.time()
cam_list = find_cam()
print(cam_list)
for cam_num in cam_list: # 根据摄像头编号创建视频处理句柄
cap = cv2.VideoCapture(int(cam_num))
cap_list.append(cap)
flag = 0
for cap in cap_list: # 判断摄像头是否可用
if cap.isOpened():
flag += 1
@set_timeout(1, after_timeout) # signal.alarm(num) num只支持int
def clear_camera_cache_dec(cap): # 清除摄像头缓存数据, 有装饰器,只能在主线程运行
success = cap.grab()
while success:
success = cap.grab()
def clear_camera_cache(cap): # 清除摄像头缓存数据,无装饰器,可在子线程运行
success = cap.grab()
while success:
success = cap.grab()
threads = []
# for cap in cap_list:
# cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) #设置宽度
# cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 360) #设置高度
# t = threading.Thread(target=clear_camera_cache, args=(cap,)) # 多线程清除摄像头缓存数据
# threads.append(t)
# t.start()
# for t in threads: # 父进程等待清缓存的子进程执行完毕
# t.join()
for cap in cap_list:
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) #设置宽度
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 360) #设置高度
clear_camera_cache_dec(cap)
end_time = time.time()
print(f'time:{end_time-start_time}')
index = 1
frame_test = np.zeros((360, 640, 3), dtype=np.uint8)
while flag == 8:
frame_list = []
res_flag = 0
for cap in cap_list:
cap.set(13, 1.0) # 设置色调=1触发
for cap in cap_list:
res, frame = cap.read() # 读取摄像头数据
if res:
res_flag += 1
frame_list.append(frame)
if res_flag == 8:
# if res0 and res1 and res2:
vs1 = np.hstack((frame_list[0], frame_list[1], frame_list[2]))
vs2 = np.hstack((frame_list[3], frame_list[4], frame_list[5]))
vs3 = np.hstack((frame_list[6], frame_list[7], frame_test)) # 合并摄像头数据
result = np.vstack((vs1, vs2, vs3))
cv2.imshow('camare:', result) # 显示数据
else:
# print(f'res0:{res0},res1:{res1},res2:{res2}')
print(f'res:{res_flag}')
# print(f'res0:{success0},res1:{success1},res2:{success2},res3:{success3},res4:{success4},res5:{success5},res6:{success6}, res7:{success7}')
k = cv2.waitKey(1) & 0xFF
if k == ord('q'):
break
if k == ord('s'): # 按下s键,显示暂停
# time.sleep(7)
cv2.waitKey(6000)
index += 1
cv2.destroyAllWindows()
for cap in cap_list:
cap.release() # 释放摄像头资源