Cloudinary Duplicate Image Detection (ALPHA)

Important
Cloudinary Duplicate Image Detection is currently in the ALPHA phase of development. Some parameter names and other implementation details will change before the official release. If you have a paid account, you can request to take part in the Alpha trial. We invite you to share any feedback via our support team.

Cloudinary is a cloud-based service that provides solutions for image and video management, including server or client-side upload, on-the-fly image and video transformations, quick CDN delivery, and a variety of asset management options.

The Cloudinary Duplicate Image Detection add-on can be invoked either on image upload, or on images already stored in your Cloudinary account, to determine if duplicate images exist in your media library. The add-on uses hashing algorithms to provide 'fingerprints' for selected images. A configurable threshold determines how close a fingerprint has to be to produce a match. Therefore, images do not need to be identical - for example, they can differ subtly in compression, resolution, contrast or brightness and still be close enough to be termed a duplicate. The add-on uses the moderation flow, so you can manually override any decisions made about the image.

Specifying which images are included in the search

Start by telling Cloudinary which of the images stored in your Cloudinary account you want to be included in the duplicate detection search. For each of these images use the explicit API method, with the moderation parameter set to duplicate:0, for example:

Ruby (cloudinary 1.x):
Copy to clipboard
Cloudinary::Uploader.explicit("sample", :type => "upload",
  :moderation => "duplicate:0")
PHP (cloudinary_php 2.x):
Copy to clipboard
$cloudinary->uploadApi()->explicit("sample", ["type" => "upload", 
  "moderation" => "duplicate:0"]);
PHP (cloudinary_php 1.x (legacy)):
Copy to clipboard
\Cloudinary\Uploader::explicit("sample", ["type" => "upload", 
  "moderation" => "duplicate:0"]);
Python (cloudinary 1.x):
Copy to clipboard
cloudinary.uploader.explicit("sample", type = "upload",
  moderation = "duplicate:0")
Node.js (cloudinary 1.x):
Copy to clipboard
cloudinary.v2.uploader.explicit("sample", 
  { type: "upload", moderation: "duplicate:0" },
  function(error, result) {console.log(result, error) });
Java (cloudinary 1.x):
Copy to clipboard
cloudinary.uploader().explicit("sample", ObjectUtils.asMap(
  "type", "upload",
  "moderation", "duplicate:0"));
.NET (CloudinaryDotNet 1.x):
Copy to clipboard
var explicitParams = new ExplicitParams("sample") 
{
  Type = "upload",
  Moderation = "duplicate:0"
};
var explicitResult = cloudinary.Explicit(explicitParams);
iOS (cloudinary 3.x):
Copy to clipboard
let params = CLDUploadRequestParams().setType("upload").setModeration("duplicate:0")
let request = cloudinary.createUploader().explicit(
  url: "sample", params: params)
Android (cloudinary-android 1.x):
Copy to clipboard
MediaManager.get().explicit("sample")
  .option("type", "upload")
  .option("moderation", "duplicate:0").dispatch();
cli:
Copy to clipboard
cld uploader explicit sample type=upload moderation=duplicate:0

When uploading subsequent images to your account, you can add these to the set of images that are searched, in a similar way, using the upload method:

Ruby (cloudinary 1.x):
Copy to clipboard
Cloudinary::Uploader.upload("new_pic.jpg", 
  :moderation => "duplicate:0")
PHP (cloudinary_php 2.x):
Copy to clipboard
$cloudinary->uploadApi()->upload("new_pic.jpg",  
  ["moderation" => "duplicate:0"]);
PHP (cloudinary_php 1.x (legacy)):
Copy to clipboard
\Cloudinary\Uploader::upload("new_pic.jpg",  
  ["moderation" => "duplicate:0"]);
Python (cloudinary 1.x):
Copy to clipboard
cloudinary.uploader.upload("new_pic.jpg",
  moderation = "duplicate:0")
Node.js (cloudinary 1.x):
Copy to clipboard
cloudinary.v2.uploader.upload("new_pic.jpg", 
  { moderation: "duplicate:0" },
  function(error, result) {console.log(result, error) });
Java (cloudinary 1.x):
Copy to clipboard
cloudinary.uploader().upload("new_pic.jpg", ObjectUtils.asMap(
  "moderation", "duplicate:0"));
