Skip to content

Instantly share code, notes, and snippets.

@victoriastuart
Last active February 27, 2020 21:38
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save victoriastuart/8092a3dd7e97ab57ede7614251bf5cbd to your computer and use it in GitHub Desktop.
Save victoriastuart/8092a3dd7e97ab57ede7614251bf5cbd to your computer and use it in GitHub Desktop.
# coding: utf-8
# FILE:
# -----
# /mnt/Vancouver/apps/Webcam-Face-Detect/webcam_cv3_v2_fps_v3.py
# FUNCTIONALITY:
# --------------
# * outputs webcam to screen and finds faces, eyes
# * writes webcam video to *.avi file (MJPG codec) via OpenCV/FourCC
# * preceding version (webcam_cv3_v2_fps_v2c.py) also split video into 4 screens
# (default + R, G, B channels); removed in this version (just the default output)
# * "venv" (geany snippet) code addition (at top of script) forces use of py35 venv
# VERSIONS:
# ---------
# webcam_cv3.py ## original (unmodified) file; reads Haar cascade filter as argument
# webcam_cv3_v1.py ## working copy of webcam_cv3.py
# webcam_cv3_v2.py ## added Adrian Rosebrock's "imutils" package to get FPS
# webcam_cv3_v2_fps_v1.py ## further modified per imutils for multithreaded OpenCV implementation
# webcam_cv3_v2_fps_v2.py ## added eye detection Haar cascade filter; added argparse() statements
# webcam_cv3_v2_fps_v2a.py ## copy? of webcam_cv3_v2_fps_v2.py
# webcam_cv3_v2_fps_v2b.py ## made more Pythonic -- added methods { cascade_detect() | main() };
## added ability to run +/- passing of command-line -n (# frames) argument,
## " while args["num_frames"] == -1 or fps._numFrames < args["num_frames"]: "
# webcam_cv3_v2_fps_v2c.py ## added ability to write the webcam video stream to a video file using OpenCV, per
## http://www.pyimagesearch.com/2016/02/22/writing-to-video-with-opencv/
## see also: http://docs.opencv.org/3.0-beta/doc/py_tutorials/py_gui/py_video_display/py_video_display.html
## splits video (also) into R, G, B channels -- all with face/eye detection
# webcam_cv3_v2_fps_v3.py ## removed the split (RGB) channels code; now just writes the video to a file; removed extraneous comments, etc.
# MINUTIAE:
# ---------
# Logitech C270 webcam default shape is: (480,640,3)
# USAGE -- any of:
# ----------------
# python 2>/dev/null webcam_cv3_v2_fps_v3.py -o video_capture.avi ## webcam runs until quit [ q ]
# python 2>/dev/null webcam_cv3_v2_fps_v3.py -n100 -o video_capture.avi ## webcam runs for 100 frames
# python 2>/dev/null webcam_cv3_v2_fps_v3.py -n 100 -o video_capture.avi ## webcam runs for 100 frames
## (indifferent to space after -n)
# "2>/dev/null" dumps/hides warnings :-)
# ============================================================================
from __future__ import print_function
# ----------------------------------------------------------------------------
# REQUIRE SCRIPT TO RUN IN PYTHON 3.5 VENV (HAS REQUIRED INSTALLED OPENCV, IMUTILS, ... PACKAGES):
import os
# FIRST, SEE IF WE ARE IN A CONDA VENV { PY27: PYTHON 2.7 | PY35: PYTHON 3.5 | TF: TENSORFLOW | THEE : THEANO }
try:
os.environ["CONDA_DEFAULT_ENV"]
except KeyError:
print("\n\tPlease set the py35 { p3 | Python 3.5 } environment!\n")
exit()
# IF WE ARE IN A CONDA VENV, REQUIRE THE P3 VENV:
if os.environ['CONDA_DEFAULT_ENV'] != "py35":
print("\n\tPlease set the py35 { p3 | Python 3.5 } environment!\n")
exit()
# more here: /home/victoria/GeanyProjects/Victoria/reference/Python%20Notes.html#Environments
# ----------------------------------------------------------------------------
import imutils ## need to include this for 'threaded' webcam
from imutils.video import WebcamVideoStream
from imutils.video import FPS
import argparse
import cv2
import sys
# ----------------------------------------
# for writing webcam stream to video file using OpenCV:
import numpy as np
# ----------------------------------------------------------------------------
# CONSTRUCT THE ARGUMENT PARSE AND PARSE THE ARGUMENTS
ap = argparse.ArgumentParser()
#ap.add_argument("-n", "--num_frames", type=int, default=100,
ap.add_argument("-n", "--num_frames", type=int, default=-1,
help="# of frames to loop over for FPS test")
ap.add_argument("-d", "--display", type=int, default=-1,
help="Whether or not frames should be displayed")
# ----------------------------------------
# READ HAAR CASCADE FILTER FILES:
ap.add_argument("-f1", "--file1", type=str, default="haarcascade_frontalface_default.xml",
help="Specify the Haar FrontalFace cascade filter (e.g.: haarcascade_frontalface_default.xml)")
ap.add_argument("-f2", "--file2", type=str, default="haarcascade_eye.xml",
help="Specify the Haar Eye cascade filter (e.g.: haarcascade_eye.xml)")
# ----------------------------------------
# for writing to video file:
# (details here: http://www.pyimagesearch.com/2016/02/22/writing-to-video-with-opencv/)
ap.add_argument("-o", "--output", required=True,
help="path to output video file")
ap.add_argument("-f", "--fps", type=int, default=20,
help="FPS of output video")
ap.add_argument("-c", "--codec", type=str, default="MJPG", ## use with -o to .*avi file; "MJPG" must be UPPERCASE
help="codec of output video")
"""FourCC (four character code): popular examples include MJPG | DIVX | H264
Victoria: tried, above, these FourCC codecs | extensions:
ONLY this combination Works:
MJPG *avi ## use with -o to .*avi file; "MJPG" must be UPPERCASE
Does NOT work:
MJPG *.mpg | *.mp4
MPG4 *.mp4 | *.avi | *.mpg
MP4V *.mp4 | *.mp4v | *.avi
DIVX *.divx | *.avi
H264 *.mkv | *.avi | *.mp4
H265 *.mkv
FLV1 *.flv | *.avi
Discussed here: http://www.pyimagesearch.com/2016/02/22/writing-to-video-with-opencv/
"""
# ----------------------------------------
args = vars(ap.parse_args())
# ----------------------------------------------------------------------------
print("\n\tcurrent env:", os.environ["CONDA_DEFAULT_ENV"])
#print("\tnum_frames:", args["num_frames"])
print("\tPress 'q' to exit\n")
faceCascade = cv2.CascadeClassifier(args["file1"])
eye_cascade = cv2.CascadeClassifier(args["file2"])
# ----------------------------------------------------------------------------
# DETECT OBJECTS { FACES | EYES } IN WEBCAM VIDEO CAPTURE:
def cascade_detect():
# ----------------------------------------------------------------------------
# INITIALIZE THE VIDEO STREAM AND ALLOW THE CAMERA SENSOR TO WARMUP:
# vs: video stream
vs = WebcamVideoStream(src=0).start()
# ----------------------------------------------------------------------------
# INITIALIZE FOURCC, VIDEO WRITER, DIMENSIONS OF THE FRAME, AND ZEROS ARRAY:
fourcc = cv2.VideoWriter_fourcc(*args["codec"])
writer = None
(h, w) = (None, None)
zeros = None
fps = FPS().start()
# ----------------------------------------------------------------------------
# LOOP OVER FRAMES FROM THE VIDEO STREAM:
#while True:
while args["num_frames"] == -1 or fps._numFrames < args["num_frames"]:
"""throws errors each frame capture (due to OpenCV libjpeg package bug) sim. to:
Corrupt JPEG data: 1 extraneous bytes before marker 0xd3
Can hide those warnings by directing them to 2>/dev/null
"""
# CAPTURE FRAME-BY-FRAME:
frame = vs.read()
# RESIZE THE FRAME:
# grab the frame from the video stream and resize it to have a maximum width of 300 pixels:
#frame = imutils.resize(frame, width=300)
##
## not resized: 15.48 | 14.80 | 22.92 | 23.95 FPS : Slower; dependent e.g. on ambient lighting: last two times
## c. desk lamp off (so, eye detection failed, but faster);
## eye detection good, with desk lamp on, but slower FPS.
## resized (W = 300): 35.52 | 31.18 | 41.42 | 38.78 FPS : Faster, but harder to detect eyes as smaller frames i.e. faces!]
# ----------------------------------------------------------------------------
# WEBCAM - DISPLAY:
# ============================================================================
global face_num, eye_num
gray_image = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
faces = faceCascade.detectMultiScale(
gray_image,
scaleFactor=1.1,
minNeighbors=5,
minSize=(30, 30),
#flags = cv2.CASCADE_SCALE_IMAGE
)
face_num = len(faces)
eye_num = 0
# DRAW A BLUE RECTANGLE AROUND THE FACES:
for (x,y,w,h) in faces:
cv2.rectangle(frame, (x,y), (x+w,y+h), (255,0,0), 2) ## (255,255,0): line color (blue);
## 2: line width (thickness)
roi_gray = gray_image[y:y+h, x:x+w]
roi_color = frame[y:y+h, x:x+w]
# DETECT THE EYES (LOOKS INSIDE THE FACES ROI):
eyes = eye_cascade.detectMultiScale(roi_gray)
# COUNT THE NUMBER OF EYES:
eye_num += len(eyes)
# DRAW A CYAN-COLORED RECTANGLE AROUND THE EYES:
for (ex,ey,ew,eh) in eyes:
cv2.rectangle(roi_color,(ex,ey),(ex+ew,ey+eh),(0,255,0),2) ## (0,255,0): line color; 2: line width/thickness
print("\r\tprocessing (live): found {0} faces and {1} eyes in this frame".format(face_num, eye_num), end='')
# ============================================================================
# ============================================================================
# WEBCAM - WRITER:
# CHECK IF THE WRITER IS None:
if writer is None: ## if it is, we need to initialize it [first, grab the spatial dimensions
## (i.e., width and height) of the frame, then instantiate the cv2.VideoWriter() ]
# store the image dimensions, initialize the video writer, and construct the zeros array:
(h, w) = frame.shape[:2]
"""You'll notice (below) that we are using a width and height that are double that of the original frame - why is that?
The reason is because our output video frame will have two rows and two columns - storing a total of four "images".
We thus need double spatial dimensions of the original frame.
"""
#
#writer = cv2.VideoWriter(args["output"], fourcc, args["fps"], (w * 2, h * 2), True) ## not splitting to R,G,B this script
writer = cv2.VideoWriter(args["output"], fourcc, args["fps"], (w, h), True)
zeros = np.zeros((h, w), dtype="uint8")
""" VICTORIA: I was getting variations (in the reported shape) of this error, at this line below (line ~258):
output[0:h, 0:w] = frame ## ValueError: could not broadcast input array from shape (480,640,3) into shape (161,161,3)
when running this script,
python webcam_cv3_v2_fps_v2c.py --output video_capture.avi
because the "writer" (above) was "None" at line ~227. The solution was to re-add this line (~231, above)
(h, w) = frame.shape[:2]
again, below (line ~252)!
"""
# WE ARE NOW READY TO CONSTRUCT OUR OUTPUT FRAME AND WRITE IT TO FILE:
# break the image into its RGB components, then construct the RGB representation of each frame individually:
(h, w) = frame.shape[:2] ## needed again, here: see my docstring, above!
#(B, G, R) = cv2.split(frame) ## not splitting to R,G,B this script
#R = cv2.merge([zeros, zeros, R]) ## not splitting to R,G,B this script
#G = cv2.merge([zeros, G, zeros]) ## not splitting to R,G,B this script
#B = cv2.merge([B, zeros, zeros]) ## not splitting to R,G,B this script
# construct the final output frame, storing the original frame at the top-left, the red channel in the top-right,
# the green channel in the bottom-right, and the blue channel in the bottom-left:
#
#output = np.zeros((h * 2, w * 2, 3), dtype="uint8") ## not splitting to R,G,B this script
output = np.zeros((h, w, 3), dtype="uint8")
#
output[0:h, 0:w] = frame ## ValueError: could not broadcast input array from shape (480,640,3) into shape (161,161,3)
#output[0:h, w:w * 2] = R ## not splitting to R,G,B this script
#output[h:h * 2, w:w * 2] = G ## not splitting to R,G,B this script
#output[h:h * 2, 0:w] = B ## not splitting to R,G,B this script
# write the output frame to file:
writer.write(output)
# ============================================================================
# ----------------------------------------
# DISPLAY THE OUTPUT FRAMES:
cv2.imshow("Frame", frame)
cv2.imshow("Output", output)
fps.update()
# ----------------------------------------------------------------------------
# WHEN DONE IMAGING (MANUAL QUIT/BREAK):
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# ----------------------------------------------------------------------------
# END OF WHILE LOOP
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# PERFORM A BIT OF CLEANUP:
# When everything is done, release the capture
#video_capture.release()
fps.stop()
cv2.destroyAllWindows()
vs.stop()
writer.release()
print("\n\n\tthreaded OpenCV implementation")
print('\tnum_frames: {:d}'.format(args["num_frames"]))
print("\twebcam -- approx. FPS: {:.2f}".format(fps.fps()))
print("\tFound {0} faces and {1} eyes!\n".format(face_num, eye_num))
# ----------------------------------------------------------------------------
# END OF cascade_detect() METHOD
# ----------------------------------------------------------------------------
def main(argv = None):
if argv is None:
argv = sys.argv
cascade_detect()
if __name__ == "__main__":
sys.exit(main())
# ============================================================================
<!DOCTYPE html>
<HTML xmlns="http://www.w3.org/1999/xhtml" lang="en-US">
<HEAD>
<meta charset="utf-8">
<TITLE>webcam_cv3_v2_fps_v3.py NOTES</TITLE>
<!-- CSS: -->
<!-- >https://stackoverflow.com/questions/3010810/forcing-images-to-not-wrap -->
<!-- http://w3schools.com/cssref/tryit.asp?filename=trycss_class-clear -->
<style type="text/css">
img{
float: none;
display: inline;
margin-right: 25px;
vertical-align: bottom;
}
</style>
<!-- <PRE></PRE> TAGSET: -->
<!-- http://stackoverflow.com/questions/8919776/html-justify-text-align-with-pre-tag -->
<!-- http://stackoverflow.com/questions/4233869/html-pre-tag-causes-linebreaks -->
<style type="text/css">
pre {
text-align: left;
display:inline;
padding-top: 0px;
padding-bottom: 0px;
}
</style>
</HEAD>
<BODY>
<UL>
<H2>WEBCAM-FACE-DETECT [OpenCV]</H2>
<UL>
<p>
<li> Victoria's "webcam_cv3_v2_fps_v3.py" notes
<p>
<li> Note: must be in my p3 (Python 3.5 | py35) venv, as I compiled (without JPEG) the GitHub OpenCV repo in that venv!
<p>
<li> For this work I cloned the <a href="https://github.com/shantnu/Webcam-Face-Detect/blob/master/webcam.py">Webcam-Face-Detect</a> GitHub repo, and modified the main Python 3 script (webcam_cv3.py), which I named&nbsp; <code><font size=2>webcam_cv3_v2.py</font></code>.
<ul>
<p>
<li> update: the modified file&nbsp; <code><font size=2><b>webcam_cv3_v2_fps</b></font></code></a>&nbsp; has an ~3x-faster FPS!
</ul>
<p>
... see my comments in that file (script) for additional detail, as well as the author's <a href="https://realpython.com/blog/python/face-detection-in-python-using-a-webcam/">blog post</a>.&nbsp; For example:
<ul>
<div><pre><font size=2>video_capture = cv2.VideoCapture(0)</font></pre></div>
<p>
"<i>... This line sets the video source to the default webcam, which OpenCV can easily capture. You can also provide a filename here, and Python will read in the video file. However, you need to have ffmpeg installed for that since OpenCV itself cannot decode compressed video. Ffmpeg acts as the front end for OpenCV, and, ideally, it should be compiled directly into OpenCV. ...</i>"
<p>
[ ... snip ... ]
</ul>
<p>
<li> Usage:
<p>
<div><pre><font size=2> python 2&gt;/dev/null webcam_cv3_v2.py haarcascade_frontalface_default.xml</font></pre></div>
<p>
<li> Notes:
<ol>
<p>
<li> Unlike YOLO with my Logitech C270 webcam (...&nbsp; <code><font size=2>yolo demo</font></code> ...;&nbsp;&nbsp; &le; ~0.2 fps ), Webcam-Face-Detect [<code><font size=2>webcam_cv3_v2.py</font></code>] is <i>much</i> faster, ~15 fps, thus enabling 'real-time' object/facial recognition!
<p>
<li> Unlike YOLO, which after compiling the OpenCV version just worked, I encountered <i>much</i> difficulty in getting this camera to work with Webcam-Face-Detect; namely, getting a version of OpenCV installed that would work with this camera; executive summary:
<p>
<div><pre> <font size=2>Note: did this in my Python 3.5 (py35) virtual environment:
conda remove opencv
cd /mnt/Vancouver/apps/opencv-git
git clone https://github.com/opencv/opencv
cd opencv
mkdir build
cd build
cmake -DWITH_JPEG=OFF -DBUILD_JPEG=OFF ..
make
sudo make install
sudo make clean</font></pre></div>
<p>
<li> Now I have a <i>working</i> installation of OpenCV in my py35 (Python 3.5) venv&nbsp; 😀 !&nbsp;&nbsp; Run the script, there.
<p>
<li> In addition to that issue, the webcam video quality initially was very poor using the USB extender cable, with which the webcam was connected; I needed to plug the webcam directly into a USB 2.0 or USB 3.0 ports on my computer (either is OK).
<p>
<li> As described in my README (item #2, above), when running the script in the terminal I would get continuous output of these warnings:
<p>
<div><pre><font size=2> (py35) [victoria@victoria Webcam-Face-Detect]$ python webcam_cv3_v2.py haarcascade_frontalface_default.xml
Corrupt JPEG data: 1 extraneous bytes before marker 0xd2
Corrupt JPEG data: 2 extraneous bytes before marker 0xd2
Corrupt JPEG data: 1 extraneous bytes before marker 0xd4
Corrupt JPEG data: 3 extraneous bytes before marker 0xd1
Corrupt JPEG data: 1 extraneous bytes before marker 0xd0
Corrupt JPEG data: 5 extraneous bytes before marker 0xd0
Corrupt JPEG data: 1 extraneous bytes before marker 0xd0
Corrupt JPEG data: 1 extraneous bytes before marker 0xd1
Corrupt JPEG data: 1 extraneous bytes before marker 0xd3
Corrupt JPEG data: 1 extraneous bytes before marker 0xd0
Corrupt JPEG data: 1 extraneous bytes before marker 0xd4
Corrupt JPEG data: 3 extraneous bytes before marker 0xd6
Corrupt JPEG data: 1 extraneous bytes before marker 0xd1
^CTraceback (most recent call last):
File "webcam_cv3_v2.py", line 52, in &lt;module&gt;
flags = cv2.CASCADE_SCALE_IMAGE
KeyboardInterrupt
(py35) [victoria@victoria Webcam-Face-Detect]$</font></pre></div>
<p>
... however, this was easily solved by directing those to /dev/null (2&gt;/dev/null : see my usage note, above). 😀
</ol>
<ul>
<p>
<li> Aside: I Googled that warning: either appears to be specific to the Logitech C270 webcam -- as that webcam comes up during that search -- and/or is due to libjpeg being compiled/added to OpenCV: a known bug.&nbsp; 😞
</ul>
<p>
<li> The same approach (code, ...) is used in this blog post, <a href="https://pythonprogramming.net/haar-cascade-face-eye-detection-python-opencv-tutorial/">Haar Cascade Object Detection Face & Eye OpenCV Python Tutorial</a>.
<p>
<li> Not surprisingly, I also noted a substantial effect of ambient lighting on webcam facial recognition, yesterday [2016-Oct-16].&nbsp; I had modified my&nbsp; <code><font size=2>webcam_cv3_v2_fps</font></code>&nbsp; script to also recognize eyes [<code><font size=2>webcam_cv3_v2_fps_v2.py</font></code>], but it didn't <i><b>appear</b></i> to be working, <i><b>until</b></i> I turned my desk lamp on (increased ambient lighting)!&nbsp; Oif!&nbsp; 😮
<p>
<li> <u>Demo</u>:
<p>
<div><pre><font size=2> (py35) [victoria@victoria Webcam-Face-Detect]$ <font color=brown><b>python 2>/dev/null webcam_cv3_v2_fps_v3.py -o video_capture.avi</b></font>
current env: py35
Press 'q' to exit
processing (live): found 2 faces and 0 eyes in this frame ^C
(py35) [victoria@victoria Webcam-Face-Detect]$
</font></pre></div>
<p>
<li> <u>Captured webcam video file</u>: (<a href="https://cloud.githubusercontent.com/assets/2575920/19709048/4cd27132-9ad8-11e6-8433-b5b59a202bd4.gif">animated GIF</a>)
</UL>
<br><br><br><br>
</UL>
</BODY>
</HTML>
@victoriastuart
Copy link
Author

byzanz capture 2016-10-25 17 26 36

@victoriastuart
Copy link
Author

victoriastuart commented Oct 26, 2016

I couldn't upload a video (even zipped) to this Gist, so that's a lossy Byzanz screen capture (animated GIF). :-/

@ShangxuanWu
Copy link

Great!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment