Howdy: HP Spectre x360 13" IR camera support

Created on 1 Dec 2018  路  32Comments  路  Source: boltgolt/howdy

OpenCV does not currently support my IR camera (/dev/video2), although it does support my regular camera (/dev/video0).

I get this error message from OpenCV. While I was able to download and compile a newer version (3.4.4 and 4.0.0) of OpenCV, I could not figure out how to force Howdy to use it.

VIDEOIO ERROR: V4L2: Pixel format of incoming image is unsupported by OpenCV

However, FFMPEG does support the IR camera. I was able to locally modify the add.py and compare.py files to use the FFMPEG python library, using a config variable set by "sudo howdy config" and an IF statement in the code that determines whether to use opencv or ffmpeg. The only issues I'm running into right now is the quality of the IR image is poor in bright daylight or just heavy brightness. This is also happening in Windows as well. I was in an airport yesterday sitting near a window and neither Windows nor the newly modified Howdy would detect my face. When I saved the photo, it appeared that half of my face was super dark, so that kind of makes sense for detection issues. I'm going to work on a dual solution, where it uses both the IR cameras and the regular camera to either merge the images together to increase quality or just attempt to use the normal camera image if the IR image fails to detect due to brightness or other issues. Also, I'm going to attempt to speed up the detection process a little bit. It seems to take a few seconds longer in Howdy than on Windows, likely my own code causing slowness. :-)

What's the best path for me to get these changes merged?
Maybe a better question is, would this be something you would be interested in merging? If not, I'll just fork the repo and use it privately (unless/until openCv works with my IR camera.)

It seems to me like this could open up Howdy to more people, if openCv doesn't detect or work properly, they could possibly use/try ffmpeg detection instead. :)

enhancement

All 32 comments

Basically, the changes are thus:

In both files, add these imports...

import ffmpeg
import numpy
from skimage import color

In add.py

# Open the camera
if config.get("video", "recording_plugin") == "opencv":
    video_capture = cv2.VideoCapture(config.get("video", "device_path"))

    # Force MJPEG decoding if true
    if config.get("video", "force_mjpeg") == "true":
        video_capture.set(cv2.CAP_PROP_FOURCC, 1196444237)

    # Set the frame width and height if requested
    if int(config.get("video", "frame_width")) != -1:
        video_capture.set(cv2.CAP_PROP_FRAME_WIDTH, int(config.get("video", "frame_width")))

    if int(config.get("video", "frame_height")) != -1:
        video_capture.set(cv2.CAP_PROP_FRAME_HEIGHT, int(config.get("video", "frame_height")))

    # Request a frame to wake the camera up
    video_capture.read()
else:
    # Request a frame to wake the camera up
    video_capture = (
        ffmpeg
        .input(config.get("video", "device_name"), format=config.get("video", "device_format"))
        .output('pipe:', vframes=1, format='image2', vcodec='mjpeg')
        .run(capture_stdout=True)
    )
    probe = ffmpeg.probe(config.get("video", "device_name"))

    if int(config.get("video", "frame_width")) != -1:
        frame_width = int(config.get("video", "frame_width"))
    else:
        frame_width = probe['streams'][0]['width']

    if int(config.get("video", "frame_height")) != -1:
        frame_height = int(config.get("video", "frame_height"))
    else:
        frame_height = probe['streams'][0]['height']



...
...
# Loop through frames till we hit a timeout
while frames < 60:
    frames += 1

    # Grab a single frame of video
    # Don't remove ret, it doesn't work without it
    if config.get("video", "recording_plugin") == "opencv":
        ret, frame = video_capture.read()
    else:
        stream, ret = (
            ffmpeg
            .input(config.get("video", "device_name"), format=config.get("video", "device_format"))
            .filter('select', 'gte(n,{})'.format(24))
            .output('pipe:', vframes=1, format='image2', vcodec='mjpeg')
            .run(capture_stdout=True)
        )
        nframe = numpy.frombuffer(stream, numpy.uint8)
        cframe = cv2.imdecode(nframe, 0)
        frame = color.gray2rgb(cframe, alpha=None)

And then in compare, I started adding a way to increase brightness over the course of multiple tries, since in very dark places, my IR camera was really dark. Maybe something with the way I saved the image, not sure...

