How to mark features which are present in both arrays in OpenCV? - python

I'm trying to write a Python 3.7 script to detect faces and features using the OpenCV Haar classifier files which stores images as n-dimensional Numpy arrays. Right now I'm only working with two features:
- The entire face
- The eyes
Both of these are obtained using two different classifiers.
The Code detects the presence of both these features in the image and then marks them with a rectangle using the cv2.rectangle() function inside a for loop for each feature i.e, one for detected faces and one for detected eyes.
I want the script to mark the eye-rectangles ONLY if those points were found in the face array as well as the eye array. numpy.intersect1d() only finds intersection within one-dimensional arrays.
I've even tried
for x,y,w,h in eyes and x,y,w,h in faces:
and all it does is return this error message:
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
Any help would be greatly appreciated.
This is being attempted on Windows 10 64-bit, the code is written in Pycharm 2019, OpenCV is imported as CV2.
# Creating the cascade classifier objects.
face_cascade =
cv2.CascadeClassifier("haarcascade_frontalface_default.xml")
eye_cascade = cv2.CascadeClassifier("haarcascade_eye.xml")
# Entering and displaying the original image.
img = cv2.imread("filename.jpg", 1)
cv2.imshow("Original Images",img)
cv2.waitKey(0)
cv2.destroyAllWindows()
# Convert the image to Black and White and store in a variable.
gry_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(gry_img, scaleFactor = 1.05, minNeighbors = 8)
eyes = eye_cascade.detectMultiScale(gry_img, scaleFactor = 1.30, minNeighbors = 12)
# Now, we mark the detected features with a rectangle on the original image.
for x,y,w,h in faces:
img = cv2.rectangle(img, (x,y), (x+w, y+h), (255, 21, 21), 3) # Blue.
for x,y,w,h in eyes:
img = cv2.rectangle(img, (x,y), (x+w, y+h), (255, 255, 24), 3) # Cyan.
cv2.imshow("Detected Faces and Eyes",img)
cv2.waitKey(0)
cv2.destroyAllWindows()
I need the eye-features to be marked only if they were ALSO found in the face-features array.

A better approach to solve this problem is already suggested in the OpenCV docs. It suggests that instead of passing the whole image for detecting the eyes, you should first detect the face, then crop the image, and use this cropped image for eyes detection. For cropping the face image you can use numpy slicing as:
for x,y,w,h in faces:
face_img = gry_img[y:y+h, x:x+w]
# Now detect the eyes in this `face_img` only.
eyes = eye_cascade.detectMultiScale(face_img, scaleFactor = 1.30, minNeighbors = 12)

Oh boy, do I feel stupid now. Well here goes:
As ZdaR said, the OpenCV documentation does indeed give a perfect example on how to achieve the result I required, the gist of which is:
for x,y,w,h in faces:
face_img = gry_img[y:y+h, x:x+w]
# Now detect the eyes in this `face_img` only.
eyes = eye_cascade.detectMultiScale(face_img, scaleFactor = 1.30, minNeighbors = 12)
What I needed to do other than this, apparently was to crop the original image to the face coordinates as well for the eye-features to be drawn upon. I'm still not sure if this should work, but for now it seems that adding that extra line does work.
Code that finally worked:
faces = face_cascade.detectMultiScale(gry_img, scaleFactor = 1.05, minNeighbors = 10)
for (x, y, w, h) in faces:
cv2.rectangle(img, (x, y),(x + w, y + h), (255, 0, 0), 3)
gry_eye = gry_img[y:y+h, x:x+w]
eye_img = img[y:y + h, x:x + w]
eyes = eye_cascade.detectMultiScale(gry_eye, scaleFactor = 1.05, minNeighbors = 5)
for (x, y, w, h) in eyes:
cv2.rectangle(eye_img, (x, y), (x + w, y + h), (255, 255, 24), 3)
Well, this has been a learning experience.
Thanks to ZdaR for the help!

Related

How can I increase the size (or zoom) the ROI selected by a Haar Cascade by x% in OpenCV-python?

I'm using a simple haar-cascade algorithm on OpenCV to detect faces in an image and draw bounding boxes around them. I intend to crop these faces and store them elsewhere to feed them to a CNN. However, I notice that most of these boxes fit very tightly to the face, and many times important facial features like the forehead and the chin get cropped out if the subject has a 'long face' (pun not intended).
import cv2
img = cv2.imread('image path here')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
faces = face_cascade.detectMultiScale(gray, 1.2, 4)
store_faces = []
store_coordinates = []
for (x, y, w, h) in faces:
cv2.rectangle(img, (x, y), (x+w, y+h), (0, 0, 255), 2)
cropped_image = img[y:y + h, x:x + w]
temp_list = [x, y, w, h]
store_faces.append(cropped_image)
store_coordinates.append(temp_list)
cv2.imshow('img', img)
cv2.waitKey()
A quick workaround would be to just zoom or expand the ROI keeping the center of the selection box fixed by, let's say 20%. This would, on average, help capture the entire face and preserve all facial features on the dataset I have. How do I go about doing this?
Here is an image for the expected output
The haar-cascade algorithm returns 4 values for each face (x, y, w, h), where (x, y) corresponds to either the top left to the bottom left vertex of the ROI (I'm not sure) and w and h stand for the width and height along the respective axes from that point. I tried fiddling with geometry to find the points for a zoomed/enlarged ROI, but the bounding boxes went haywire :(
Here's my (incorrect) formula for a 50% expansion:
"""
Enlarging bounding-box by a factor of 1.5.
Formula: (x, y, w, h) ——> (x-(w/4), y-(h/4), (3*w)/2, (3*h)/2)
"""

How to customize parameters for face detection by using face cascade

I have a simple face detection implementation as following
import cv2
face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + "haarcascade_frontalface_default.xml")
filename = "path/to/image"
img = cv2.imread(filename)
cv2.imshow("Original image", img)
face_region = face_cascade.detectMultiScale(img, 1.1, 4)
for (x, y, w, h) in face_region:
cv2.rectangle(img, (x, y), (x+w, y+h), (255, 0, 0), 2)
cv2.imshow("Output", img)
cv2.waitKey(0)
after running the code, I got the following result
As you can see that, the implementation detects two faces! How can I get rid of this kind of false detection?
first delete the textual data like in this link Delete OCR word from Image (OpenCV,Python)
after that try to use you face detection code...then it will improve your accuracy

How to get each value of x,y,w,h in this crop image?

i want to ask something. In opencv there is cv2.rectangle to build a rectangle of object interest. After I got the object interest which is represent with rectangle, I want to get the region of interest box. To do that I used crop = frame[y:y+h, x:x+w].
Now I want to get the value of x,y,w,h of that cropped box. How to do that?
Here is my code
while bol:
capture = cv2.VideoCapture(0)
ret, frame = capture.read()
#load object detector
path = os.getcwd()+'\haarcascade_frontalface_default.xml'
face_cascade = cv2.CascadeClassifier(path)
#convert image to grayscale
imgGray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
for (x, y, w, h) in face_cascade.CascadeClassifier(imgGray):
cv2.rectangle(frame, (x,y), (x+w, y+h), (255,255,255), 3)
_croppedImage = frame[y:y+h, x:x+w]
ROI = _croppedImage
how to get value of x,y,w,h of _croppedImage ?
The value of x & y for _croppedImage is of no importance and significance. This is because the _croppedImage is simply an image cropped out of another image.
To get the w & h for _croppedImage, you can use .shape() method. w & h of an image is simply the width and height of the image respectively.
h, w = _croppedImage[:2]

How to use openCV and HAAR Cascades to blur faces?

