Adapting opencv facial recognition script from haarcascade to DNN

I have a script chop that detects faces in a video feed using opencv haarcascades.

I am in the process of adapting this script to work with opencv DNN-based Face Detection And Recognition
https://docs.opencv.org/4.x/d0/dd4/tutorial_dnn_face.html

I’m a newb to python and touchdesigner, so running into issues.

I am currently getting this error on line 58:
cv2.error: OpenCV(4.7.0) /Users/malcolm/dev/devel/opencv/modules/dnn/src/layers/convolution_layer.cpp:420: error: (-2:Unspecified error) Number of input channels should be multiple of 3 but got 1 in function 'getMemoryShapes'

Line 58 is:
faces1 = detector.detect(gray, faces)

I’ve tried various other things in here based on research I’ve done but I’m shooting in the dark for the most part.

Not sure if line 57 is at all correct…but it doesn’t throw an error:
faces = np.zeros((0,0), dtype=np.float32)

Here’s my .toe if you want to take a look. I removed the artistic effects that I am using in my project just to simplify. In the file you can see the working haarcascade version and how I am handling the outputs. I’d like to do the same or similar with the DNN method.
005.opencv-face-v3-DNN-FORFORUM.toe (7.0 KB)

Here is the full script:

# me - this DAT
# scriptOp - the OP which is cooking

import cv2
import numpy as np

# press 'Setup Parameters' in the OP to call this function to re-create the parameters.
def onSetupParameters(scriptOp):
	page = scriptOp.appendCustomPage('Face Detection')
	p = page.appendTOP('Top', label='TOP')
	p = page.appendFloat('Scale', label='Scale Factor')
	p.normMin = 1.001
	p.normMax = 2
	p.clampMin = True
	p.default = 1.1
	p = page.appendInt('Neighbors', label='Min Neighbors')
	p.normMin = 0
	p.normMax = 10
	p.default = 1
	p = page.appendFile('Facedetectionmodel', label='Face Detection yunet .onnx file')
	p = page.appendFile('Facerecognitionmodel', label='Face Recognition sface .onnx file')
	p = page.appendFloat('Scorethreshold', label='Filtering out faces of score < score threshold')
	p = page.appendFloat('Nmsthreshold', label='Suppress bounding boxes of iou >= nms_threshold')
	p = page.appendInt('Topk', label='Keep top_k bounding boxes before NMS')
	return


# get the node this DAT is docked to
# this way we can get to the custom file parameter above
# dockedScript = me.dock
# face_cascade = cv2.CascadeClassifier(me.dock.par.Facedetectionmodel.eval())

# called whenever custom pulse parameter is pushed
def onPulse(par):
	return

def onCook(scriptOp):

	scriptOp.clear()
	inputFrame = scriptOp.par.Top.eval().numpyArray(delayed=True)
	
	gray = cv2.cvtColor(inputFrame, cv2.COLOR_RGB2GRAY)
	gray = (gray * 255).astype(np.uint8)

	# detect faces
	detector = cv2.FaceDetectorYN.create(
		scriptOp.par.Facedetectionmodel.eval(),
		"",
		(320, 320),
		scriptOp.par.Scorethreshold.eval(),
		scriptOp.par.Nmsthreshold.eval(),
		scriptOp.par.Topk.eval(),
	) 
	
	# Set input size before inference
	detector.setInputSize((640, 360))
	faces = np.zeros((0,0), dtype=np.float32)
	faces1 = detector.detect(gray, faces)

	# draw rectangles around the faces 
	# for (x, y, w, h) in faces:
	# 	cv2.rectangle(inputFrame, (x, y), (x+w, y+h), (255, 0, 255), 2)        
	# scriptOp.copyNumpyArray(inputFrame)

	# setup CHOP
	facesDetected = len(faces)
	scriptOp.numSamples = facesDetected
	x = scriptOp.appendChan('x')
	y = scriptOp.appendChan('y')
	w = scriptOp.appendChan('w')
	h = scriptOp.appendChan('h')
	a = scriptOp.appendChan('active')
	
	# transpose the array with detected faces
	if len(faces):
		faces = faces.transpose()
	
		# now we get an array of 4 arrays
		# this is easy to assign to channel values
		x.vals = faces[0]
		y.vals = faces[1]
		w.vals = faces[2]
		h.vals = faces[3]
		a.vals = [facesDetected]

	return

Hi @nerk,

the error Number of input channels should be multiple of 3 but got 1 in function 'getMemoryShapes' hints to the function only receiving a single channel when 3 are expected. So that got me looking in your code and I see that you are converting the image to a gray scale single channel array:

gray = cv2.cvtColor(inputFrame, cv2.COLOR_RGB2GRAY)
gray = (gray * 255).astype(np.uint8)

I don’t think this is required, rather you should pass in rgb channels, hence the need to cut off the alpha channel:

inputFrame = scriptOp.par.Top.eval().numpyArray(delayed=True)
# discard the alpha channel
inputFrame = inputFrame[:,:,:3]

I’m not sure if this needs to be a uint8 and hence still must be converted but this should get you a bit further.

Cheers
Markus