compare.py

def increase_brightness(img, value=30):
    hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    h, s, v = cv2.split(hsv)

    lim = 255 - value
    v[v > lim] = 255
    v[v <= lim] += value

    final_hsv = cv2.merge((h, s, v))
    img = cv2.cvtColor(final_hsv, cv2.COLOR_HSV2BGR)
    return img

...
...


if config.get("video", "recording_plugin") == "opencv":

    # Start video capture on the IR camera
    video_capture = cv2.VideoCapture(config.get("video", "device_path"))

    # Force MJPEG decoding if true
    if config.get("video", "force_mjpeg") == "true":
        # 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
    if int(config.get("video", "frame_width")) != -1:
        video_capture.set(cv2.CAP_PROP_FRAME_WIDTH, int(config.get("video", "frame_width")))

    if int(config.get("video", "frame_height")) != -1:
        video_capture.set(cv2.CAP_PROP_FRAME_HEIGHT, int(config.get("video", "frame_height")))

    # 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.read()

else:
    # Request a frame to wake the camera up
    video_capture = (
        ffmpeg
        .input(config.get("video", "device_name"), format=config.get("video", "device_format"))
        .output('pipe:', vframes=1, format='image2', vcodec='mjpeg')
        .run(capture_stdout=True)
    )
    probe = ffmpeg.probe(config.get("video", "device_name"))

    if int(config.get("video", "frame_width")) != -1:
        frame_width = int(config.get("video", "frame_width"))
    else:
        frame_width = probe['streams'][0]['width']

    if int(config.get("video", "frame_height")) != -1:
        frame_height = int(config.get("video", "frame_height"))
    else:
        frame_height = probe['streams'][0]['height']

...
...

# Start the read loop
frames = 0
brightness_boost = 0

while True:
    # Increment the frame count every loop
    frames += 1

    # Stop if we've exceded the time limit
    if time.time() - timings[3] > int(config.get("video", "timout")):
        stop(11)

    # Grab a single frame of video
    # Don't remove ret, it doesn't work without it
    if config.get("video", "recording_plugin") == "opencv":
        ret, frame = (video_capture.read())
    else:
        stream, ret = (
            ffmpeg
            .input(config.get("video", "device_name"), format=config.get("video", "device_format"))
            .filter('select', 'gte(n,{})'.format(24))
            .output('pipe:', vframes=1, format='image2', vcodec='mjpeg')
            .run(capture_stdout=True)
        )
        nframe = numpy.frombuffer(stream, numpy.uint8)
        cframe = cv2.imdecode(nframe, 0)
        frame = color.gray2rgb(cframe, alpha=None)
        if brightness_boost > 0:
            frame = increase_brightness(frame, value=brightness_boost)
            print("brightness_boost: " + str(brightness_boost))

...
...
    # Scrip the frame if it exceeds the threshold
    if float(hist[0]) / hist_total * 100 > float(config.get("video", "dark_threshold")):
        dark_tries += 1
        brightness_boost += 10
        continue


And finally, in the [video] section of the config ...

[video]
#
# opencv or ffmpeg.  Choose ffmpeg if your IR device is a grayscale only.
# Run this: v4l2-ctl --list-devices --all
#
recording_plugin = ffmpeg

#
# format to use for ffmpeg. Could be vfwcap or v4l2 or some other thing...
#
device_format = v4l2

#
# device name if using ffmpeg
#
device_name = /dev/video2

Final note, I'm not quite sure yet how to get test.py working with ffmpeg. Seems to be a much larger code modification for that piece.

The FFmpeg option isn't hard to support for face addition or authentication, but the test command heavily depends on OpenCV to show the output. You can add the config value to use FFmpeg in those 2 commands. Be sure to reuse as much code as possible between OpenCV and FFmpeg though.

For now it is sufficient to just disable the test command when FFmpeg is used. This limitation will be gone once we have an actual UI.

As for the speed improvements, take a look at #99. Multiple people are working really hard over there to cut startup times and improve recognition.

I found my issue with speed of detection. It was actually this line:

            .filter('select', 'gte(n,{})'.format(24))

This tells ffmpeg which frame to save. Since the IR emitters turn on and off, frame 24 for me is nearly dark. I just chose it at random when I was testing. frame 30 is also fairly dark as well. The lower the frame number you can use, the quicker detection becomes. Frame 30 on a camera that is 30fps is at the tail end of 1 second. (I should have realized that sooner. lol) The IR lights flashing off is why I had to add the increase brightness function. When I changed the capture number to 3, it began working perfectly and very quickly every time. (i.e frame 3 is 1/10 of a second)

So I added one more option to the config, to set the frame number to capture.

add.py and compare.py

            .filter('select', 'gte(n,{})'.format(int(config.get("video", "image_number"))))

config

#
# When saving an image from the camera, which image number to use. 0 will almost always be black
# and when using an IR camera, it depends on how the lights are flashing as to which image number
# is black or darker. Experiment if the add function doesn't pick your face out quickly. 
#
image_number = 3

I'm wondering if during initialization, Howdy could just capture a group of 20 frames, and then determine which frame is the brightest? Ask if the user knows which frame they want to use, if not, do that test.

I'll run through my code and make sure it's lean and clean before submitting a pull request. I still want to run it for a few more days to make sure it's stable for me.

The OpenCV version circumvents this by detecting if a frame seems really dark and skipping it if it is, you could probably do the same for FFmpeg. I think a config value for this is very unfriendly to users, who not only need to understand the concept of dark frames but also randomly guess the right one for their camera.

Agreed, the video frames are coming in in such a way that they are identical to the openCV (numpy array) so they can be treated identically.

There's no way (to my knowledge) to read a single frame at a time through ffmpeg, so what I did was created a class for the functions needed. And on first read(), it runs an initialization which simply probes the device. On subsequent reads, it pulls in 10 frames into a buffer, that is then read as an array on each iteration of the while loop. If the while loop needs to continue past 10 frames, the read() function will then record() another set of 10 frames and continue on indefinitely. By only recording 10 frames, we limit the camera use time to roughly 1/3 of a second.

ffmpeg_reader class file

import ffmpeg
import numpy
from cv2 import CAP_PROP_FRAME_WIDTH
from cv2 import CAP_PROP_FRAME_HEIGHT

#
# This class was created to look as similar to the openCV features used
# in Howdy as possible for overall code cleanliness. 
#
class ffmpeg_reader:

    #
    # Initialization
    #
    def __init__(self, device_name, device_format, numframes):
        self.device_name = device_name
        self.device_format = device_format
        self.numframes = numframes
        self.video = ()
        self.num_frames_read = 0
        self.height = 0
        self.width = 0
        self.init_camera = True

    #
    # Setter method for height and width
    #
    def set(self, prop, setting):
        if prop == CAP_PROP_FRAME_WIDTH:
            self.width = setting
        elif prop == CAP_PROP_FRAME_HEIGHT:
            self.height = setting


    #
    # Getter method for height and width
    #
    def get(self, prop):
        if prop == CAP_PROP_FRAME_WIDTH:
            return self.width
        elif prop == CAP_PROP_FRAME_HEIGHT:
            return self.height

    #
    # Probe the video device to get and set height and width info
    #
    def probe(self):
        if self.get(CAP_PROP_FRAME_WIDTH) == 0 or self.get(CAP_PROP_FRAME_HEIGHT) == 0:
            probe = ffmpeg.probe(self.device_name)

        if self.get(CAP_PROP_FRAME_WIDTH) == 0:
            self.set(CAP_PROP_FRAME_WIDTH, probe['streams'][0]['width'])
        if self.get(CAP_PROP_FRAME_HEIGHT) == 0:
            self.set(CAP_PROP_FRAME_HEIGHT, probe['streams'][0]['height'])

    #
    # Record a video, saving it to self.video for processing later
    #
    def record(self, numframes):
        #
        # In case we forget to call probe, ensure we have set our width and height
        #
        self.probe()
        self.num_frames_read = 0

        stream, ret = (
            ffmpeg
            .input(self.device_name, format=self.device_format)
            .output('pipe:', format='rawvideo', pix_fmt='rgb24', vframes=numframes)
            .run(capture_stdout=True, quiet=True)
        )
        self.video = (
            numpy
            .frombuffer(stream, numpy.uint8)
            .reshape([-1, self.width, self.height, 3])
        )

    #
    # Read a frame from the video
    #
    def read(self):
        #
        # First time we are called, we want to initialize the camera by probing it and ensure self.video is empty.
        #
        if self.init_camera:
            self.init_camera = False
            self.probe
            self.video = ()
            return 0, self.video

        #
        # If we are called and self.video is empty, we should record self.numframes to fill the video buffer
        #
        if self.video == ():
            self.record(self.numframes)

        #
        # If we've read max frames, but still are being requested to read more, we simply record another batch.
        # Note, the video array is 0 based, so if numframes is 10, we must subtract 1 or run into an array index
        # error.
        #
        if self.num_frames_read >= (self.numframes - 1):
            self.record(self.numframes)

        #
        # Add one to the count. If we were at 0, that's fine as frame 0 is almost 100% going to be black
        # as the IR lights aren't fully active yet anyways. Saves us some trouble in the long run.
        #
        self.num_frames_read += 1
        return 0, self.video[self.num_frames_read]

and then the code to instantiate the new video_capture variable inside of add.py and compare.py is the only piece of the code that actually needs an IF statement. The rest of the original code can stay the same.

I placed the ffmpeg_reader.py in the same folder as the compare.py.

from ffmpeg_reader import ffmpeg_reader
...
...
...
if config.get("video", "recording_plugin") == "opencv":

    # Start video capture on the IR camera
    video_capture = cv2.VideoCapture(config.get("video", "device_path"))

    # Force MJPEG decoding if true
    if config.get("video", "force_mjpeg") == "true":
        # Set a magic number, will enable MJPEG but is badly documentated
        video_capture.set(cv2.CAP_PROP_FOURCC, 1196444237)

else:
    # Set the capture source for ffmpeg
    video_capture = ffmpeg_reader(config.get("video", "device_name"), config.get("video", "device_format"), 10)


# Set the frame width and height if requested
if int(config.get("video", "frame_width")) != -1:
    video_capture.set(cv2.CAP_PROP_FRAME_WIDTH, int(config.get("video", "frame_width")))

if int(config.get("video", "frame_height")) != -1:
    video_capture.set(cv2.CAP_PROP_FRAME_HEIGHT, int(config.get("video", "frame_height")))

# 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
# Note: For FFMPEG this just runs a probe to ensure height and width are set properly. 
video_capture.read()

...
...
    # Grab a single frame of video
    # Don't remove ret, it doesn't work without it
    ret, frame = (video_capture.read())

Looks good, but cv2.CAP_PROP_FRAME_WIDTH will not exist without a OpenCV import. Besides that, i think OpenCV should be the default and FFmpeg explicitly set. (reverse the if else with config.get("video", "recording_plugin") == "ffmpeg")

Could you also take a look at the documentation style in your class? Take a look at the style of the other files in the repo and maybe look into docstrings.

No problem! I reversed the if/else as requested, and I added docstrings into ffmpeg_reader class.

Help on module ffmpeg_reader:

NAME
    ffmpeg_reader

CLASSES
    builtins.object
        ffmpeg_reader

    class ffmpeg_reader(builtins.object)
     |  This class was created to look as similar to the openCV features used in Howdy as possible for overall code cleanliness.
     |  
     |  Methods defined here:
     |  
     |  __init__(self, device_name, device_format, numframes)
     |      Initialize self.  See help(type(self)) for accurate signature.
     |  
     |  get(self, prop)
     |      Getter method for height and width
     |  
     |  probe(self)
     |      Probe the video device to get height and width info
     |  
     |  read(self)
     |      Read a sigle frame from the self.video array. Will record a video if array is empty.
     |  
     |  record(self, numframes)
     |      Record a video, saving it to self.video array for processing later
     |  
     |  set(self, prop, setting)
     |      Setter method for height and width
     |  
     |  ----------------------------------------------------------------------

Changes are committed in my forked repo here.

timwelch/howdy

Great! You can open a pull request against the dev branch on this repo.

