Skip to main content
Ben Nadel at CFinNC 2009 (Raleigh, North Carolina) with: Phil Molaro and Andy Matthews
Ben Nadel at CFinNC 2009 (Raleigh, North Carolina) with: Phil Molaro Andy Matthews

Selecting My SOTR Caffeinated Raffle Winner Using ColdFusion And Face.com's Facial Detection API

By
Published in Comments (11)

A couple of days before Scotch On The Rocks (SOTR - Europe's premier ColdFusion conference), I Tweeted that anyone who brought be a sugar-free energy drink would get entered into a raffle. I know it's taken me a few weeks to get around to it; but, Darren Walker, Gareth Sykes, Paul Klinkenberg, Tom Chiverton (and Rachael), and Tyler Schofield were awesome enough to supply me with enough caffeine to cope with the 5-hour timezone shift and lack of sleep.

Since I had my picture taken with each one of them, I thought a fun way to go about selecting a winner would be through some sort of Facial Recognition algorithm. At first, I was going to try the Faint project (as described by Todd Sharp). But, as it turns out, Faint only works on Windows machines thanks to some of the linked libraries that it uses.

After a bit of Googling, however, I discovered Face.com. Face.com provides a free developer API for facial recognition and organization of people within photos. But, more than just facial detection, it helps you match faces across photos and then organize them into some sort of contact database (at least from what I gather) that will help you automatically tag photos going forward.

Face.com appears to have a really robust feature set; but for our purposes, we'll only need the raw face detection API. The API can be accessed using CFHTTP in which one photo can be posted at a time. For each photo that is posted, Face.com returns a collection of "Tags" - each of which represents a facial detection within the given photo.

Face.com facial detection API response.

For each tag, Face.com reports the boundaries of the face, facial landmarks (ie. eyes, nose, mouth, ears), and rotational position of the face (which I assume is what Yaw, Roll, and Pitch represent). It also tells you, with a given degree of confidence, if the visual representation is, in fact, a face, whether or not it's smiling, wearing glasses, and what gender it is. To select my Caffeinated Comrade winner, I figured I could use Face.com to select the photo that contains the most "confident" face. That is, the photo in which the API is most sure about: Face, Gender, Smiling, and Eye Glasses.

<!---
	Give this page some extra timeout wiggle room since we'll be
	uploading and then detail with image manipulation APIs.
--->
<cfsetting requesttimeout="#(60 * 2)#" />


<!--- Define the path to our images directory. --->
<cfset imageDirectory = expandPath( "./images/" ) />

<!---
	Define the image to upload for face-detection. These are
	the awesome ColdFusion developers who hooked me up with
	some caffeinated drinks when I was at Scotch on the Rocks.
--->
<cfset imageFiles = [
	"darren_walker.jpg",
	"gareth_sykes.jpg",
	"paul_klinkenberg.jpg",
	"tom_chiverton.jpg",
	"tyler_schofield.jpg"
	] />


<!--- ----------------------------------------------------- --->
<!--- ----------------------------------------------------- --->


<!---
	Create an array to hold detection results. Face.com can only
	accept one image at a time, so we'll have too loop over the
	photos and post each individually, storing the results in turn.
--->
<cfset results = [] />

<!---
	Post these photos to Face.com. Each photo will be analyzed
	for faces; each face will be returned as a "Tag" with
	positional information as well as confidence as to whether
	or not the face was an actual face.
--->
<cfloop
	index="imageFile"
	array="#imageFiles#">


	<!---
		Post the photo to the Face.com API.

		NOTE: By using a ".json" as the target web service file
		extension, we will be getting a JSON response.
	--->
	<cfhttp
		result="detectionRequest"
		method="post"
		url="http://api.face.com/faces/detect.json">

		<!--- Post credentials. --->
		<cfhttpparam
			type="url"
			name="api_key"
			value="#request.faceAPIKey#"
			/>

		<cfhttpparam
			type="url"
			name="api_secret"
			value="#request.faceAPISecret#"
			/>

		<!---
			Set the matching strength. We'll use Normal. An
			"Agressive" search might turn up more faces; but for
			our pursoses, that shouldn't be necessary (the photos
			are very clear).
		--->
		<cfhttpparam
			type="url"
			name="detector"
			value="Normal"
			/>

		<!--- Post the file (as "filename"). --->
		<cfhttpparam
			type="file"
			name="filename"
			file="#imageDirectory##imageFile#"
			mimetype="images/jpeg"
			/>

	</cfhttp>


	<!---
		Deserialize the facial detection response and save the
		data structure for evaluation.
	--->
	<cfset arrayAppend(
		results,
		deserializeJSON( detectionRequest.fileContent )
		) />

</cfloop>


<!--- ----------------------------------------------------- --->
<!--- ----------------------------------------------------- --->


<!---
	At this point, we have passed each image to the Face.com and
	should have detection information. Now, we are going to take
	those results and see if we can highlight the faces in our
	images.
--->


<!--- Create an array to hold our ColdFusion images objects. --->
<cfset images = [] />

<!---
	Each of the tags within our images (as returned from Face.com)
	provides information about the face, gender, eye glasses, ears,
	and mouth. And each of these is given a confidence. To figure
	out the WINNER, we are going to which photo contains the face
	that Face.com gives the most confident detection.
--->
<cfset entries = queryNew(
	"url, confidence",
	"cf_sql_varchar, cf_sql_integer"
	) />