.NET (CloudinaryDotNet 1.x):
Copy to clipboard
var uploadParams = new ImageUploadParams() 
{
  File = new FileDescription(@"new_pic.jpg"),
  Moderation = "duplicate:0"
};
var uploadResult = cloudinary.Upload(uploadParams);
iOS (cloudinary 3.x):
Copy to clipboard
let params = CLDUploadRequestParams().setModeration("duplicate:0")
var mySig = MyFunction(params)  // your own function that returns a signature generated on your backend
params.setSignature(CLDSignature(signature: mySig.signature, timestamp: mySig.timestamp))
let request = cloudinary.createUploader().signedUpload(
  url: "new_pic.jpg", params: params)
Android (cloudinary-android 1.x):
Copy to clipboard
MediaManager.get().upload("new_pic.jpg")
  .option("moderation", "duplicate:0").dispatch();
cli:
Copy to clipboard
cld uploader upload new_pic.jpg moderation=duplicate:0

Additionally, all images approved by the Cloudinary Duplicate Image Detection add-on, either automatically or via a manual override, are added to the set of images to search in subsequent duplicate detection requests.

Automatic image moderation flow

The Cloudinary Duplicate Image Detection add-on uses the following moderation flow to mark images as approved or rejected based on whether duplicate images are detected in the account:

  1. Image upload
    1. Upload an image to Cloudinary, requesting duplicate detection and specifying a confidence threshold.
    2. The uploaded image is set to a 'pending' status, with short term CDN caching.
  2. Image moderation
    1. The uploaded image is sent to the Duplicate Image Detection algorithm for asynchronous analysis in the background.
    2. The image is either approved or rejected by the add-on, based on whether the confidence score is below or above the threshold.
    3. An optional notification callback is sent to your webhook with the image moderation result.
    4. If the image is approved, i.e. no duplicate images are detected, its cache settings are modified to be long-term.
    5. If the image is rejected, i.e. duplicate or near-duplicate images are found in your account, the image does not appear in your media library, but is backed up, consuming storage, so that it can be restored if necessary.
  3. Manual override
    1. Pending, approved and rejected images can be listed programmatically using Cloudinary's API or interactively using our online Media Library web interface.
    2. You can manually override the automatic moderation using the API or Media Library.

Detecting duplicate images

To activate duplicate detection when uploading an image, set the moderation parameter in the upload method to duplicate:<threshold>, where threshold is a float greater than 0 and less than or equal to 1.0, and specifies how similar an image needs to be in order to be considered a duplicate. A value of 1.0 means the image is an exact duplicate, whereas lower levels indicate subtle differences between images. For example, to detect images that are almost identical to new_pic.jpg, where the threshold for a positive detection is 0.8:

Ruby (cloudinary 1.x):
Copy to clipboard
Cloudinary::Uploader.upload("new_pic.jpg", 
  :moderation => "duplicate:0.8")
PHP (cloudinary_php 2.x):
Copy to clipboard
$cloudinary->uploadApi()->upload("new_pic.jpg",  
  ["moderation" => "duplicate:0.8"]);
PHP (cloudinary_php 1.x (legacy)):
Copy to clipboard
\Cloudinary\Uploader::upload("new_pic.jpg",  
  ["moderation" => "duplicate:0.8"]);
Python (cloudinary 1.x):
Copy to clipboard
cloudinary.uploader.upload("new_pic.jpg",
  moderation = "duplicate:0.8")
Node.js (cloudinary 1.x):
Copy to clipboard
cloudinary.v2.uploader.upload("new_pic.jpg", 
  { moderation: "duplicate:0.8" },
  function(error, result) {console.log(result, error) });
Java (cloudinary 1.x):
Copy to clipboard
cloudinary.uploader().upload("new_pic.jpg", ObjectUtils.asMap(
  "moderation", "duplicate:0.8"));
.NET (CloudinaryDotNet 1.x):
Copy to clipboard
var uploadParams = new ImageUploadParams() 
{
  File = new FileDescription(@"new_pic.jpg"),
  Moderation = "duplicate:0.8"
};
var uploadResult = cloudinary.Upload(uploadParams);
iOS (cloudinary 3.x):
Copy to clipboard
let params = CLDUploadRequestParams().setModeration("duplicate:0.8")
var mySig = MyFunction(params)  // your own function that returns a signature generated on your backend
params.setSignature(CLDSignature(signature: mySig.signature, timestamp: mySig.timestamp))
let request = cloudinary.createUploader().signedUpload(
  url: "new_pic.jpg", params: params)
Android (cloudinary-android 1.x):
Copy to clipboard
MediaManager.get().upload("new_pic.jpg")
  .option("moderation", "duplicate:0.8").dispatch();
