Skip to content

Facial Attributes Detection Using Cloudinary

Cloudinary provides an Advanced Facial Attribute Detection add-on, an integrated face detection solution that utilizes Microsoft Cognitive Services to automatically extract meaningful advanced data about the face(s) in an image. It also extracts other face-related attributes and the exact location of notable facial features. This add-on is fully integrated into Cloudinary’s image management and transformation pipeline, allowing us to extend Cloudinary’s features that involve semantic photo data extraction, image cropping, and the positioning of image overlays.

In this post, we’ll create a simple app that illustrates how to extract advanced facial attributes from an image, crop, and add an image overlay based on the extracted data.

Here is a link to the demo CodeSandbox.

Create a new Next.js application using the following command:

npx create-next-app facial-attributes-detection

Run these commands to navigate into the project directory and install the required dependencies:

cd facial-attributes-detection
npm install cloudinary axios

The Cloudinary Node SDK will provide easy-to-use methods to interact with the Cloudinary APIs, while axios will serve as our HTTP client.

Now we can start our application on http://localhost:3000/ using the following command:

npm run dev

First, sign up for a free Cloudinary account if you don’t have one already. Displayed on your account’s Management Console (aka Dashboard) are important details: your cloud name, API key, etc.

Next, let’s create environment variables to hold the details of our Cloudinary account. Create a new file called .env at the root of your project and add the following to it:

CLOUD_NAME = YOUR CLOUD NAME HERE
API_KEY = YOUR API API KEY
API_SECRET = YOUR API API SECRET

This will be used as a default when the project is set up on another system. To update your local environment, create a copy of the .env file using the following command:

    cp .env .env.local
Code language: CSS (css)

By default, this local file resides in the .gitignore folder, mitigating the security risk of inadvertently exposing secret credentials to the public. You can update the .env.local file with your Cloudinary credentials.

Cloudinary makes it compulsory that we subscribe to an add-on before we can use it. To register for the Advanced facial attributes detection add-on, follow the steps below:

  • Click on the Add-ons link in your Cloudinary console.

  • You should see a page consisting of all the available Cloudinary add-ons. Scroll down to locate the Advanced Facial Attributes Detection add-on, click on it and select your preferred plan. We’ll be using the free plan for this project, which gives us 50 free detections monthly.

A detailed object comprising facial attributes of faces detected in an image can be extracted by setting the detection parameter to adv_face when uploading an image to Cloudinary using the upload API. Data detected and extracted by the add-on are stored in a data key nested in an info node of the JSON response.

The value stored in the data key is an array of objects, with each object holding full details about the individual faces detected. The details are divided into attributes, bounding_box, and facial_landmarks.

  • attributes: holds a key-value pair of general information such as the expression expressed by an individual, details about the hair, gender, make-up, and so on.

  • bounding_box: contains details about the bounding box surrounding a detected face, height, width, etc.

  • facial_landmarks: contains the exact position details of specific elements of the mouth, eyebrows, eyes, and nose.

Let’s upload an image to Cloudinary and set the detection parameter to adv_face to see the complete response returned. Create a file named upload.js in the pages/api directory and add the following to it:

const cloudinary = require("cloudinary").v2;

cloudinary.config({
  cloud_name: process.env.CLOUD_NAME,
  api_key: process.env.API_KEY,
  api_secret: process.env.API_SECRET,
  secure: true,
});

export default async function handler(req, res) {
  try {
    const response = await cloudinary.uploader.upload(req.body.image, {
      detection: "adv_face",
    });
    res.status(200).json(response);
  } catch (error) {
    res.status(500).json(error);
  }
}

export const config = {
  api: {
    bodyParser: {
      sizeLimit: "4mb",
    },
  },
};
Code language: JavaScript (javascript)