So, I'm not 100% happy with the code. Specifically, the PROBE. It takes 800ms to probe. We must probe to grab the resolution (in my case 352x352) of the IR camera before running the numpy resize to make the video compatible with the facial recog code.

Would it be easy to do a probe on initial configure and set height/width in the config file? My code will ignore the probe if those values are set.

Here's a run with probe...

Time spent
  Starting up: 118ms
  Opening the camera: 1836ms
  Importing face_recognition: 958ms
  Searching for known face: 94ms

Resolution
  Native: 352x352
  Used: 320x320

Frames searched: 4 (42.72 fps)
Dark frames ignored: 3 
Certainty of winning frame: 3.369
Winning model: 0 ("Initial model")
Identified face as tim

And here's a run without probe (hard coded 352x352). Note the time on Opening the camera, that's where probe is called.

Time spent
  Starting up: 115ms
  Opening the camera: 1071ms
  Importing face_recognition: 954ms
  Searching for known face: 92ms

Resolution
  Native: 352x352
  Used: 320x320

Frames searched: 1 (10.91 fps)
Dark frames ignored: 0 
Certainty of winning frame: 3.460
Winning model: 0 ("Initial model")
Identified face as tim

Ok, I figured it out... The call to ffmpeg returns the output of the actual command line command in the value of ret as a byte object.

This looks like ...

ffmpeg version 3.4.4-0ubuntu0.18.04.1 Copyright (c) 2000-2018 the FFmpeg developers
  built with gcc 7 (Ubuntu 7.3.0-16ubuntu3)
  configuration: --prefix=/usr --extra-version=0ubuntu0.18.04.1 --toolchain=hardened --libdir=/usr/lib/x86_64-linux-gnu --incdir=/usr/include/x86_64-linux-gnu --enable-gpl --disable-stripping --enable-avresample --enable-avisynth --enable-gnutls --enable-ladspa --enable-libass --enable-libbluray --enable-libbs2b --enable-libcaca --enable-libcdio --enable-libflite --enable-libfontconfig --enable-libfreetype --enable-libfribidi --enable-libgme --enable-libgsm --enable-libmp3lame --enable-libmysofa --enable-libopenjpeg --enable-libopenmpt --enable-libopus --enable-libpulse --enable-librubberband --enable-librsvg --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libssh --enable-libtheora --enable-libtwolame --enable-libvorbis --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx265 --enable-libxml2 --enable-libxvid --enable-libzmq --enable-libzvbi --enable-omx --enable-openal --enable-opengl --enable-sdl2 --enable-libdc1394 --enable-libdrm --enable-libiec61883 --enable-chromaprint --enable-frei0r --enable-libopencv --enable-libx264 --enable-shared
  libavutil      55. 78.100 / 55. 78.100
  libavcodec     57.107.100 / 57.107.100
  libavformat    57. 83.100 / 57. 83.100
  libavdevice    57. 10.100 / 57. 10.100
  libavfilter     6.107.100 /  6.107.100
  libavresample   3.  7.  0 /  3.  7.  0
  libswscale      4.  8.100 /  4.  8.100
  libswresample   2.  9.100 /  2.  9.100
  libpostproc    54.  7.100 / 54.  7.100
Input #0, video4linux2,v4l2, from '/dev/video2':
  Duration: N/A, start: 62106.710514, bitrate: 29736 kb/s
    Stream #0:0: Video: rawvideo (Y800 / 0x30303859), gray, 352x352, 29736 kb/s, 30 fps, 30 tbr, 1000k tbn, 1000k tbc
Stream mapping:
  Stream #0:0 -> #0:0 (rawvideo (native) -> rawvideo (native))
Press [q] to stop, [?] for help
Output #0, rawvideo, to 'pipe:':
  Metadata:
    encoder         : Lavf57.83.100
    Stream #0:0: Video: rawvideo (RGB[24] / 0x18424752), rgb24, 352x352, q=2-31, 89210 kb/s, 30 fps, 30 tbn, 30 tbc
    Metadata:
      encoder         : Lavc57.107.100 rawvideo
frame=   10 fps=0.0 q=-0.0 Lsize=    3630kB time=00:00:00.33 bitrate=89211.0kbits/s dup=5 drop=0 speed=2.45x    
video:3630kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 0.000000%