cli:
Copy to clipboard
cld uploader upload new_pic.jpg moderation=duplicate:0.8

Tip
You can use upload presets to centrally define a set of upload options including add-on operations to apply, instead of specifying them in each upload call. You can define multiple upload presets, and apply different presets in different upload scenarios. You can create new upload presets in the Upload page of the Management Console settings or using the upload_presets Admin API method. From the Upload page of the console settings, you can also select default upload presets to use for image, video, and raw API uploads (respectively) as well as default presets for image, video, and raw uploads performed via the Media Library UI.

Learn more: Upload presets

The uploaded image is available for delivery based on the randomly assigned public ID with short-term caching of 10 minutes. Image analysis by the Duplicate Image Detection add-on is performed asynchronously and should be completed within up to a few minutes.

The following snippet shows the response of the upload API call that signifies that the duplicate detection is in the pending status.

Copy to clipboard
{
  "asset_id": "746c2613eeb861e91badb90afacf948b",
  "public_id": "iswyvngmi8avzvomdpfc",
  "version": 1393751993,
  ...
  "url": "http:/res.cloudinary.com/demo/image/upload/v1393751993/iswyvngmi8avzvomdpfc.jpg",
  ...
  "moderation": [
    {
      "status": "pending",
      "kind": "duplicate"
    }
  ],
  ...
}

If you want to apply duplicate detection to an already uploaded image, you can use the explicit method in a similar way:

Ruby (cloudinary 1.x):
Copy to clipboard
Cloudinary::Uploader.explicit("old_pic", :type => "upload",
  :moderation => "duplicate:0.8")
PHP (cloudinary_php 2.x):
Copy to clipboard
$cloudinary->uploadApi()->explicit("old_pic.jpg", ["type" => "upload", 
  "moderation" => "duplicate:0.8"]);
PHP (cloudinary_php 1.x (legacy)):
Copy to clipboard
\Cloudinary\Uploader::explicit("old_pic", ["type" => "upload", 
  "moderation" => "duplicate:0.8"]);
Python (cloudinary 1.x):
Copy to clipboard
cloudinary.uploader.explicit("old_pic", type = "upload",
  moderation = "duplicate:0.8")
Node.js (cloudinary 1.x):
Copy to clipboard
cloudinary.v2.uploader.explicit("old_pic", 
  { type: "upload", moderation: "duplicate:0.8" },
  function(error, result) {console.log(result, error) });
Java (cloudinary 1.x):
Copy to clipboard
cloudinary.uploader().explicit("old_pic", ObjectUtils.asMap(
  "type", "upload",
  "moderation", "duplicate:0.8"));
.NET (CloudinaryDotNet 1.x):
Copy to clipboard
var explicitParams = new ExplicitParams("old_pic") 
{
  Type = "upload",
  Moderation = "duplicate:0.8"
};
var explicitResult = cloudinary.Explicit(explicitParams);
iOS (cloudinary 3.x):
Copy to clipboard
let params = CLDUploadRequestParams().setType("upload").setModeration("duplicate:0.8")
let request = cloudinary.createUploader().explicit(
  url: "old_pic", params: params)
Android (cloudinary-android 1.x):
Copy to clipboard
MediaManager.get().explicit("old_pic")
  .option("type", "upload")
  .option("moderation", "duplicate:0.8").dispatch();
cli:
Copy to clipboard
cld uploader explicit old_pic type=upload moderation=duplicate:0.8

Status notification

Due to the fact that the Cloudinary Duplicate Image Detection add-on analyzes images asynchronously, you might want to get notified when the analysis is complete.

When calling the upload API with duplicate image detection, you can request a notification by setting the notification_url parameter to a webhook. Cloudinary sends a POST request to the specified endpoint when the analysis is complete.

Ruby (cloudinary 1.x):
Copy to clipboard
Cloudinary::Uploader.upload("local_file.jpg", 
  :moderation => "duplicate:0.8",
  :notification_url => "https://mysite.example.com/hooks")
PHP (cloudinary_php 2.x):
Copy to clipboard
$cloudinary->uploadApi()->upload("local_file.jpg", 
  ["moderation" => "duplicate:0.8",
  "notification_url" => "https://mysite.example.com/hooks"]);
PHP (cloudinary_php 1.x (legacy)):
Copy to clipboard
\Cloudinary\Uploader::upload("local_file.jpg", 
  ["moderation" => "duplicate:0.8",
  "notification_url" => "https://mysite.example.com/hooks"]);
