Merge pull request #207 from AnthonyWharton/refactor-reader
Refactor video capture logic
This commit is contained in:
commit
af2568c390
4 changed files with 134 additions and 128 deletions
|
|
@ -9,6 +9,7 @@ import configparser
|
|||
import builtins
|
||||
import cv2
|
||||
import numpy as np
|
||||
from recorders.video_capture import VideoCapture
|
||||
|
||||
# Try to import dlib and give a nice error if we can't
|
||||
# Add should be the first point where import issues show up
|
||||
|
|
@ -35,10 +36,6 @@ if not os.path.isfile(path + "/../dlib-data/shape_predictor_5_face_landmarks.dat
|
|||
config = configparser.ConfigParser()
|
||||
config.read(path + "/../config.ini")
|
||||
|
||||
if not os.path.exists(config.get("video", "device_path")):
|
||||
print("Camera path is not configured correctly, please edit the 'device_path' config value.")
|
||||
sys.exit(1)
|
||||
|
||||
use_cnn = config.getboolean("core", "use_cnn", fallback=False)
|
||||
if use_cnn:
|
||||
face_detector = dlib.cnn_face_detection_model_v1(path + "/../dlib-data/mmod_human_face_detector.dat")
|
||||
|
|
@ -98,36 +95,8 @@ insert_model = {
|
|||
"data": []
|
||||
}
|
||||
|
||||
# Check if the user explicitly set ffmpeg as recorder
|
||||
if config.get("video", "recording_plugin") == "ffmpeg":
|
||||
# Set the capture source for ffmpeg
|
||||
from recorders.ffmpeg_reader import ffmpeg_reader
|
||||
video_capture = ffmpeg_reader(config.get("video", "device_path"), config.get("video", "device_format"))
|
||||
elif config.get("video", "recording_plugin") == "pyv4l2":
|
||||
# Set the capture source for pyv4l2
|
||||
from recorders.pyv4l2_reader import pyv4l2_reader
|
||||
video_capture = pyv4l2_reader(config.get("video", "device_path"), config.get("video", "device_format"))
|
||||
else:
|
||||
# Start video capture on the IR camera through OpenCV
|
||||
video_capture = cv2.VideoCapture(config.get("video", "device_path"))
|
||||
|
||||
# Force MJPEG decoding if true
|
||||
if config.getboolean("video", "force_mjpeg", fallback=False):
|
||||
# Set a magic number, will enable MJPEG but is badly documentated
|
||||
video_capture.set(cv2.CAP_PROP_FOURCC, 1196444237)
|
||||
|
||||
# Set the frame width and height if requested
|
||||
fw = config.getint("video", "frame_width", fallback=-1)
|
||||
fh = config.getint("video", "frame_height", fallback=-1)
|
||||
if fw != -1:
|
||||
video_capture.set(cv2.CAP_PROP_FRAME_WIDTH, fw)
|
||||
|
||||
if fh != -1:
|
||||
video_capture.set(cv2.CAP_PROP_FRAME_HEIGHT, fh)
|
||||
|
||||
# Request a frame to wake the camera up
|
||||
video_capture.grab()
|
||||
|
||||
# Set up video_capture
|
||||
video_capture = VideoCapture(config)
|
||||
print("\nPlease look straight into the camera")
|
||||
|
||||
# Give the user time to read
|
||||
|
|
@ -141,10 +110,7 @@ dark_threshold = config.getfloat("video", "dark_threshold")
|
|||
|
||||
# Loop through frames till we hit a timeout
|
||||
while frames < 60:
|
||||
# Grab a single frame of video
|
||||
# Don't remove ret, it doesn't work without it
|
||||
ret, frame = video_capture.read()
|
||||
gsframe = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
||||
frame, gsframe = video_capture.read_frame()
|
||||
|
||||
# Create a histogram of the image with 8 values
|
||||
hist = cv2.calcHist([gsframe], [0], None, [8], [0, 256])
|
||||
|
|
@ -165,8 +131,6 @@ while frames < 60:
|
|||
if face_locations:
|
||||
break
|
||||
|
||||
video_capture.release()
|
||||
|
||||
# If more than 1 faces are detected we can't know wich one belongs to the user
|
||||
if len(face_locations) > 1:
|
||||
print("Multiple faces detected, aborting")
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import sys
|
|||
import time
|
||||
import cv2
|
||||
import dlib
|
||||
from recorders.video_capture import VideoCapture
|
||||
|
||||
# Get the absolute path to the current file
|
||||
path = os.path.dirname(os.path.abspath(__file__))
|
||||
|
|
@ -20,22 +21,7 @@ if config.get("video", "recording_plugin") != "opencv":
|
|||
print("Aborting")
|
||||
sys.exit(12)
|
||||
|
||||
# Start capturing from the configured webcam
|
||||
video_capture = cv2.VideoCapture(config.get("video", "device_path"))
|
||||
|
||||
# Force MJPEG decoding if true
|
||||
if config.getboolean("video", "force_mjpeg", fallback=False):
|
||||
# Set a magic number, will enable MJPEG but is badly documented
|
||||
video_capture.set(cv2.CAP_PROP_FOURCC, 1196444237)
|
||||
|
||||
# Set the frame width and height if requested
|
||||
fw = config.getint("video", "frame_width", fallback=-1)
|
||||
fh = config.getint("video", "frame_height", fallback=-1)
|
||||
if fw != -1:
|
||||
video_capture.set(cv2.CAP_PROP_FRAME_WIDTH, fw)
|
||||
|
||||
if fh != -1:
|
||||
video_capture.set(cv2.CAP_PROP_FRAME_HEIGHT, fh)
|
||||
video_capture = VideoCapture(config)
|
||||
|
||||
# Read exposure and dark_thresholds from config to use in the main loop
|
||||
exposure = config.getint("video", "exposure", fallback=-1)
|
||||
|
|
@ -109,17 +95,7 @@ try:
|
|||
sec_frames = 0
|
||||
|
||||
# Grab a single frame of video
|
||||
ret, frame = video_capture.read()
|
||||
|
||||
try:
|
||||
# Convert from color to grayscale
|
||||
# First processing of frame, so frame errors show up here
|
||||
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
||||
except RuntimeError:
|
||||
pass
|
||||
except cv2.error:
|
||||
print("\nUnknown camera, please check your 'device_path' config value.\n")
|
||||
raise
|
||||
_, frame = video_capture.read_frame()
|
||||
|
||||
frame = clahe.apply(frame)
|
||||
# Make a frame to put overlays in
|
||||
|
|
@ -211,8 +187,8 @@ try:
|
|||
# are captured and even after a delay it does not
|
||||
# always work. Setting exposure at every frame is
|
||||
# reliable though.
|
||||
video_capture.set(cv2.CAP_PROP_AUTO_EXPOSURE, 1.0) # 1 = Manual
|
||||
video_capture.set(cv2.CAP_PROP_EXPOSURE, float(exposure))
|
||||
video_capture.intenal.set(cv2.CAP_PROP_AUTO_EXPOSURE, 1.0) # 1 = Manual
|
||||
video_capture.intenal.set(cv2.CAP_PROP_EXPOSURE, float(exposure))
|
||||
|
||||
# On ctrl+C
|
||||
except KeyboardInterrupt:
|
||||
|
|
@ -220,5 +196,4 @@ except KeyboardInterrupt:
|
|||
print("\nClosing window")
|
||||
|
||||
# Release handle to the webcam
|
||||
video_capture.release()
|
||||
cv2.destroyAllWindows()
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import cv2
|
|||
import dlib
|
||||
import numpy as np
|
||||
import _thread as thread
|
||||
from recorders.video_capture import VideoCapture
|
||||
|
||||
|
||||
def init_detector(lock):
|
||||
|
|
@ -46,13 +47,6 @@ def init_detector(lock):
|
|||
timings["ll"] = time.time() - timings["ll"]
|
||||
lock.release()
|
||||
|
||||
|
||||
def stop(status):
|
||||
"""Stop the execution and close video stream"""
|
||||
video_capture.release()
|
||||
sys.exit(status)
|
||||
|
||||
|
||||
# Make sure we were given an username to tast against
|
||||
if len(sys.argv) < 2:
|
||||
sys.exit(12)
|
||||
|
|
@ -113,36 +107,7 @@ thread.start_new_thread(init_detector, (lock, ))
|
|||
# Start video capture on the IR camera
|
||||
timings["ic"] = time.time()
|
||||
|
||||
# Check if the user explicitly set ffmpeg as recorder
|
||||
if config.get("video", "recording_plugin") == "ffmpeg":
|
||||
# Set the capture source for ffmpeg
|
||||
from recorders.ffmpeg_reader import ffmpeg_reader
|
||||
video_capture = ffmpeg_reader(config.get("video", "device_path"), config.get("video", "device_format"))
|
||||
elif config.get("video", "recording_plugin") == "pyv4l2":
|
||||
# Set the capture source for pyv4l2
|
||||
from recorders.pyv4l2_reader import pyv4l2_reader
|
||||
video_capture = pyv4l2_reader(config.get("video", "device_path"), config.get("video", "device_format"))
|
||||
else:
|
||||
# Start video capture on the IR camera through OpenCV
|
||||
video_capture = cv2.VideoCapture(config.get("video", "device_path"))
|
||||
|
||||
# Force MJPEG decoding if true
|
||||
if config.getboolean("video", "force_mjpeg", fallback=False):
|
||||
# Set a magic number, will enable MJPEG but is badly documented
|
||||
# 1196444237 is "GPJM" in ASCII
|
||||
video_capture.set(cv2.CAP_PROP_FOURCC, 1196444237)
|
||||
|
||||
# Set the frame width and height if requested
|
||||
fw = config.getint("video", "frame_width", fallback=-1)
|
||||
fh = config.getint("video", "frame_height", fallback=-1)
|
||||
if fw != -1:
|
||||
video_capture.set(cv2.CAP_PROP_FRAME_WIDTH, fw)
|
||||
if fh != -1:
|
||||
video_capture.set(cv2.CAP_PROP_FRAME_HEIGHT, fh)
|
||||
|
||||
# Capture a single frame so the camera becomes active
|
||||
# This will let the camera adjust its light levels while we're importing for faster scanning
|
||||
video_capture.grab()
|
||||
video_capture = VideoCapture(config)
|
||||
|
||||
# Read exposure from config to use in the main loop
|
||||
exposure = config.getint("video", "exposure", fallback=-1)
|
||||
|
|
@ -158,7 +123,7 @@ del lock
|
|||
# Fetch the max frame height
|
||||
max_height = config.getfloat("video", "max_height", fallback=0.0)
|
||||
# Get the height of the image
|
||||
height = video_capture.get(cv2.CAP_PROP_FRAME_HEIGHT) or 1
|
||||
height = video_capture.internal.get(cv2.CAP_PROP_FRAME_HEIGHT) or 1
|
||||
|
||||
# Calculate the amount the image has to shrink
|
||||
scaling_factor = (max_height / height) or 1
|
||||
|
|
@ -178,24 +143,10 @@ while True:
|
|||
|
||||
# Stop if we've exceded the time limit
|
||||
if time.time() - timings["fr"] > timeout:
|
||||
stop(11)
|
||||
sys.exit(11)
|
||||
|
||||
# Grab a single frame of video
|
||||
ret, frame = video_capture.read()
|
||||
|
||||
if frames == 1 and ret is False:
|
||||
print("Could not read from camera")
|
||||
exit(12)
|
||||
|
||||
try:
|
||||
# Convert from color to grayscale
|
||||
# First processing of frame, so frame errors show up here
|
||||
gsframe = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
||||
except RuntimeError:
|
||||
gsframe = frame
|
||||
except cv2.error:
|
||||
print("\nUnknown camera, please check your 'device_path' config value.\n")
|
||||
raise
|
||||
frame, gsframe = video_capture.read_frame()
|
||||
|
||||
# Create a histogram of the image with 8 values
|
||||
hist = cv2.calcHist([gsframe], [0], None, [8], [0, 256])
|
||||
|
|
@ -255,7 +206,7 @@ while True:
|
|||
print_timing("Total time", "tt")
|
||||
|
||||
print("\nResolution")
|
||||
width = video_capture.get(cv2.CAP_PROP_FRAME_WIDTH) or 1
|
||||
width = video_capture.fw or 1
|
||||
print(" Native: %dx%d" % (height, width))
|
||||
# Save the new size for diagnostics
|
||||
scale_height, scale_width = frame.shape[:2]
|
||||
|
|
@ -269,7 +220,7 @@ while True:
|
|||
print("Winning model: %d (\"%s\")" % (match_index, models[match_index]["label"]))
|
||||
|
||||
# End peacefully
|
||||
stop(0)
|
||||
sys.exit(0)
|
||||
|
||||
if exposure != -1:
|
||||
# For a strange reason on some cameras (e.g. Lenoxo X1E)
|
||||
|
|
@ -277,5 +228,5 @@ while True:
|
|||
# are captured and even after a delay it does not
|
||||
# always work. Setting exposure at every frame is
|
||||
# reliable though.
|
||||
video_capture.set(cv2.CAP_PROP_AUTO_EXPOSURE, 1.0) # 1 = Manual
|
||||
video_capture.set(cv2.CAP_PROP_EXPOSURE, float(exposure))
|
||||
video_capture.intenal.set(cv2.CAP_PROP_AUTO_EXPOSURE, 1.0) # 1 = Manual
|
||||
video_capture.intenal.set(cv2.CAP_PROP_EXPOSURE, float(exposure))
|
||||
|
|
|
|||
116
src/recorders/video_capture.py
Normal file
116
src/recorders/video_capture.py
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
# Top level class for a video capture providing simplified API's for common
|
||||
# functions
|
||||
|
||||
# Import required modules
|
||||
import configparser
|
||||
import cv2
|
||||
import os
|
||||
import sys
|
||||
|
||||
"""
|
||||
Class to provide boilerplate code to build a video recorder with the
|
||||
correct settings from the config file.
|
||||
|
||||
The internal recorder can be accessed with 'video_capture.internal'
|
||||
"""
|
||||
class VideoCapture:
|
||||
|
||||
"""
|
||||
Creates a new VideoCapture instance depending on the settings in the
|
||||
provided config file.
|
||||
|
||||
Config can either be a string to the path, or a pre-setup configparser.
|
||||
"""
|
||||
def __init__(self, config):
|
||||
if isinstance(config, str):
|
||||
self.config = configparser.ConfigParser()
|
||||
self.config.read(config)
|
||||
else:
|
||||
self.config = config
|
||||
|
||||
# Check device path
|
||||
if not os.path.exists(config.get("video", "device_path")):
|
||||
print("Camera path is not configured correctly, " +
|
||||
"please edit the 'device_path' config value.")
|
||||
sys.exit(1)
|
||||
|
||||
# Create reader
|
||||
self.internal = None # The internal video recorder
|
||||
self.fw = None # The frame width
|
||||
self.fh = None # The frame height
|
||||
self._create_reader()
|
||||
|
||||
# Request a frame to wake the camera up
|
||||
self.internal.grab()
|
||||
|
||||
"""
|
||||
Frees resources when destroyed
|
||||
"""
|
||||
def __del__(self):
|
||||
if self is not None:
|
||||
self.internal.release()
|
||||
|
||||
"""
|
||||
Reads a frame, returns the frame and an attempted grayscale conversion of
|
||||
the frame in a tuple:
|
||||
|
||||
(frame, grayscale_frame)
|
||||
|
||||
If the grayscale conversion fails, both items in the tuple are identical.
|
||||
"""
|
||||
def read_frame(self):
|
||||
# Grab a single frame of video
|
||||
# Don't remove ret, it doesn't work without it
|
||||
ret, frame = self.internal.read()
|
||||
if not ret:
|
||||
print("Failed to read camera specified in your 'device_path', " +
|
||||
"aborting")
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
# Convert from color to grayscale
|
||||
# First processing of frame, so frame errors show up here
|
||||
gsframe = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
||||
except RuntimeError:
|
||||
gsframe = frame
|
||||
except cv2.error:
|
||||
print("\nAn error occurred in OpenCV\n")
|
||||
raise
|
||||
return frame, gsframe
|
||||
|
||||
"""
|
||||
Sets up the video reader instance
|
||||
"""
|
||||
def _create_reader(self):
|
||||
if self.config.get("video", "recording_plugin") == "ffmpeg":
|
||||
# Set the capture source for ffmpeg
|
||||
from recorders.ffmpeg_reader import ffmpeg_reader
|
||||
self.internal = ffmpeg_reader(
|
||||
self.config.get("video", "device_path"),
|
||||
self.config.get("video", "device_format")
|
||||
)
|
||||
elif self.config.get("video", "recording_plugin") == "pyv4l2":
|
||||
# Set the capture source for pyv4l2
|
||||
from recorders.pyv4l2_reader import pyv4l2_reader
|
||||
self.internal = pyv4l2_reader(
|
||||
self.config.get("video", "device_path"),
|
||||
self.config.get("video", "device_format")
|
||||
)
|
||||
else:
|
||||
# Start video capture on the IR camera through OpenCV
|
||||
self.internal = cv2.VideoCapture(
|
||||
self.config.get("video", "device_path")
|
||||
)
|
||||
|
||||
# Force MJPEG decoding if true
|
||||
if self.config.getboolean("video", "force_mjpeg", fallback=False):
|
||||
# Set a magic number, will enable MJPEG but is badly documentated
|
||||
self.internal.set(cv2.CAP_PROP_FOURCC, 1196444237)
|
||||
|
||||
# Set the frame width and height if requested
|
||||
self.fw = self.config.getint("video", "frame_width", fallback=-1)
|
||||
self.fh = self.config.getint("video", "frame_height", fallback=-1)
|
||||
if self.fw != -1:
|
||||
self.internal.set(cv2.CAP_PROP_FRAME_WIDTH, self.fw)
|
||||
if self.fh != -1:
|
||||
self.internal.set(cv2.CAP_PROP_FRAME_HEIGHT, self.fh)
|
||||
Loading…
Reference in a new issue