And in testing, we can easily pull out the values for height and width

>>> tim = str(ret.decode("utf-8"))
>>> for line in tim.splitlines():
...    if ", rgb24, " in line:
...       (height, width) = [x.strip() for x in [y.strip() for y in line.split(',')][2].split('x')]
...       print(height)
...       print(width)
... 
352
352

So we can parse from there. I used the output to pipe line that included ", rgb24, " in the line. It only occurs once, and we know it will be there, since the call to record forces the output to rgb24.

So all we have to do is move the probe after the record call and the parse, and before the numpy in case the parse fails. This will ensure the probe runs as a backup, but since it's expensive (800ms) we only run it if we absolutely need to.

    def record(self, numframes):
        """ Record a video, saving it to self.video array for processing later """


        # Ensure num_frames_read is reset to 0
        self.num_frames_read = 0

        stream, ret = (
            ffmpeg
            .input(self.device_name, format=self.device_format)
            .output('pipe:', format='rawvideo', pix_fmt='rgb24', vframes=numframes)
            .run(capture_stdout=True, quiet=True)
        )

        # We can detect the video resolution from the ret output of the video. But it takes a little parsing.
        # We are looking for this line:  
        #       Stream #0:0: Video: rawvideo (RGB[24] / 0x18424752), rgb24, 352x352, q=2-31, 89210 kb/s, 30 fps, 30 tbn, 30 tbc
        parse_resolution = str(ret.decode("utf-8"))
        for line in parse_resolution.splitlines():
            if ", rgb24, " in line:
                (height, width) = [x.strip() for x in [y.strip() for y in line.split(',')][2].split('x')]
                if height.isdigit() and width.isdigit():
                    self.set(CAP_PROP_FRAME_HEIGHT, int(height))
                    self.set(CAP_PROP_FRAME_WIDTH, int(width))

        # Eensure we have set our width and height before we attempt to call numpy so the call to numpy does not fail
        if self.get(CAP_PROP_FRAME_WIDTH) == 0 or self.get(CAP_PROP_FRAME_HEIGHT) == 0:
            self.probe()

        self.video = (
            numpy
            .frombuffer(stream, numpy.uint8)
            .reshape([-1, self.width, self.height, 3])
        )

This decreases the "Opening the camera" step by the ~800ms ...

Time spent
  Starting up: 114ms
  Opening the camera: 1073ms
  Importing face_recognition: 951ms
  Searching for known face: 97ms

Resolution
  Native: 352x352
  Used: 320x320

Frames searched: 1 (10.36 fps)
Dark frames ignored: 0 
Certainty of winning frame: 2.765
Winning model: 0 ("Initial model")
Identified face as tim

I'm a bit worried about fetching the resolution directly from the FFmpeg output, as this is not meant as input and could change in later versions. I'd propose using v4l2-ctrl if possible or another machine usable tool.

That does make sense. I agree with you. Looking at v4l2-ctrl, it would only work on cams that support it, correct? In my case, that works fine, but there could be a case where a webcam uses vfwcap driver instead. I don't know if the v4l2-ctrl command would still work in that scenario?

Should be easy to get a python function written to pull in the info, it will need to be parsed.

v4l2-ctl -d /dev/video2 --list-formats-ext | grep Size| awk '{print $3}'

352x352

Here's what I have so far in ffmpeg_reader class for probe:

    def probe(self):
        """ Probe the video device to get height and width info """

        args = ["v4l2-ctl", "-d", self.device_name, "--list-formats-ext"]
        process = Popen(args, stdout=PIPE, stderr=PIPE)
        out, err = process.communicate()

        if err:
            print("Error opening subprocess for v4l2-ctl.\n\n%s" % err)
            sys.exit(1)

        regex = re.compile('\s\d+x\d+')

        probe = regex.findall(str(out.decode("utf-8")))
        if len(probe) < 1: 
            # Could not determine the resolution from v4l2-ctl call. Reverting to ffmpeg.probe()
            probe = ffmpeg.probe(self.device_name)
            height = probe['streams'][0]['height']
            width = probe['streams'][0]['width']
        else:
            (height, width) = [x.strip() for x in probe[0].split('x')]


        if height.isdigit() and self.get(CAP_PROP_FRAME_HEIGHT) == 0:
            self.set(CAP_PROP_FRAME_HEIGHT, int(height))

        if width.isdigit() and self.get(CAP_PROP_FRAME_WIDTH) == 0:
            self.set(CAP_PROP_FRAME_WIDTH, int(width))