Python (cloudinary 1.x):
Copy to clipboard
cloudinary.uploader.upload("local_file.jpg",
  moderation = "duplicate:0.8",
  notification_url = "https://mysite.example.com/hooks")
Node.js (cloudinary 1.x):
Copy to clipboard
cloudinary.v2.uploader.upload("local_file.jpg", 
  { moderation: "duplicate:0.8",
    notification_url: "https://mysite.example.com/hooks" },
  function(error, result){console.log(result);});
Java (cloudinary 1.x):
Copy to clipboard
cloudinary.uploader().upload("local_file.jpg", 
  ObjectUtils.asMap(
    "moderation", "duplicate:0.8",
    "notification_url", "https://mysite.example.com/hooks"));
.NET (CloudinaryDotNet 1.x):
Copy to clipboard
var uploadParams = new ImageUploadParams(){
  File = new FileDescription(@"local_file.jpg"),
  Moderation = "duplicate:0.8",
  NotificationUrl = "https://mysite.example.com/hooks"};
var uploadResult = cloudinary.Upload(uploadParams);
iOS (cloudinary 3.x):
Copy to clipboard
let params = CLDUploadRequestParams()
  .setModeration("duplicate:0.8")
  .setNotificationUrl("https://mysite.example.com/hooks")
var mySig = MyFunction(params)  // your own function that returns a signature generated on your backend
params.setSignature(CLDSignature(signature: mySig.signature, timestamp: mySig.timestamp))
let request = cloudinary.createUploader().signedUpload(
  url: "local_file.jpg", params: params)
Android (cloudinary-android 1.x):
Copy to clipboard
MediaManager.get().upload("local_file.jpg")
  .option("moderation", "duplicate:0.8")
  .option("notification_url", "https://mysite.example.com/hooks").dispatch();
cli:
Copy to clipboard
cld uploader upload local_file.jpg moderation=duplicate:0.8 notification_url="https://mysite.example.com/hooks"

The following JSON snippet is an example of a POST request sent to the notification URL when moderation is completed. The moderation_status value in this case can be either approved or rejected:

Copy to clipboard
{
  "moderation_status": "approved",
  "moderation_kind": "duplicate",
  "moderation_updated_at": "2014-03-02T09:33:43Z",
  "asset_id": "665cda4fc6f1c78086cb18eda5d6610f",
  "public_id": "l8yvniicjhmyxuuqqyqy",
  "uploaded_at": "2014-03-02T09:33:42Z",
  "version": 1571216958,
  "url": 
    "http://res.cloudinary.com/demo/image/upload/v1571216958/l8yvniicjhmyxuuqqyqy.jpg",
  "secure_url": 
    "https://res.cloudinary.com/demo/image/upload/v1571216958/l8yvniicjhmyxuuqqyqy.jpg",
  "etag": "83340520d28b704ca4f4b019effb33dc",
  "notification_type": "moderation" 
}

If the image is rejected, the response includes the public IDs of all images that scored higher than the threshold. In this case, one identical image was found, and one that differed very slightly, in brightness.

Copy to clipboard
{
  "moderation_response": [
    {
      "public_id": "docs/c8hje3gtaugkoglho1kp",
      "confidence": 1
    },
    {
      "public_id": "docs/fgn4r31gk9aoqjoftnpp",
      "confidence": 0.98
    }
  ],
  "moderation_status": "rejected",
  "moderation_kind": "duplicate",
  "moderation_updated_at": "2021-08-12T15:55:02Z",
  "asset_id": "25d356819147eb6d77865808305d2acd",
  "public_id": "s8txmefx8feoysdxdccz",
  "uploaded_at": "2021-08-12T15:55:01Z",
  "version": 1628784848,
  "url": "http://res.cloudinary.com/demo/image/upload/v1628784848/d9ghf2hsbihdoepeo4sz.jpg",
  "secure_url": "https://res.cloudinary.com/demo/image/upload/v1628784848/d9ghf2hsbihdoepeo4sz.jpg",
  "etag": null,
  "notification_type": "moderation"
}
Original image to upload Identical image
Confidence: 1
Happy couple Near-duplicate image
Confidence: 0.98

Image moderation queue

Cloudinary's Admin API can be used to list all moderated images. You can list all approved, pending or rejected images by specifying the value of the status parameter of the resources_by_moderation API method. For example to list all rejected images:

Ruby (cloudinary 1.x):
Copy to clipboard
Cloudinary::Api.resources_by_moderation("duplicate", "rejected")
PHP (cloudinary_php 2.x):
Copy to clipboard
$api->assetsByModeration("duplicate", "rejected");
PHP (cloudinary_php 1.x (legacy)):
Copy to clipboard
$api->resources_by_moderation("duplicate", "rejected");
Python (cloudinary 1.x):
Copy to clipboard
cloudinary.api.resources_by_moderation("duplicate", "rejected")
Node.js (cloudinary 1.x):
Copy to clipboard
cloudinary.v2.api.resources_by_moderation('duplicate', 'rejected',
  function(error, result) {console.log(result, error); });
Java (cloudinary 1.x):
Copy to clipboard
cloudinary.api().resourcesByModeration("duplicate", "rejected",
  ObjectUtils.emptyMap());
.NET (CloudinaryDotNet 1.x):
Copy to clipboard
cloudinary.ListResourcesByModerationStatus("duplicate", "rejected");
curl:
Copy to clipboard
curl https://<API_KEY>:<API_SECRET>@api.cloudinary.com/v1_1/<cloud_name>/resources/image/moderations/duplicate/rejected
cli:
Copy to clipboard
cld admin resources_by_moderation duplicate rejected

Example response:

Copy to clipboard
{
 "resources": 
  [{
    "asset_id": "9e5790c0d8f342bf8a927b7355160a2f",
    "public_id": "q7vcvrfjm9mj4bfp3qc8",
    "format": "jpg",
    "version": 1570976812,
    "resource_type": "image",
    "type": "upload",
    "created_at": "2017-05-04T41:02:23Z",
    "bytes": 120253,
    "width": 864,
    "height": 576,
    "backup": true,
    "access_mode": "public",
    "url": "http://res.cloudinary.com/demo/image/upload/v1570976812/q7vcvrfjm9mj4bfp3qc8.jpg",
    "secure_url": "https://res.cloudinary.com/demo/image/upload/v1570976812/q7vcvrfjm9mj4bfp3qc8.jpg"
  },
  {
    "asset_id": "f914ee03fae69c94ceccc0d18c90a310",
    "public_id": "zp4fgdbabhlwwa7bxu84",
    "format": "jpg",
    ...
  }
  ...
 ]
}

Manual override

While the automatic image analysis of the Cloudinary Duplicate Image Detection add-on is very accurate, in some cases you may want to manually override the moderation decision. You can either approve a previously rejected image or reject an approved one.

One way to manually override the moderation result is using Cloudinary's Media Library web interface. From the left navigation menu, select Moderation. Then, from the drop-down list of moderation types in the top menu, select Duplicate and then select which moderation queue of images to display (Pending, Rejected, or Approved).

  • When listing the queue of images rejected by the add-on, you can click on the thumbs up Approve button to revert the decision and recover the original rejected image.
  • When listing the queue of images approved by the add-on, you can click on the thumbs down Reject button to revert the decision and prevent a certain image from being publicly available to your users.

Alternatively, you can use Cloudinary's Admin API to manually override the moderation result. The following sample code uses the update API method while specifying a public ID of a moderated image and setting the moderation_status parameter to the approved status.

Ruby (cloudinary 1.x):
Copy to clipboard
Cloudinary::Api.update("hwepb67oxzh4lrigssld", 
  :moderation_status => "approved")
PHP (cloudinary_php 2.x):
Copy to clipboard
$api->update("hwepb67oxzh4lrigssld", 
  ["moderation_status" => "approved"]);
PHP (cloudinary_php 1.x (legacy)):
Copy to clipboard
$api->update("hwepb67oxzh4lrigssld", 
  ["moderation_status" => "approved"]);
Python (cloudinary 1.x):
Copy to clipboard
cloudinary.api.update("hwepb67oxzh4lrigssld",
  moderation_status = "approved")
Node.js (cloudinary 1.x):
Copy to clipboard
cloudinary.v2.api.update("hwepb67oxzh4lrigssld", 
  { moderation_status: "approved" },
  function(error, result){console.log(result);});
Java (cloudinary 1.x):
Copy to clipboard
cloudinary.api().update("hwepb67oxzh4lrigssld", 
  ObjectUtils.asMap("moderation_status", "approved"));
.NET (CloudinaryDotNet 1.x):
Copy to clipboard
var updateParams = new UpdateParams("hwepb67oxzh4lrigssld"){
  ModerationStatus = "approved"};
var updateResult = cloudinary.Update(updateParams);
cli:
Copy to clipboard
cld admin update "hwepb67oxzh4lrigssld" moderation_status="approved"

✔️ Feedback sent!

Rate this page: