Auto-Tag and Categorize Images Using Cloudinary

Ifeoma Imoh

An ever-increasing amount of visual data is created and stored due to the rapid evolution of digital technology. Therefore, there is an urgent need for an effective and efficient tool or mechanism for categorizing and finding visual information on demand. Image tagging is adding text tags to an image based on what is in the image.

Although images can be annotated manually by humans, this approach can be too subjective and even impractical when there are a large number of images. However, a better approach is image auto-tagging, a time-saving and cost-effective process by which tags in the form of keywords are automatically assigned to digital images and are used to organize and retrieve images of interest from a collection or database. Images that have been auto-tagged can be retrieved using those tags or labels.

Cloudinary offers a Google auto-tagging add-on fully integrated into its image management and transformation system and allows detecting, automatically categorizing, and assigning multiple tags to images based on the categories in each image.

In this article, we'll go through how to use Cloudinary to generate tags for images automatically, fetch the generated image tags, and access image resources based on a specified tag.

Here is a link to the demo CodeSandbox.

Setting Up the Project

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

1npx create-next-app cloudinary-image-autotag

Next, let’s add the project dependencies using the following command:

1npm install cloudinary axios dotenv

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

1npm start

Setting up Cloudinary

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:

1CLOUD_NAME = YOUR CLOUD NAME HERE
2API_KEY = YOUR API API KEY
3API_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:

1cp .env .env.local

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.

Before using the Google Auto Tagging add-on, we need to register for the add-on. Go through these steps to register for the add-on:

  • 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 Google Auto Tagging 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 image categorizations monthly.

So far, we've set up our project and registered for Cloudinary's Google auto-tagging add-on. Let's move on to the procedures for implementing the add-on and fetching image resources based on specified tags.

Uploading and Auto-Tagging Images

Cloudinary introduces a straightforward and uncomplicated approach to automatically generating tags or labels for images. We can generate image tags by simply setting the categorization parameter of Cloudinary's upload method to google-tagging when calling Cloudinary's upload method.

The uploaded image is processed and analyzed to categorize its many segments and scenes into tags automatically. The API gives a response consisting of the categorization information, including the status, individual tags, and confidence score.

The confidence score is a numerical value between 0 and 1 that specifies the discovered tag's confidence level. The higher the level of confidence, the more confident the add-on is about the discovered tag and vice versa.

Before we begin, let's add some styles to give our application a decent look. Add the following to your Home.module.css file in the /styles directory:

1.container {
2 width: 1200px;
3 max-width: 90%;
4 margin: 2rem auto;
5}
6.container > form {
7 display: flex;
8 flex-direction: column;
9 align-items: center;
10 width: 20rem;
11 margin: 0 auto;
12}
13.container > form > button {
14 padding: 0.8rem 1.5rem;
15 background: #0000ff;
16 border: none;
17 margin: 1rem 0;
18 cursor: pointer;
19 width: 100%;
20 color: #fff;
21 font-size: 0.9rem;
22 font-weight: 700;
23}
24.tags {
25 display: flex;
26 flex-wrap: wrap;
27}
28.tags > button {
29 padding: 0.2rem 0.5rem;
30 margin: 0.3rem 0.2rem;
31 font-size: 0.8rem;
32 background: rgb(13, 150, 155);
33 border: none;
34 cursor: pointer;
35 color: #fff;
36}
37.images {
38 display: grid;
39 grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
40 grid-gap: 0.5rem;
41 margin: 1.5rem 0;
42}
43.images > div {
44 width: 100%;
45 height: 12rem;
46}
47.images > div > img {
48 object-fit: cover;
49 width: 100%;
50 height: 100%;
51}

Clear the existing content in your index.js file and update it with the following:

1import { useState } from "react";
2import styles from "../styles/Home.module.css";
3import axios from "axios";
4
5export default function Home() {
6 const [image, setImage] = useState("");
7
8 const handleInputOnChange = (event) => {
9 const reader = new FileReader();
10 reader.readAsDataURL(event.target.files[0]);
11 reader.onload = function (e) {
12 setImage(e.target.result);
13 };
14 };
15
16 const handleOnFormSubmit = async (event) => {
17 event.preventDefault();
18 try {
19 const response = await axios.post("/api/uploadImage", {
20 image: JSON.stringify(image),
21 });
22 console.log(response.data);
23 } catch (error) {
24 console.log("imageUpload" + error);
25 }
26 };
27
28 return (
29 <div className={styles.container}>
30 <form onSubmit={handleOnFormSubmit}>
31 <input type="file" onChange={handleInputOnChange}></input>
32 <button disabled={image ? false : true}>Upload to Cloudinary</button>
33 </form>
34 </div>
35 );
36}

In the code snippet above, we made some imports, and then we created a Home component that has an image state which contains the data sent to our serverless function. The Home component renders a simple form with a file input field and a submit button. The input field has an onChange prop that triggers the handleInputOnChange function whenever a file is selected.

The handleInputOnChange function uses the File reader API to convert the selected image file into its base64 string equivalent, which is then used to update the image state on successful completion. When the submit button is clicked, the handleOnFormSubmit function gets called. The handleOnFormSubmit function makes an HTTP request to the UploadImage serverless endpoint while attaching the base64 image data payload.