In the code above, we defined an upload API route to handle file upload to Cloudinary. We import Cloudinary and configure it with an object consisting of our Cloudinary credentials. Next, we define a route handler, which calls the Cloudinary upload method and passes the expected base64 image with an object to set the detection parameter as arguments. The response is sent back to the client; otherwise, an error is sent.

At the bottom of the file, we export the Next.js default config object to set the default payload size limit to 4MB.

Now let’s create a client-side for selecting and forwarding any selected image to the /upload route. Clear the existing content in your pages/index.js file and update it with the following:

import { useState } from "react";
import axios from "axios";
import styles from "../styles/Home.module.css";

export default function Home() {
  const [image, setImage] = useState("");
  const [uploadStatus, setUploadStatus] = useState();
  const [imageId, setImageId] = useState("");

  const handleImageChange = (e, setStateFunc) => {
    const reader = new FileReader();
    if (!e.target.files[0]) return;
    reader.readAsDataURL(e.target.files[0]);
    reader.onload = function (e) {
      setStateFunc(e.target.result);
    };
  };

  const handleUpload = async () => {
    setUploadStatus("Uploading...");
    try {
      const response = await axios.post("/api/upload", { image });
      setImageId(response.data.public_id);
      setUploadStatus("Upload successful");
      console.log(response.data);
    } catch (error) {
      setUploadStatus("Upload failed..");
    }
  };

  return (
    <main className={styles.main}>
      <h2>Facial attributes detection</h2>
      <div>
        <div className={styles.input}>
          <div>
            <label htmlFor="image">
              {image ? (
                <img src={image} alt="image" />
              ) : (
                "Click to select image"
              )}
            </label>
            <input
              type="file"
              id="image"
              onChange={(e) => handleImageChange(e, setImage)}
            />
          </div>
          <button onClick={handleUpload}>Upload</button>
          <p>{uploadStatus}</p>
        </div>
      </div>
    </main>
  );
}
Code language: JavaScript (javascript)

In the code above, we defined the Home component to hold three states for the selected image, the request status, and a Cloudinary-generated ID. Next, we rendered a file input field and worked around opening the custom file picker that triggers the handleImageChange function when a file is selected. The function then converts the selected image to its base64 equivalent.

We also rendered a button that calls the handleUpload function on click. handleUpload makes an Axios call to our API route and sets the required states accordingly. We also logged the complete response to the console.

Now let’s add some styles to give our application a decent look. Copy the styles in this codeSandbox link to your styles/Home.module.css file.

Next, preview the application in your browser and upload an image with faces. Then open the developer console to see the complete JSON response object.

Displayed below is a closer look at the response object.

We can also use Cloudinary’s Admin API to apply automatic face attribute detection to uploaded images based on their public IDs. To achieve this, call the update method of the Admin API and set the detection parameter to adv_face, as shown below.

const response = await cloudinary.v2.api.update("public-id", {
  detection: "adv_face",
});
Code language: JavaScript (javascript)

As mentioned earlier, the Advanced Facial Attribute Detection add-on is fully integrated into Cloudinary’s image management and transformation pipeline. Therefore, we can crop and apply other transformations to the image based on the position of facial attributes detected by the Advanced Facial Attribute Detection add-on.

To crop a processed image so it focuses on the detected faces in the image, we need to set the gravity parameter to adv_faces or adv_face to focus on the single largest detected face in the image when calling the image method of Cloudinary’s image transformation API.

We also need to specify the width and height parameters and set the crop parameter to either crop, thumb, or fill. Click here to learn more about the various image resizing and cropping options.

To add the cropping functionality to our application, create a crop.js file in the pages/api folder and add the following to it:

const cloudinary = require("cloudinary").v2;

cloudinary.config({
  cloud_name: process.env.CLOUD_NAME,
  api_key: process.env.API_KEY,
  api_secret: process.env.API_SECRET,
  secure: true,
});

export default async function handler(req, res) {
  try {
    const response = await cloudinary.image(`${req.body.imageId}.jpg`, {
      gravity: "adv_faces",
      height: 240,
      width: 240,
      crop: "thumb",
      sign_url: true,
    });
    res.status(200).json(response);
  } catch (error) {
    res.status(500).json(error);
  }
}
Code language: JavaScript (javascript)

In the code above, in addition to the gravity, width, height, and crop parameters, we set the sign_url parameter to true to reduce the potential costs of users accessing unplanned dynamic URLs with the Advanced Facial Attribute Detection cropping directives.

The expected response sent back to the client-side will be an <img> element with a URL that links to the cropped image.

Let’s update the client-side of the application to reflect the changes. Update your pages/index.js file with the following:

export default function Home() {
  //...

  // Add this
  const [cldData, setCldData] = useState("");

  const handleImageChange = (e, setStateFunc) => {
    //...
  };

  const handleUpload = async () => {
    //...
  };

  const handleCrop = async () => {
    setUploadStatus("Cropping...");
    try {
      const response = await axios.post("/api/crop", { imageId });
      const imageUrl = /'(.+)'/.exec(response.data)[1].split("' ")[0];
      setCldData(imageUrl);
      setUploadStatus("done");
    } catch (error) {
      setUploadStatus("failed..");
    }
  };

  return (
    <main className={styles.main}>
      <h2>Facial attributes detection</h2>
      <div>
        <div className={styles.input}>
          <div>
            <label htmlFor="image">
              {image ? (
                <img src={image} alt="image" />
              ) : (
                "Click to select image"
              )}
            </label>
            <input
              type="file"
              id="image"
              onChange={(e) => handleImageChange(e, setImage)}
            />
          </div>
          <button onClick={handleUpload}>Upload</button>
          <p>{uploadStatus}</p>
          {/* Add this */}
          <div className={styles.btns}>
            <button disabled={!imageId} onClick={handleCrop}>
              Crop
            </button>
          </div>
        </div>
        {/* Add this */}
        <div className={styles.output}>
          {cldData ? <img src={cldData} alt=" " /> : "Output image"}
        </div>
      </div>
    </main>
  );
}
Code language: JavaScript (javascript)

In the updated code, we defined a state called cldData to hold the expected URL of the cropped image. We also rendered a button and an image with the URL saved in the cldData state.

The button gets disabled until a valid Cloudinary image ID is returned after uploading; it triggers the handleCrop function when clicked. The function initiates an Axios call to the /crop API route to get the <img> element response returned by Cloudinary. It then extracts the URL from the response and sets the states accordingly.

Save the changes and preview the application in your browser. You should be able to upload and crop an image based on the detected faces.

In addition to cropping processed images based on the detected faces, Cloudinary also supports eye detection-based cropping. It automatically crops images based on the position of detected eyes, leveraging the data detected by the add-on. To implement this, change the update your pages/api/crop.js file with the following:

const response = await cloudinary.image(`${req.body.imageId}.jpg`, {
  gravity: "adv_eyes", // add this
  height: 240,
  width: 240,
  crop: "thumb",
  sign_url: true,
});
Code language: JavaScript (javascript)

While considering the pose of the face detected in the extracted facial attribute, Cloudinary can position overlays on top of detected faces and even automatically scale and rotate the overlay according to how the underlying face is positioned.

To properly place an overlay on all detected faces in a processed image, set an overlay parameter to the public ID of your preferred overlay image and the gravity parameter of the added overlay to adv_faces. We also need to set the region_relative flag together with a width and crop value. The width takes a relative value that scales the overlay to 110% of the width of the detected face.

Let’s update our application to include this functionality. Create a file called overlay.js in the pages/api folder and add the following to it:

const cloudinary = require("cloudinary").v2;

cloudinary.config({
  cloud_name: process.env.CLOUD_NAME,
  api_key: process.env.API_KEY,
  api_secret: process.env.API_SECRET,
  secure: true,
});

export default async function handler(req, res) {
  const { imageId, overlay } = req.body;
  try {
    await cloudinary.uploader.upload(
      overlay,
      async function (error, uploadedOverlay) {
        const response = await cloudinary.image(`${imageId}.jpg`, {
          transformation: [
            { overlay: `${uploadedOverlay.public_id}` },
            { flags: "region_relative", width: "1.1", crop: "scale" },
            { flags: "layer_apply", gravity: "adv_faces" },
          ],
          sign_url: true,
        });
        res.status(200).json(response);
      }
    );
  } catch (error) {
    res.status(500).json(error);
  }
}

export const config = {
  api: {
    bodyParser: {
      sizeLimit: "4mb",
    },
  },
};
Code language: JavaScript (javascript)

With the code above, we created a new API route to handle applying overlay to a processed image. It expects the public id of the image that needs to be transformed and an overlay image from the client side.

The approach is similar to the one used in the previous API route files, except that we now configured the route handler to upload the overlay image to Cloudinary first to extract its public id from the response. Next, we called the image transformation method and set the overlay parameter to the extracted public id of the uploaded overlay image.

Let’s update the frontend code. Open your pages/index.js file and update the code as shown below:

export default function Home() {
  //...
  //add this
  const [overlay, setOverlay] = useState("");

  const handleImageChange = (e, setStateFunc) => {
    //...
  };

  const handleUpload = async () => {
    //...
  };

  const handleCrop = async () => {
    //...
  };

  // add this
  const handleAddOverlay = async () => {
    setUploadStatus("Adding overlay...");
    try {
      const response = await axios.post("/api/overlay", { imageId, overlay });
      const imageUrl = /'(.+)'/.exec(response.data)[1];
      setCldData(imageUrl);
      setUploadStatus("done");
    } catch (error) {
      setUploadStatus("failed..");
    }
  };

  return (
    <main className={styles.main}>
      <h2>Facial attributes detection</h2>
      <div>
        <div className={styles.input}>
          <div>
            <label htmlFor="image">
              {image ? (
                <img src={image} alt="image" />
              ) : (
                "Click to select image"
              )}
            </label>
            <input
              type="file"
              id="image"
              onChange={(e) => handleImageChange(e, setImage)}
            />
          </div>
          <button onClick={handleUpload}>Upload</button>
          <p>{uploadStatus}</p>
          <div className={styles.btns}>
            <button disabled={!imageId} onClick={handleCrop}>
              Crop
            </button>
            {/* add this */}
            <button disabled={!imageId || !overlay} onClick={handleAddOverlay}>
              Add Overlay
            </button>
          </div>
          {/* add this */}
          <div className={styles.overlay}>
            <label>Select Overlay</label>
            <input
              type="file"
              onChange={(e) => handleImageChange(e, setOverlay)}
            />
          </div>
        </div>
        <div className={styles.output}>
          {cldData ? <img src={cldData} alt=" " /> : "Output image"}
        </div>
      </div>
    </main>
  );
}
Code language: JavaScript (javascript)

We added a new state called overlay to hold the base64 equivalent of the overlay image selected by the user. Next, we added a <input> tag with a file type to select an overlay image and a button that triggers the handleAddOverlay function when clicked.

The function initiates an Axios call to the /overlay API route and attaches the image ID and the overlay image to the request’s body. Next, it formats the response to extract the output image URL and sets the states accordingly.

Now you can save the changes and preview the application in your browser.

Find the complete project here on GitHub.

The Advanced Facial Attribute Detection add-on powered by Cloudinary’s integration with Microsoft’s Cognitive Services provides a high-precision mechanism that can seamlessly analyze images to extract specific information about facial attributes. Using a simple Next.js application, we’ve seen how to use this add-on to extract advanced face attributes and smartly crop, position, rotate, and add overlay images based on these attributes.

Resources You May Find Helpful

Back to top

Featured Post