This will attempt to use v4l2-ctl, and revert back to using ffmpeg.probe() if the command fails.

On my laptop, this brings the probe function down to between 1100-1200ms, from 1800ms.

  Opening the camera: 1145ms

Note, there's also this command in ffmpeg itself that I found would return the resolution as well... but that command takes an additional 50-60ms. So, it would be less dependencies at the cost of 50-60ms.

 ffmpeg -f v4l2 -list_formats all -i /dev/video2

I switched to using ffmpeg, so as not to increase dependencies. It does add a little more time, since the ffmpeg process itself is beefy, but 100-150ms is still much better than 800ms total for the ffmpeg.probe() to run.

  Opening the camera: 1164ms

no need for ffmpeg for the spectre ?

Just adding
if len(frame.shape) == 2: frame = cv2.cvtColor(frame,cv2.COLOR_GRAY2BGR)
after each ret, frame = video_capture.read() worked here ( from https://github.com/boltgolt/howdy/issues/70#issuecomment-439123621 )

Maybe I missed something ?

What model are you using?

On my model (13t-something), frame.shape was empty. OpenCV is not currently capable of handling the IR camera. If I use the normal camera, it works fine. But that's not desirable.

I have since found a better way than using FFMPEG, it has been merged in. See the link below.

https://github.com/boltgolt/howdy/pull/116

I have an ap0xxxx ( 2018 Gem cut ) 13" i7.

Hardware is UVC 1.50 device HP Wide Vision FHD Camera (0408:5251).

Creates 4 /dev/video. the one to use for the IR is /dev/video2.

Release 2.5.0 includes both alternative recorders and grayscale support, so this issue should be fixed.

@boltgolt I just patched the AUR build config and compiled 2.5.0 for Manjaro on my 2018 13" Spectre x360 and can confirm it works like a charm!

I was struggling with the current (outdated, and flagged as such) AUR build of 2.3.1; sudo howdy test would launch the IR camera and my face would be circled in black but no recognition stats would be shown (which it would if I configured it to use the colour camera) but compiling this new release and doing the permissions patch on the models directory along with adding auth sufficient pam_python.so /lib/security/howdy/pam.py into the top line of /etc/pam.d/sudo and /etc/pam.d/gdm-password has got everything working exactly as desired.

Huge thanks to you and the other contributors for this excellent work! 馃憦馃憦馃憦

Is there anything that needs to be edited to get it to work on an x360? I have a 13t-ae000 and just following the instructions in ReadMe, I still get the original error plus more?

VIDEOIO ERROR: V4L2: Pixel format of incoming image is unsupported by OpenCV

Please look straight into the camera
Traceback (most recent call last):
  File "/usr/local/bin/howdy", line 89, in <module>
    import cli.add
  File "/lib/security/howdy/cli/add.py", line 147, in <module>
    gsframe = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
cv2.error: OpenCV(3.4.4) /io/opencv/modules/imgproc/src/color.cpp:181: error: (-215:Assertion failed) !_src.empty() in function 'cvtColor'

@mjlabe which camera is selected? What happens when you run sudo howdy test? And which distro are you using? Are you running a distribution packaged version or did you build it yourself?

It might be you need to run sudo howdy config and point howdy at a different numbered device in /dev/videoX; on Arch the camera that's auto detected is /dev/video0 - this needs to be changed to /dev/video2 to use the IR camera.

I'm not sure which camera, how can I tell?
sudo howdy test results in:

```VIDEOIO ERROR: V4L2: Pixel format of incoming image is unsupported by OpenCV

Opening a window with a test feed

Press ctrl+C in this terminal to quit
Click on the image to enable or disable slow mode

No protocol specified
: cannot connect to X server :0

Distro: PopOS 18.10
Running a distribution packaged version (sudo apt install howdy)

Let's try all the cameras (sudo howdy test) in ```/dev/v4l/by-path```:

pci-0000:00:14.0-usb-0:5:1.2-video-index0 (Default):

VIDEOIO ERROR: V4L2: Pixel format of incoming image is unsupported by OpenCV

Opening a window with a test feed

Press ctrl+C in this terminal to quit
Click on the image to enable or disable slow mode

No protocol specified
: cannot connect to X server :0

pci-0000:00:14.0-usb-0:5:1.2-video-index1:

VIDEOIO ERROR: V4L2: Could not obtain specifics of capture window.
/dev/v4l/by-path/pci-0000:00:14.0-usb-0:5:1.2-video-index1 does not support memory mapping

Opening a window with a test feed

Press ctrl+C in this terminal to quit
Click on the image to enable or disable slow mode

No protocol specified
: cannot connect to X server :0

pci-0000:00:14.0-usb-0:5:1.0-video-index0:

Opening a window with a test feed

Press ctrl+C in this terminal to quit
Click on the image to enable or disable slow mode

No protocol specified
: cannot connect to X server :0

pci-0000:00:14.0-usb-0:5:1.0-video-index1:

VIDEOIO ERROR: V4L2: Could not obtain specifics of capture window.
/dev/v4l/by-path/pci-0000:00:14.0-usb-0:5:1.0-video-index1 does not support memory mapping

Opening a window with a test feed

Press ctrl+C in this terminal to quit
Click on the image to enable or disable slow mode

No protocol specified
: cannot connect to X server :0
```

/dev/v4l/by-id/usb-SunplusIT_Inc_HP_Wide_Vision_FHD_Camera_01.00.00-video-index0:

VIDEOIO ERROR: V4L2: Pixel format of incoming image is unsupported by OpenCV

Opening a window with a test feed

Press ctrl+C in this terminal to quit
Click on the image to enable or disable slow mode

No protocol specified
: cannot connect to X server :0

/dev/v4l/by-id/usb-SunplusIT_Inc_HP_Wide_Vision_FHD_Camera_01.00.00-video-index1:

VIDEOIO ERROR: V4L2: Could not obtain specifics of capture window.
/dev/v4l/by-id/usb-SunplusIT_Inc_HP_Wide_Vision_FHD_Camera_01.00.00-video-index1 does not support memory mapping

Opening a window with a test feed

Press ctrl+C in this terminal to quit
Click on the image to enable or disable slow mode

No protocol specified
: cannot connect to X server :0

/dev/video[1,2,3] all give me the same error:

VIDEOIO ERROR: V4L2: Pixel format of incoming image is unsupported by OpenCV

Opening a window with a test feed

Press ctrl+C in this terminal to quit
Click on the image to enable or disable slow mode

No protocol specified
: cannot connect to X server :0

You might want to switch to v4l2 as recorder, as it seems to handle some grayscale problems better than the default opencv recorder

I have exactly the same errors and even the same identifiers for the cameras on my 13t-ae0000 (the rosegold 2018 model) running Ubuntu 18.10. I installed howdy through the ppa.

"/dev/video0" and "pci-0000:00:14.0-usb-0:5:1.0-video-index0:" don't output any errors when running sudo howdy test but they do not open any additional windows as they should. I can use them to add a face model, but howdy is clearly using the normal camera and not the IR cameras.

Super bummed because I was excited about finally being able to log in with face recognition... :disappointed:

I also have the same issue on my HP Spectre x360 15t-ch000, running Ubuntu 18.10 as well. I get all the same errors as molguin92, as well as mjlabe.

What does dmesg | grep "Unknown video format" output for you both?

[ 4.203881] uvcvideo: Unknown video format 00000032-0002-0010-8000-00aa00389b71

That's the Microsoft IR 8 bit format, which means you need to upgrade your kernel to at least 4.19. Check out this comment.

Thanks. I guess I might as well wait for 19.04 at this point! Hopefully that fixes it.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

gabrielspadon picture gabrielspadon  路  6Comments

NICHOLAS85 picture NICHOLAS85  路  7Comments

boltgolt picture boltgolt  路  9Comments

joshlangley picture joshlangley  路  5Comments

nhusby picture nhusby  路  4Comments