Hi @snaut , yes that did get me further, thanks. here is my updated and stripped down code for now:

def onCook(scriptOp):

	scriptOp.clear()
	inputFrame = scriptOp.par.Top.eval().numpyArray(delayed=True)
	inputFrame = inputFrame[:,:,:3]

	#create face detector
	detector = cv2.FaceDetectorYN.create(
		scriptOp.par.Facedetectionmodel.eval(),
		"",
		(320, 320),
		scriptOp.par.Scorethreshold.eval(),
		scriptOp.par.Nmsthreshold.eval(),
		scriptOp.par.Topk.eval(),
	) 
	
	# Set input size before inference
	detector.setInputSize((640, 360))
	faces = detector.detect(inputFrame)

	return

and on the faces = detector.detect(inputFrame) line I am now getting the error:

cv2.error: OpenCV(4.7.0) 
/Users/malcolm/dev/devel/opencv/modules/dnn/src/layers/nary_eltwise_layers.cpp:139: 
error: (-215:Assertion failed) shape[i] == 1 || outShape[i] == 1 in function 'findCommonShape'`

I have made sure that inputFrame is the correct shape, which I believe needs to be (360, 640, 3), or is that incorrect?

Hi @nerk,

i’m not seeing this error when plainly running the code - only real difference between setups is my system being windows while you are on OSX. Can you run this script as a straight up python file on your system without errors?

cheers
Markus

@snaut if I run this script:

import cv2
import numpy as np

detector = cv2.FaceDetectorYN.create(
	'/Users/noah/Desktop/TouchDesigner/Tools/face detection and recognition models/face_detection_yunet_2023mar.onnx',
	"",
	(320, 320),
	0.9,
	0.3,
	5000,
) 

detector.setInputSize((640, 360))

faces = detector.detect(inputFrame)

i get this error:

Traceback (most recent call last):
  File "/Users/noah/Downloads/test.py", line 1, in <module>
    import cv2
  File "/opt/homebrew/lib/python3.11/site-packages/cv2/__init__.py", line 181, in <module>
    bootstrap()
  File "/opt/homebrew/lib/python3.11/site-packages/cv2/__init__.py", line 153, in bootstrap
    native_module = importlib.import_module("cv2")
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/python@3.11/3.11.6/Frameworks/Python.framework/Versions/3.11/lib/python3.11/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ImportError: dlopen(/opt/homebrew/Cellar/opencv/4.8.1_1/lib/python3.11/site-packages/cv2/python-3.11/cv2.cpython-311-darwin.so, 0x0002): Library not loaded: /opt/homebrew/opt/mbedtls/lib/libmbedcrypto.14.dylib
  Referenced from: <8C2E2CF9-054E-300F-BDE7-3DE52F848638> /opt/homebrew/Cellar/librist/0.2.7_3/lib/librist.4.dylib
  Reason: tried: '/opt/homebrew/opt/mbedtls/lib/libmbedcrypto.14.dylib' (no such file), '/System/Volumes/Preboot/Cryptexes/OS/opt/homebrew/opt/mbedtls/lib/libmbedcrypto.14.dylib' (no such file), '/opt/homebrew/opt/mbedtls/lib/libmbedcrypto.14.dylib' (no such file), '/usr/local/lib/libmbedcrypto.14.dylib' (no such file), '/usr/lib/libmbedcrypto.14.dylib' (no such file, not in dyld cache), '/opt/homebrew/Cellar/mbedtls/3.5.0/lib/libmbedcrypto.14.dylib' (no such file), '/System/Volumes/Preboot/Cryptexes/OS/opt/homebrew/Cellar/mbedtls/3.5.0/lib/libmbedcrypto.14.dylib' (no such file), '/opt/homebrew/Cellar/mbedtls/3.5.0/lib/libmbedcrypto.14.dylib' (no such file), '/usr/local/lib/libmbedcrypto.14.dylib' (no such file), '/usr/lib/libmbedcrypto.14.dylib' (no such file, not in dyld cache)

Seems like there is an issue with my opencv installation, I think?

Hi @nerk,

difficult to tell for me - I just asked bing and got these answers:

This error is alluding to the fact that your Python program is trying to import the OpenCV library, but it fails to load one of its dependencies, which is the libmbedcrypto.14.dylib library. This library is part of the mbedtls package, which provides cryptographic and SSL/TLS functions 1. It seems that your program cannot find this library in any of the expected locations on your system.

Some possible causes and solutions for this error are:

• You have installed OpenCV and mbedtls using different package managers, such as pip and brew, and they are not compatible with each other. You can try to uninstall both packages and reinstall them using the same package manager, preferably brew 2.

• You have updated or upgraded your mbedtls package and it has changed the name or location of the libmbedcrypto library. You can try to reinstall OpenCV or link it to the new library using the install_name_tool command 3.

• You have an M1 Mac and you are running your Python program in a different architecture mode than your OpenCV and mbedtls packages. You can try to use the arch command to specify the architecture mode, such as arch -x86_64 or arch -arm64, when running your program 4.

• You have a corrupted or incomplete installation of OpenCV or mbedtls. You can try to reinstall them from scratch or from a source file 5.

cheers
Markus

1 Like