With that, we’ve created the upload interface. Now, let's create our serverless function to process the image using Cloudinary. Create a file named uploadImage.js in the /pages/api directory and add the following to it:

1const cloudinary = require("cloudinary").v2;
2require("dotenv").config();
3cloudinary.config({
4 cloud_name: process.env.CLOUD_NAME,
5 api_key: process.env.API_KEY,
6 api_secret: process.env.API_SECRET,
7 secure: true,
8});
9export default async function handler(req, res) {
10 const image = JSON.parse(req.body.image);
11 try {
12 const cldResponse = await cloudinary.uploader.upload(image, {
13 categorization: "google_tagging",
14 auto_tagging: 0.6,
15 });
16 res.status(200).json(cldResponse);
17 } catch (error) {
18 console.log(error);
19 res.json({ message: "an error occured while uploading image" });
20 }
21}

In the code above, we import Cloudinary and configure it with an object consisting of our Cloudinary credentials. Then we created our serverless function, which returns the response of calling Cloudinary's upload method on success or an error if it fails.

The upload method takes the image data sent as a payload from the frontend and an object containing optional parameters as arguments. In our case, we specified google-tagging as the value of the categorization add-on, which indicates automatically generating tags using the Google auto-tagging add-on to classify the scenes of the uploaded asset.

The object also takes an optional auto_tagging parameter which defines the minimum confidence score of a detected category that should be automatically used as an assigned resource tag. For example, we specified that the add-on should only generate tags with a minimum confidence score of 0.6.

Now you can upload an image to Cloudinary and view the uploaded image and its corresponding auto-generated tags in your Cloudinary account.

Displaying the Images

We need to devise a way to fetch and display all uploaded image resources and filter through the auto-generated tags. To do that, let's create a getImages.js file in our /pages/api folder and add the following to it:

1const cloudinary = require("cloudinary").v2;
2cloudinary.config({
3 cloud_name: process.env.CLOUD_NAME,
4 api_key: process.env.API_KEY,
5 api_secret: process.env.API_SECRET,
6 secure: true,
7});
8export default async function handler(req, res) {
9 try {
10 const images = await cloudinary.api.resources();
11 res.status(200).json(images.resources);
12 } catch (error) {
13 res.json({ message: "an error occured" });
14 }
15}

We imported Cloudinary and configured it by passing in an object consisting of our Cloudinary credentials and created a serverless function that fetches a list of all resources from the Cloudinary account and returns it as JSON data.

Now let's update our index.js file with the following:

1//import useEffect
2import { useState, useEffect } from "react";
3import styles from "../styles/Home.module.css";
4import axios from "axios";
5
6export default function Home() {
7 const [image, setImage] = useState("");
8 const [cldImages, setCldImages] = useState([]);
9
10 useEffect(() => {
11 getAllImages();
12 }, []);
13
14 async function getAllImages() {
15 try {
16 const images = await axios.get("/api/getImages");
17 setCldImages(images.data);
18 } catch (error) {
19 console.log(error);
20 }
21 }
22
23 const handleInputOnChange = (event) => {
24 //...
25 };
26 const handleOnFormSubmit = async (event) => {
27 //...
28 };
29
30 return (
31 <div className={styles.container}>
32 //...
33 {/* Add this */}
34 <main>
35 <div className={styles.images}>
36 {cldImages
37 ? cldImages.map((img) => (
38 <div key={img.public_id}>
39 <img src={img.secure_url}></img>
40 </div>
41 ))
42 : "Loading..."}
43 </div>
44 </main>
45 </div>
46 );
47}

We defined a new state variable, cldImages, to hold an array of images fetched from Cloudinary. Then we called a getAllImages function in the useEffect hook which initiates an axios call to the getImages serverless endpoint and updates the cldImages state with the returned value. Finally, we display a grid of the images.

Fetching Generated Image Tags

So far, we've been able to upload images, generate tags automatically, and retrieve images from our Cloudinary account. Let's use Cloudinary's tags API to get a list of all the tags associated with a certain resource type. In your /pages/api folder, create a file called getImageTag.js and add the following to it:

1const cloudinary = require("cloudinary").v2;
2cloudinary.config({
3 cloud_name: process.env.CLOUD_NAME,
4 api_key: process.env.API_KEY,
5 api_secret: process.env.API_SECRET,
6 secure: true,
7});
8export default async function handler(req, res) {
9 try {
10 const imageTags = await cloudinary.api.tags({
11 max_results: 50,
12 });
13 res.status(200).json(imageTags);
14 } catch (error) {
15 console.log(error);
16 res.json({ message: "an error occurred" });
17 }
18}

We configured Cloudinary and called the tags API to get a list of tags from the imageTags endpoint. The API takes an optional parameter — max_results which defines the maximum number of tags to return, and we specified 50.

To render the list of tags retrieved from Cloudinary, update your index.js file with the following:

