项目场景:
项目需要在较暗的光照环境下使用USB相机实时拍着视频,且需要保证相机跑满30帧。如果使用相机默认的自动曝光设置,会因为低光照而自动延长曝光时间,最终导致拍摄帧率只有15帧左右。因此,需要通过OpenCV控制相机关闭自动曝光,并设置手动的曝光参数,以限制实际的曝光时间。从而在部分牺牲拍摄亮度的情况下换取更高的拍摄帧率。
系统环境:win10、ubuntu20.04
OpenCV版本:均为4.7.0
问题描述
软件需要兼容win10、ubuntu20.04,最初在win10上编写和调试。
按照网上的教程,使用cap.set(cv2.CAP_PROP_AUTO_EXPOSURE, value) 设置曝光时,value在0-2.6为手动曝光,2.6-4为自动曝光。于是写了如下代码:
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
exposure = -5
cap.set(cv2.CAP_PROP_AUTO_EXPOSURE, 3.0) # 先打开自动曝光
cap.set(cv2.CAP_PROP_AUTO_EXPOSURE, 1.0) # 再关闭自动曝光
cap.set(cv2.CAP_PROP_EXPOSURE, exposure) # 最后设置曝光参数
测试时发现,相机拍到的图像有时候会变得非常暗。远比使用相机调试软件设置相同参数下拍到的图像暗。并且时而正常时而不正常。
经过多次测试发现,当使用windows系统自带的相机打开USB相机并关闭后,再使用自己编写的程序可以以正常的曝光亮度打开相机一次。但重启自己编写的程序后相机的图像又会变得非常暗。所以,基本可以推测自己的程序设置的参数可能有问题。
于是又自己测试了一遍cap.set(cv2.CAP_PROP_AUTO_EXPOSURE, value) 参数。奇怪的是在设置value为1.0时可以打开自动曝光,而在设置value为0.0时可以关闭自动曝光???
然后把代码改成
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
exposure = -5 # 我这里取-5时相机刚好能达到30帧
cap.set(cv2.CAP_PROP_AUTO_EXPOSURE, 1.0) # 先打开自动曝光
cap.set(cv2.CAP_PROP_AUTO_EXPOSURE, 0.0) # 再关闭自动曝光
cap.set(cv2.CAP_PROP_EXPOSURE, exposure) # 最后设置曝光参数
相机的帧率和亮度一切看起来都正常了。
然而当开始把代码搬到ubuntu上调试的时候一切问题又重来了。相机的图像又变得非常暗,甚至比在win10上出问题时还要暗。出问题的代码如下:
cap = cv2.VideoCapture(0, cv2.CAP_V4L2)
exposure = 300 # linux的曝光参数计算和windows不太一样,我这里取大约300时相机刚好达到30帧
cap.set(cv2.CAP_PROP_AUTO_EXPOSURE, 1.0) # 先打开自动曝光
cap.set(cv2.CAP_PROP_AUTO_EXPOSURE, 0.0) # 再关闭自动曝光
cap.set(cv2.CAP_PROP_EXPOSURE, exposure) # 最后设置曝光参数
一番折腾之后,把cap.set(cv2.CAP_PROP_AUTO_EXPOSURE, value)的参数改回:
cap.set(cv2.CAP_PROP_AUTO_EXPOSURE, 3.0) # 先打开自动曝光
cap.set(cv2.CAP_PROP_AUTO_EXPOSURE, 1.0) # 再关闭自动曝光
拍摄的图像又恢复正常了。
解决方案:
经过测试发现,在windows下,以下参数是正常工作的:
cap.set(cv2.CAP_PROP_AUTO_EXPOSURE, 1.0) # 打开自动曝光
cap.set(cv2.CAP_PROP_AUTO_EXPOSURE, 0.0) # 关闭自动曝光
在ubuntu下,以下参数是正常工作的:
cap.set(cv2.CAP_PROP_AUTO_EXPOSURE, 3.0) # 打开自动曝光
cap.set(cv2.CAP_PROP_AUTO_EXPOSURE, 1.0) # 关闭自动曝光
推测网上主流的教程是以linux系统写的。这个问题目前不确定是系统差异还是OpenCV的bug(毕竟一个开关量用0~4之间的数表示感觉十分反直觉)。
最后,顺带一提cap.set(cv2.CAP_PROP_EXPOSURE, exposure) 设置手动曝光时windows和linux上exposure参数的换算。直观上感觉linux的exposure比较接近快门时间ms数×10。和windows上的曝光参数大致可按
exposure_linux≈10000*2^(exposure_win)
换算
目前可用的程序
import cv2
import os
import numpy as np
import sys
import time
def TakePictures(fileName = '', dirPath='.'):
framesSkip = 30
FPS = 999
interval_ms = 1000.0 / FPS - 1;
manualExposure = -5
inputSrc = 0
if sys.platform.startswith('win'):
cap = cv2.VideoCapture(int(inputSrc), cv2.CAP_DSHOW)
elif sys.platform.startswith('linux'):
cap = cv2.VideoCapture(int(inputSrc), cv2.CAP_V4L2)
else:
print('Unknow System')
return
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1920)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 1080)
cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('M', 'J', 'P', 'G'))
# 打开自动曝光
if sys.platform.startswith('win'):
cap.set(cv2.CAP_PROP_AUTO_EXPOSURE, 1.0)
elif sys.platform.startswith('linux'):
cap.set(cv2.CAP_PROP_AUTO_EXPOSURE, 3.0)
cap.set(cv2.CAP_PROP_AUTO_WB, 1.0)
# 打开自动白平衡
cap.set(cv2.CAP_PROP_AUTO_WB, 1.0)
for i in range(framesSkip):
cap.read();
time.sleep(interval_ms / 1000);
# 关闭自动曝光
# 并设置手动曝光参数
if sys.platform.startswith('win'):
cap.set(cv2.CAP_PROP_AUTO_EXPOSURE, 0.0)
cap.set(cv2.CAP_PROP_EXPOSURE, manualExposure)
elif sys.platform.startswith('linux'):
cap.set(cv2.CAP_PROP_AUTO_EXPOSURE, 1.0) # 关闭自动曝光
cap.set(cv2.CAP_PROP_EXPOSURE, 10000 * 2**manualExposure)
n = 1
goon, frame = cap.read()
# print('size: %d x %d' % (frame.shape[1], frame.shape[0]))
cv2.namedWindow('camera', 0)
dt = interval_ms / 1000
t0 = time.time()
while goon:
cv2.imshow('camera', frame)
key = cv2.waitKey(2)
if key == 13: #ENTER
if fileName == '':
saveFileName = os.path.join(dirPath, 'pic_%04d.png' % (n,))
cv2.imwrite(saveFileName, frame)
n += 1
else:
saveFileName = os.path.join(dirPath, fileName)
cv2.imwrite(saveFileName, frame)
break
print('save: ', saveFileName)
elif key == 27 or key == ord('q'): #ESC
break
goon, frame = cap.read()
t1 = time.time()
dt += (t1 - t0 - dt) * 0.1
t0 = t1
print('FPS = %f' % (1.0/dt), end='\r')
cap.release()
cv2.destroyAllWindows()
if __name__ == '__main__':
TakePictures()