I would like to know is there is a way to blur the faces that have been automatically identify by the haarcascade face classifier.
using the code below, I'm able to detect the faces, crop the image around this face or draw a rectangle on it.
image = cv2.imread(imagepath)
# Specify the trained cascade classifier
face_cascade_name = "./haarcascade_frontalface_alt.xml"
# Create a cascade classifier
face_cascade = cv2.CascadeClassifier()
# Load the specified classifier
face_cascade.load(face_cascade_name)
#Preprocess the image
grayimg = cv2.cvtColor(image, cv2.cv.CV_BGR2GRAY)
grayimg = cv2.equalizeHist(grayimg)
#Run the classifiers
faces = face_cascade.detectMultiScale(grayimg, 1.1, 2, 0|cv2.cv.CV_HAAR_SCALE_IMAGE, (30, 30))
print "Faces detected"
if len(faces) != 0: # If there are faces in the images
for f in faces: # For each face in the image
# Get the origin co-ordinates and the length and width till where the face extends
x, y, w, h = [ v for v in f ]
# Draw rectangles around all the faces
cv2.rectangle(image, (x,y), (x+w,y+h), (255,255,255))
sub_face = image[y:y+h, x:x+w]
for i in xrange(1,31,2):
cv2.blur(sub_face, (i,i))
face_file_name = "./face_" + str(y) + ".jpg"
cv2.imwrite(face_file_name, sub_face)
But I would like to blur the face of the people so they can't be recognized.
Do you have an idea on how to do that?
Thanks for your help
Arnaud
I finally succeeded to do what I want.
To do that apply a gaussianblur as Hammer has suggested.
The code is :
image = cv2.imread(imagepath)
result_image = image.copy()
# Specify the trained cascade classifier
face_cascade_name = "./haarcascade_frontalface_alt.xml"
# Create a cascade classifier
face_cascade = cv2.CascadeClassifier()
# Load the specified classifier
face_cascade.load(face_cascade_name)
#Preprocess the image
grayimg = cv2.cvtColor(image, cv2.cv.CV_BGR2GRAY)
grayimg = cv2.equalizeHist(grayimg)
#Run the classifiers
faces = face_cascade.detectMultiScale(grayimg, 1.1, 2, 0|cv2.cv.CV_HAAR_SCALE_IMAGE, (30, 30))
print "Faces detected"
if len(faces) != 0: # If there are faces in the images
for f in faces: # For each face in the image
# Get the origin co-ordinates and the length and width till where the face extends
x, y, w, h = [ v for v in f ]
# get the rectangle img around all the faces
cv2.rectangle(image, (x,y), (x+w,y+h), (255,255,0), 5)
sub_face = image[y:y+h, x:x+w]
# apply a gaussian blur on this new recangle image
sub_face = cv2.GaussianBlur(sub_face,(23, 23), 30)
# merge this blurry rectangle to our final image
result_image[y:y+sub_face.shape[0], x:x+sub_face.shape[1]] = sub_face
face_file_name = "./face_" + str(y) + ".jpg"
cv2.imwrite(face_file_name, sub_face)
# cv2.imshow("Detected face", result_image)
cv2.imwrite("./result.png", result_image)
Arnaud
The whole end of your code can be replaced by :
img[startX:endX, startY:endY] = cv2.blur(img[startX:endX, startY:endY], (23, 23))
instead of :
# Get the origin co-ordinates and the length and width till where the face extends
x, y, w, h = [ v for v in f ]
# get the rectangle img around all the faces
cv2.rectangle(image, (x,y), (x+w,y+h), (255,255,0), 5)
sub_face = image[y:y+h, x:x+w]
# apply a gaussian blur on this new recangle image
sub_face = cv2.GaussianBlur(sub_face,(23, 23), 30)
# merge this blurry rectangle to our final image
result_image[y:y+sub_face.shape[0], x:x+sub_face.shape[1]] = sub_face
Especially because you don't request to have a circular mask, it's (to me) much easier to read.
PS : Sorry for not commenting, not enough reputation to do it. Even if the post is 5 years old, I guess this may be worth it, as found it for this particular question ..
Note: Neural Networks (like Resnet) are now more accurate than HAAR Cascade to detect the faces, and they are also now integrated in OpenCV. It might be better than using the solutions mentionned in this question.
However, the code to blur / pixelate a face is still applicable.
You can also pixelate the region of the face by adding squares that contain the average of RGB values of the zones in the face.
A function performing this could be like that:
def pixelate_image(image: np.ndarray, nb_blocks=5, in_place=False) -> np.ndarray:
"""Return a pixelated version of a picture (need to be fed with a face to pixelate)"""
# To pixelate, we will split into a given number of blocks
# For each block, we will compute the average of RGB values of the block
# And then we can just replace with a rectangle of this color
# divide the input image into NxN blocks
if not in_place:
image = np.copy(image)
h, w = image.shape[:2]
blocks = tuple(
np.linspace(0, d, nb_blocks + 1, dtype="int") for d in (w, h)
)
for i, j in product(*[range(1, len(s)) for s in blocks]):
# compute the starting and ending (x, y)-coordinates
# for the current block
start = blocks[0][i - 1], blocks[1][j - 1]
end = blocks[0][i], blocks[1][j]
# extract the ROI using NumPy array slicing, compute the
# mean of the ROI, and then draw a rectangle with the
# mean RGB values over the ROI in the original image
roi = image[start[1]:end[1], start[0]:end[0]]
bgr = [int(x) for x in cv2.mean(roi)[:3]]
cv2.rectangle(image, start, end, bgr, -1)
return image
You then just need to use it in a function like this (updated to Python 3 with pathlib and type hints):
from pathlib import Path
from typing import Union
import cv2
import numpy as np
PathLike = Union[Path, str]
face_cascade = cv2.CascadeClassifier("haarcascade_frontalface_alt.xml")
def pixelate_faces_haar(img_path: PathLike, dest: Path):
"""Pixelate faces of people with OpenCV and save to a destination file"""
img = cv2.imread(str(img_path))
# To use cascade, we need to use Grayscale images
# We can then detect faces
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(gray, 1.1, 4)
for (x, y, width, height) in faces:
roi = img[y:y+height, x:x+width]
pixelate_image(roi, 15, in_place=True)
dest.parent.mkdir(parents=True, exist_ok=True)
cv2.imwrite(str(dest), img)
print(f"Saved pixelated version of {img_path} to {dest}")```

Count number of detections with OpenCV lib with Python

I have started to learn OpenCV lib in Python. I want to write a code that will detect faces and eyes on the photo but also count number of detections.
I have wrote a picture that has 6 people on it.
You can download the image form here:
https://www.sendspace.com/file/cznbqa
I wrote a code that detect faces and eyes, but I do not know how to count number of detections.
Also, my code detect 4/8 faces and 1/12 eyes on the photo so...I probably have some issues in the code, If you can help me with that also, I would be very thankful.
# Standard imports
import cv2
import numpy as np
# Read image
my_image = cv2.imread("ljudi.jpg", 1)
# data for detecting faces and eyes
face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_alt.xml')
eye_cascade = cv2.CascadeClassifier('haarcascade_eye.xml')
img = cv2.imread("ljudi.jpg", 1)
gray = cv2.cvtColor(img, 0)
faces = face_cascade.detectMultiScale(gray, 1.2, 4)
for (x,y,w,h) in faces:
#detect face with bule rectangle
#rec. start point (x,y), rec. end point (x+w, y+h), blue color(255,0,0), line width 2
face_rec = cv2.rectangle(img, (x,y), (x+w, y+h), (255,0,0), 1)
roi_gray = gray[y:y+h, x:x+w]
roi_color = img[y:y+h, x:x+w]
eyes = eye_cascade.detectMultiScale(roi_gray, 1.1, 2)
for (ex,ey,ew,eh) in eyes:
eye_rec = cv2.rectangle(roi_color, (ex,ey), (ex+ew, ey+eh), (0,255,0), )
cv2.imshow('img', img)
cv2.waitKey(0)
From the OpenCV documentation:
https://www.docs.opencv.org/2.4/modules/objdetect/doc/cascade_classification.html#cascadeclassifier-detectmultiscale
Detects objects of different sizes in the input image. The detected objects are returned as a list of rectangles.
where a rectangle for the Python bindings simply seems to be of type list [x,y,w,h] (https://www.docs.opencv.org/2.4/modules/core/doc/basic_structures.html#rect).
To the number of faces / rectangles returned inside the list can easily be retrieved by getting the length of the list (i.e. number of items inside), which is done with the Python builtin function len(): https://docs.python.org/3/library/functions.html#len.

Categories