1import { useState, useEffect } from "react";
2import styles from "../styles/Home.module.css";
3import axios from "axios";
4
5export default function Home() {
6 const [image, setImage] = useState("");
7 const [cldImages, setCldImages] = useState([]);
8 // Add this
9 const [imageTags, setImageTags] = useState([]);
10
11 useEffect(() => {
12 // Add this
13 async function getCldImageTags() {
14 try {
15 const cldTags = await axios.get("/api/getImageTags");
16 setImageTags(cldTags.data.tags);
17 } catch (error) {
18 console.log("tags" + error);
19 }
20 }
21 getCldImageTags()
22 getAllImages();
23 }, []);
24
25 async function getAllImages() {
26 //...
27 }
28 const handleInputOnChange = (event) => {
29 //...
30 };
31 const handleOnFormSubmit = async (event) => {
32 //...
33 };
34 return (
35 <div className={styles.container}>
36 //...
37 <main>
38 {/* Add this */}
39 <div className={styles.tags}>
40 {imageTags
41 ? imageTags.map((tag) => <button key={tag}>{tag}</button>)
42 : "upload Image"}
43 </div>
44 <div className={styles.images}>//...</div>
45 </main>
46 </div>
47 );
48}

We added a state variable called imageTags to hold an array of all retrieved tags. Then we defined a function, getCldImageTags, to initiate an axios call to the getImageTags endpoint and store the retrieved tags in the state.

Next, we iterate through the image tags and render a button for each.

Retrieve Images by Tag

Finally, we want to click on a tag and retrieve all images associated with that particular tag. Cloudinary provides an api.resources_by_tag method that handles fetching image resources based on a given tag.

The method takes a required parameter, tag, which defines the category of resources to be returned and some optional parameters. To implement this, create a file called getImagesByTag.js in your /pages/api folder and add the following to it:

1const cloudinary = require("cloudinary").v2;
2cloudinary.config({
3 cloud_name: process.env.CLOUD_NAME,
4 api_key: process.env.API_KEY,
5 api_secret: process.env.API_SECRET,
6 secure: true,
7});
8export default async function handler(req, res) {
9 try {
10 const tag = JSON.parse(req.body.tag).tag;
11 const resources = await cloudinary.api.resources_by_tag(tag);
12 res.status(200).json(resources);
13 } catch (error) {
14 console.log(error);
15 res.json({ message: "an error occured" });
16 }
17}

This file's setup is identical to the ones we've defined earlier, but we now expect a tag value obtained from the request body, which is passed to the api.resources_by_tag method to retrieve the image resources based on that tag.

Finally, let's update our index.js file with the following:

1import { useState, useEffect } from "react";
2import styles from "../styles/Home.module.css";
3import axios from "axios";
4
5export default function Home() {
6 const [image, setImage] = useState("");
7 const [cldImages, setCldImages] = useState([]);
8 const [imageTags, setImageTags] = useState([]);
9
10 useEffect(() => {
11 //...
12 getCldImageTags()
13 getAllImages();
14 }, []);
15
16 async function getAllImages() {
17 //...
18 }
19
20 const handleInputOnChange = (event) => {
21 //...
22 };
23 const handleOnFormSubmit = async (event) => {
24 //...
25 };
26
27 // Add this
28 const handleTagClick = async (tag) => {
29 try {
30 const resImages = await axios.post("/api/getImagesByTag", {
31 tag: JSON.stringify({
32 tag: tag,
33 }),
34 });
35 setCldImages(resImages.data.resources);
36 } catch (error) {
37 console.log("tagClick" + error);
38 }
39 };
40
41 return (
42 <div className={styles.container}>
43 <form onSubmit={handleOnFormSubmit}>
44 <input type="file" onChange={handleInputOnChange}></input>
45 <button disabled={image ? false : true}>Upload to Cloudinary</button>
46 </form>
47 <main>
48 <div className={styles.tags}>
49 {/* Add onClick event listener to the button */}
50 {imageTags
51 ? imageTags.map((tag) => (
52 <button key={tag} onClick={() => handleTagClick(tag)}>
53 {tag}
54 </button>
55 ))
56 : "upload Image"}
57 </div>
58 <div className={styles.images}>
59 {cldImages
60 ? cldImages.map((img) => (
61 <div key={img.public_id}>
62 <img src={img.secure_url} alt={img.public_id}></img>
63 </div>
64 ))
65 : "Loading..."}
66 </div>
67 </main>
68 </div>
69 );
70}

In the code above, we added an onClick event listener to the button element that triggers the handleTagClick function when any of the buttons get clicked. The handleTagClick function makes a call to the /api/getImagesByTag endpoint while attaching the tag as payload. The response images get saved in the cldImages to re-render a new set of images based on the clicked tag.

Find the complete project here on GitHub.

Conclusion

Cloudinary provides a comprehensive range of image processing and analysis tools with the ability to extract semantic data from uploaded images. In this article, we looked at how to implement Cloudinary's Google auto-tagging add-on in a simple application. We also looked at how to retrieve images by tags.

Resources You May Find Helpful

Ifeoma Imoh

Software Developer

Ifeoma is a software developer and technical content creator in love with all things JavaScript.