diff --git a/dshowcapture.py b/dshowcapture.py index f91650f..cfb4e79 100644 --- a/dshowcapture.py +++ b/dshowcapture.py @@ -5,6 +5,7 @@ import numpy as np from ctypes import * from PIL import Image import cv2 +import json def resolve(name): f = os.path.join(os.path.dirname(__file__), name) @@ -13,7 +14,7 @@ def resolve(name): lib = None def create_frame_buffer(width, height): - buffer = bytearray(width * height * 4) + buffer = bytearray(width * height * 4 * 4) char_array = c_char * len(buffer) return char_array.from_buffer(buffer) @@ -31,16 +32,20 @@ class DShowCapture(): lib.get_devices.argtypes = [c_void_p] lib.get_device.argtypes = [c_void_p, c_int, c_char_p, c_int] lib.capture_device.argtypes = [c_void_p, c_int, c_int, c_int, c_int] + lib.capture_device_by_dcap.argtypes = [c_void_p, c_int, c_int, c_int, c_int, c_longlong] lib.capture_device_default.argtypes = [c_void_p, c_int] lib.get_width.argtypes = [c_void_p] lib.get_height.argtypes = [c_void_p] lib.get_fps.argtypes = [c_void_p] lib.get_flipped.argtypes = [c_void_p] lib.get_colorspace.argtypes = [c_void_p] + lib.get_colorspace_internal.argtypes = [c_void_p] lib.capturing.argtypes = [c_void_p] lib.get_frame.argtypes = [c_void_p, c_int, c_char_p, c_int] lib.get_size.argtypes = [c_void_p] lib.stop_capture.argtypes = [c_void_p] + lib.get_json_length.argtypes = [c_void_p] + lib.get_json.argtypes = [c_void_p, c_char_p, c_int] lib.destroy_capture.argtypes = [c_void_p] self.lib = lib self.cap = lib.create_capture() @@ -49,6 +54,7 @@ class DShowCapture(): self.have_devices = False self.size = None self.real_size = None + self.info = None def __del__(self): if not self.buffer is None: @@ -65,6 +71,14 @@ class DShowCapture(): name_str = str(self.name_buffer.value.decode('utf8')) return name_str + def get_info(self): + json_length = self.lib.get_json_length(self.cap); + json_buffer = create_string_buffer(json_length) + self.lib.get_json(self.cap, json_buffer, json_length); + json_text = str(json_buffer.value.decode('utf8')) + self.info = json.loads(json_text) + return self.info + def capture_device(self, cam, width, height, fps): if not self.have_devices: self.get_devices() @@ -81,6 +95,23 @@ class DShowCapture(): self.real_size = None return ret; + def capture_device_by_dcap(self, cam, dcap, width, height, fps): + if not self.have_devices: + self.get_devices() + fps = int(10000000 / fps) + ret = self.lib.capture_device_by_dcap(self.cap, cam, dcap, width, height, fps) == 1 + if ret: + self.width = self.get_width() + self.height = self.get_height() + self.flipped = self.get_flipped() + self.colorspace = self.get_colorspace() + self.size = self.width * self.height * 4 * 4 + self.buffer = create_frame_buffer(self.width, self.height) + else: + self.size = None + self.real_size = None + return ret; + def capture_device_default(self, cam): if not self.have_devices: self.get_devices() @@ -90,7 +121,7 @@ class DShowCapture(): self.height = self.get_height() self.flipped = self.get_flipped() self.colorspace = self.get_colorspace() - self.size = self.width * self.height * 4 + self.size = self.width * self.height * 4 * 4 self.buffer = create_frame_buffer(self.width, self.height) else: self.size = None @@ -110,7 +141,13 @@ class DShowCapture(): return self.lib.get_flipped(self.cap) != 0 def get_colorspace(self): - return self.lib.get_colorspace(self.cap) + colorspace = self.lib.get_colorspace(self.cap) + if colorspace == 0: + colorspace = self.lib.get_colorspace_internal(self.cap) + return colorspace + + def get_colorspace_internal(self): + return self.lib.get_colorspace_internal(self.cap) def capturing(self): return self.lib.capturing(self.cap) == 1 @@ -118,32 +155,36 @@ class DShowCapture(): def get_frame(self, timeout): if self.size is None: return None - if self.lib.get_frame(self.cap, timeout, self.buffer, self.size) == 0: - return None - if self.real_size is None: - self.real_size = self.lib.get_size(self.cap) + self.real_size = self.lib.get_frame(self.cap, timeout, self.buffer, self.size) img = np.frombuffer(self.buffer, dtype=np.uint8)[0:self.real_size] if self.colorspace in [100, 101]: - img = cv2.cvtColor(img.reshape((self.height,self.width,4)), cv2.COLOR_BGRA2BGR); + if self.real_size == self.height * self.width * 4: + img = cv2.cvtColor(img.reshape((self.height,self.width,4)), cv2.COLOR_BGRA2BGR) + elif self.real_size == self.height * self.width * 3: + img = img.reshape((self.height,self.width,3)) + else: + return None if not self.flipped: img = cv2.flip(img, 0) return img elif self.colorspace == 200: - img = cv2.cvtColor(img.reshape((3 * self.height // 2,self.width,1)), cv2.COLOR_YUV2BGR_I420); + img = cv2.cvtColor(img.reshape((3 * self.height // 2,self.width,1)), cv2.COLOR_YUV2BGR_I420) elif self.colorspace == 201: - img = cv2.cvtColor(img.reshape((3 * self.height // 2,self.width,1)), cv2.COLOR_YUV2BGR_NV12); + img = cv2.cvtColor(img.reshape((3 * self.height // 2,self.width,1)), cv2.COLOR_YUV2BGR_NV12) elif self.colorspace == 202: - img = cv2.cvtColor(img.reshape((3 * self.height // 2,self.width,1)), cv2.COLOR_YUV2BGR_YV12); + img = cv2.cvtColor(img.reshape((3 * self.height // 2,self.width,1)), cv2.COLOR_YUV2BGR_YV12) elif self.colorspace == 203: - img = cv2.cvtColor(img.reshape((self.height,self.width,1)), cv2.COLOR_GRAY2BGR); + img = cv2.cvtColor(img.reshape((self.height,self.width,1)), cv2.COLOR_GRAY2BGR) elif self.colorspace == 300: - img = cv2.cvtColor(img.reshape((self.height,self.width,2)), cv2.COLOR_YUV2BGR_YVYU); + img = cv2.cvtColor(img.reshape((self.height,self.width,2)), cv2.COLOR_YUV2BGR_YVYU) elif self.colorspace == 301: - img = cv2.cvtColor(img.reshape((self.height,self.width,2)), cv2.COLOR_YUV2BGR_YUY2); + img = cv2.cvtColor(img.reshape((self.height,self.width,2)), cv2.COLOR_YUV2BGR_YUY2) elif self.colorspace == 302: - img = cv2.cvtColor(img.reshape((self.height,self.width,2)), cv2.COLOR_YUV2BGR_UYVY); + img = cv2.cvtColor(img.reshape((self.height,self.width,2)), cv2.COLOR_YUV2BGR_UYVY) elif self.colorspace == 303: return None + elif self.colorspace == 400: + img = cv2.imdecode(img, cv2.IMREAD_COLOR); if self.flipped: img = cv2.flip(img, 0) return img @@ -186,7 +227,7 @@ if __name__ == "__main__": width = cap.get_width() height = cap.get_height() flipped = cap.get_flipped() - print(f"Width: {width} Height: {height} FPS: {cap.get_fps()} Flipped: {flipped} Colorspace: {cap.get_colorspace()}") + print(f"Width: {width} Height: {height} FPS: {cap.get_fps()} Flipped: {flipped} Colorspace: {cap.get_colorspace()} Internal: {cap.get_colorspace_internal()}") while True: img = cap.get_frame(1000) diff --git a/dshowcapture/dshowcapture_x64.dll b/dshowcapture/dshowcapture_x64.dll index eb8a233..98f9f9a 100644 Binary files a/dshowcapture/dshowcapture_x64.dll and b/dshowcapture/dshowcapture_x64.dll differ diff --git a/dshowcapture/dshowcapture_x86.dll b/dshowcapture/dshowcapture_x86.dll index 5cb2b62..3437f14 100644 Binary files a/dshowcapture/dshowcapture_x86.dll and b/dshowcapture/dshowcapture_x86.dll differ diff --git a/facetracker.py b/facetracker.py index d030c02..5fe2a41 100644 --- a/facetracker.py +++ b/facetracker.py @@ -9,10 +9,11 @@ parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFo parser.add_argument("-i", "--ip", help="Set IP address for sending tracking data", default="127.0.0.1") parser.add_argument("-p", "--port", type=int, help="Set port for sending tracking data", default=11573) if os.name == 'nt': - parser.add_argument("-l", "--list-cameras", type=int, help="Set this to 1 to list the available cameras and quit, set this to a number higher than 1 to output only the names", default=0) + parser.add_argument("-l", "--list-cameras", type=int, help="Set this to 1 to list the available cameras and quit, set this to 2 to output only the names and to 3 to output camera names and device capabilities", default=0) parser.add_argument("-W", "--width", type=int, help="Set camera and raw RGB width", default=640) parser.add_argument("-H", "--height", type=int, help="Set camera and raw RGB height", default=360) parser.add_argument("-F", "--fps", type=int, help="Set camera frames per second", default=24) + parser.add_argument("-D", "--dcap", type=int, help="Set which device capability line to use or -1 to use the default camera settings", default=None) else: parser.add_argument("-W", "--width", type=int, help="Set raw RGB width", default=640) parser.add_argument("-H", "--height", type=int, help="Set raw RGB height", default=360) @@ -69,15 +70,30 @@ sys.stderr = OutputLog(output_logfile, sys.stderr) if os.name == 'nt' and args.list_cameras > 0: import dshowcapture cap = dshowcapture.DShowCapture() - camera_count = cap.get_devices() - if args.list_cameras == 1: - print("Available cameras:") - for i in range(camera_count): - camera_name = cap.get_device(i) + if args.list_cameras == 3: + info = cap.get_info() + unit = 10000000.; + formats = {0: "Any", 1: "Unknown", 100: "ARGB", 101: "XRGB", 200: "I420", 201: "NV12", 202: "YV12", 203: "Y800", 300: "YVYU", 301: "YUY2", 302: "UYVY", 303: "HDYC (Unsupported)", 400: "MJPEG", 401: "H264" } + for cam in info: + print(f"{cam['id']}: {cam['name']}") + for caps in cam['caps']: + format = caps['format'] + if caps['format'] in formats: + format = formats[caps['format']] + if caps['minCX'] == caps['maxCX'] and caps['minCY'] == caps['maxCY']: + print(f" {caps['id']}: Resolution: {caps['minCX']}x{caps['minCY']} FPS: {unit/caps['maxInterval']:.3f}-{unit/caps['minInterval']:.3f} Format: {format}") + else: + print(f" {caps['id']}: Resolution: {caps['minCX']}x{caps['minCY']}-{caps['maxCX']}x{caps['maxCY']} FPS: {unit/caps['maxInterval']:.3f}-{unit/caps['minInterval']:.3f} Format: {format}") + else: + camera_count = cap.get_devices() if args.list_cameras == 1: - print(f"{i}: {camera_name}") - else: - print(camera_name) + print("Available cameras:") + for i in range(camera_count): + camera_name = cap.get_device(i) + if args.list_cameras == 1: + print(f"{i}: {camera_name}") + else: + print(camera_name) cap.destroy_capture() sys.exit(0) @@ -86,7 +102,7 @@ import time import cv2 import socket import struct -from input_reader import InputReader, VideoReader, try_int +from input_reader import InputReader, VideoReader, DShowCaptureReader, try_int from tracker import Tracker target_ip = args.ip @@ -96,11 +112,15 @@ if args.faces >= 40: print("Transmission of tracking data over network is not supported with 40 or more faces.") fps = 0 +dcap = None use_dshowcapture_flag = False if os.name == 'nt': fps = args.fps + dcap = args.dcap use_dshowcapture_flag = True if args.use_dshowcapture == 1 else False - input_reader = InputReader(args.capture, args.raw_rgb, args.width, args.height, fps, use_dshowcapture=use_dshowcapture_flag) + input_reader = InputReader(args.capture, args.raw_rgb, args.width, args.height, fps, use_dshowcapture=use_dshowcapture_flag, dcap=dcap) + if args.dcap == -1 and type(input_reader) == DShowCaptureReader: + fps = min(fps, input_reader.device.get_fps()) else: input_reader = InputReader(args.capture, args.raw_rgb, args.width, args.height, fps, use_dshowcapture=use_dshowcapture_flag) if type(input_reader.reader) == VideoReader: @@ -146,7 +166,7 @@ try: source_name = input_reader.name while repeat or input_reader.is_open(): if not input_reader.is_open() or need_reinit == 1: - input_reader = InputReader(args.capture, args.raw_rgb, args.width, args.height, fps, use_dshowcapture=use_dshowcapture_flag) + input_reader = InputReader(args.capture, args.raw_rgb, args.width, args.height, fps, use_dshowcapture=use_dshowcapture_flag, dcap=dcap) if input_reader.name != source_name: print(f"Failed to reinitialize camera and got {input_reader.name} instead of {source_name}.") sys.exit(1) diff --git a/input_reader.py b/input_reader.py index 8eb3542..709308d 100644 --- a/input_reader.py +++ b/input_reader.py @@ -54,16 +54,26 @@ class EscapiReader(VideoReader): escapi.deinit_camera(self.device) class DShowCaptureReader(VideoReader): - def __init__(self, capture, width, height, fps, use_dshowcapture=True): + def __init__(self, capture, width, height, fps, use_dshowcapture=True, dcap=None): self.device = None self.width = width self.height = height self.fps = fps + self.dcap = dcap; self.device = dshowcapture.DShowCapture() self.device.get_devices() self.name = self.device.get_device(capture) - self.device.capture_device(capture, self.width, self.height, self.fps) - print(f"Camera: \"{self.name}\" Resolution: {self.device.get_width()}x{self.device.get_height()} Frame rate: {self.device.get_fps()} Colorspace: {self.device.get_colorspace()} Flipped: {self.device.get_flipped()}") + if dcap is None: + print("Opening classic") + self.device.capture_device(capture, self.width, self.height, self.fps) + else: + if dcap < 0: + print("Opening default") + self.device.capture_device_default(capture) + else: + print("Opening exact") + self.device.capture_device_by_dcap(capture, dcap, self.width, self.height, self.fps) + print(f"Camera: \"{self.name}\" Capability ID: {dcap} Resolution: {self.device.get_width()}x{self.device.get_height()} Frame rate: {self.device.get_fps()} Colorspace: {self.device.get_colorspace()} Internal: {self.device.get_colorspace_internal()} Flipped: {self.device.get_flipped()}") self.timeout = 1000 def is_open(self): return self.device.capturing() @@ -159,7 +169,7 @@ def test_reader(reader): return False class InputReader(): - def __init__(self, capture, raw_rgb, width, height, fps, use_dshowcapture=False): + def __init__(self, capture, raw_rgb, width, height, fps, use_dshowcapture=False, dcap=None): self.reader = None self.name = str(capture) try: @@ -174,7 +184,7 @@ class InputReader(): name = "" try: if use_dshowcapture: - self.reader = DShowCaptureReader(int(capture), width, height, fps) + self.reader = DShowCaptureReader(int(capture), width, height, fps, dcap=dcap) name = self.reader.name good = test_reader(self.reader) self.name = name