<!--- Loop over the results to help create the images. --->
<cfloop
	index="resultIndex"
	from="1"
	to="#arrayLen( results )#"
	step="1">


	<!--- Get a reference to this result. --->
	<cfset result = results[ resultIndex ].photos[ 1 ] />

	<!--- Create a ColdFusion image for this result. --->
	<cfset image = imageNew( "./images/#imageFiles[ resultIndex ]#" ) />

	<!---
		Get the image height and width (we can get this from the
		image, but since the result provides it already for their
		calculations, just grab it).
	--->
	<cfset imageWidth = result.width />
	<cfset imageHeight = result.height />

	<!---
		Now, loop over the "Tags". Each tag represents a facial
		detection within the current photo.
	--->
	<cfloop
		index="tag"
		array="#result.tags#">

		<!---
			Get the tag dimensions. While we have defined the image
			dimensions above as pixels, everything about the tags is
			expressed as percentage (to allow for arbitrary image
			scaling).
		--->
		<cfset tagWidth = (tag.width / 100 * imageWidth) />
		<cfset tagHeight = (tag.height / 100 * imageHeight) />

		<!---
			The coordinates of the tag are expressed as X/Y at the
			center of the tag.
		--->
		<cfset tagCenterX = (tag.center.x / 100 * imageWidth) />
		<cfset tagCenterY = (tag.center.y / 100 * imageHeight) />

		<!---
			Before we draw the box, let's define the line properties
			of the rectangle.
		--->
		<cfset strokeOptions = {
			width = 3,
			endcaps = "round"
			} />

		<!--- Set the line drawing properties. --->
		<cfset ImageSetDrawingStroke( image, strokeOptions ) />

		<!--- Draw the box around the current face (Tag). --->
		<cfset imageDrawRect(
			image,
			(tagCenterX - (tagWidth / 2)),
			(tagCenterY - (tagHeight / 2)),
			tagWidth,
			tagHeight
			) />


		<!--- Each tag is going to be an entry in our query. --->
		<cfset queryAddRow( entries ) />

		<!--- Set the query values. --->
		<cfset entries[ "url" ][ entries.recordCount ] = javaCast(
			"string",
			results[ resultIndex ].photos[ 1 ].url
			) />

		<cfset entries[ "confidence" ][ entries.recordCount ] = javaCast(
			"int",
			(
				tag.attributes.face.confidence +
				tag.attributes.gender.confidence +
				tag.attributes.glasses.confidence +
				tag.attributes.smiling.confidence
			)
			) />

	</cfloop>


	<!---
		Now that all of the tags have been applied to the image,
		store the upadated ColdFusion image object for later use.
	--->
	<cfset arrayAppend( images, image ) />


</cfloop>


<!--- ----------------------------------------------------- --->
<!--- ----------------------------------------------------- --->


<h1>
	Scotch On The Rocks Caffeinated Comrades
</h1>


<cfloop
	index="image"
	array="#images#">

	<p>
		<!--- Draw the image to the browser. --->
		<cfimage
			action="writeToBrowser"
			source="#image#"
			width="505"
			/>
	</p>

</cfloop>


<!--- ----------------------------------------------------- --->
<!--- ----------------------------------------------------- --->


<!---
	Now that we have output all the images and facial detection
	boxes, it's time to see which face Face.com was most generally
	confident about.

	Re-order to the entries query by confidence.
--->
<cfquery name="entries" dbtype="query">
	SELECT
		*
	FROM
		entries
	ORDER BY
		confidence DESC
</cfquery>

<h2>
	And The Winner Is....
</h2>

<p>
	<!--- Output the winner. --->
	<cfimage
		action="writeToBrowser"
		source="#entries.url#"
		width="505"
		/>
</p>

When we run the above code, we get the following page output:

SOTR Caffeinated Comrades raffler winner selection using Face.com facial detection API.

Congratulations to Tom Chiverton and Rachael!

And, on a personal note, an extra special thanks to Tom and Rachael who are totally awesome and let me tag along Saturday after the SOTR conference to explore Edinburgh and The Whiskey Experience. Oh, and they also got me my first-ever Irn-Bru which, from what I can tell, is basically liquified Gummi Bears.

IrnBru - pronounced, urn-brew.

It's pronounced, earn-br'!

Want to use code from this post? Check out the license.

Reader Comments

3 Comments

Liquified Gummi Bears, LOL. Quite a good description actually :)

I've always pronounced it Iron Brew, and a few copy cat companies have labelled it that as well. Helps the non-Scotch tongued out there I'm sure ;).

Glad you enjoyed it and the trip though Ben; it's one of our national treasures :)

James
( +20yrs as a Scottish legal alien )

15,841 Comments

@James,

I started off pronouncing it "iron brew", but was quickly corrected by the "locals" that I was adding far too many syllables :)

It seems that a lot of English-to-Scottish translation is performed by simply removing parts of words :P

3 Comments

Absolutely and it gets a LOT worse the further north you go :). I swear some of the locals here don't even speak the same language as me!!

They would have probably given you a different answer / pronunciation for it as well, lol.

148 Comments

Liquified gummi bears? Okay, that's going to be easier to digest (these gummi bears sure are tough to chew, aren't these?).

And . . . how does Face.com determine whether a person is smiling or not?

11 Comments

Now that was an awesome way to do a raffle! I enjoyed reading it, but will be feeling sad and disappointed all night now :-P
See you next year probably!

15,841 Comments

@Paul,

Thanks - AND, thanks for the drink! Always great to see you (and congratulations again on the beautiful baby!). See you next year.

12 Comments

Nice post Ben and awesome way to pick a winner! I have to question the results though, as I believe that the confidence rating is based on all the faces detected in the photo! (kidding)

I was great to catch up with you again, I'm just sad that I didn't get a new picture at all this year :)

15,841 Comments

@John,

Always good to catch up - sorry we didn't get to talk more. The conference is getting so big these days! I'll definitely see you next year. My goal: be able to talk to you about ORM by next year :D

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel