Cloudinary Blog

How to use OCR Text Recognition to automatically manipulate images

How to use OCR Text Recognition to automatically manipulate images

Websites of all kinds enhance user experience with images. In fact, images appear on almost every Web page. Some of the images are uploaded by users, some are proprietary, and some come from 3rd parties. Regardless of origin, many of these images include text elements, and sometimes you need to be aware of or handle that text.

For example, you might need to:

  • Blur or pixelate texts that you don't want displayed on your website
  • Cover text in an uploaded image with another image
  • Have an automatic way to extract the text content so you can programmatically analyze it or perform operations based on the detected text. For example, you might want to make sure uploaded images do not contain too much text, or maybe you want to tag your images based on keywords detected in them.

Webinar
Marketing Without Barriers Through Dynamic Asset Management

These are common needs, but it's a hassle to do these things manually, even for your own proprietary images, and not an option for images that are uploaded by your users for immediate display.

The good news: This article will show you how you can handle all these and other text detection scenarios, on-the-fly, with only one or a few lines of code. Here are a couple examples:

An outline overlay shows automatically detected text An outline overlay shows
automatically detected text
The pixelate effect hides detected text The pixelate effect hides
detected text

Here's the code that builds the delivery URL for the right-hand (pixelated) image above:

Ruby:
Copy to clipboard
cl_image_tag("highway_sign.jpg", :transformation=>[
  {:width=>1300, :height=>900, :gravity=>"south_east", :crop=>"crop"},
  {:effect=>"pixelate_region", :gravity=>"ocr_text"}
  ])
PHP:
Copy to clipboard
cl_image_tag("highway_sign.jpg", array("transformation"=>array(
  array("width"=>1300, "height"=>900, "gravity"=>"south_east", "crop"=>"crop"),
  array("effect"=>"pixelate_region", "gravity"=>"ocr_text")
  )))
Python:
Copy to clipboard
CloudinaryImage("highway_sign.jpg").image(transformation=[
  {'width': 1300, 'height': 900, 'gravity': "south_east", 'crop': "crop"},
  {'effect': "pixelate_region", 'gravity': "ocr_text"}
  ])
Node.js:
Copy to clipboard
cloudinary.image("highway_sign.jpg", {transformation: [
  {width: 1300, height: 900, gravity: "south_east", crop: "crop"},
  {effect: "pixelate_region", gravity: "ocr_text"}
  ]})
Java:
Copy to clipboard
cloudinary.url().transformation(new Transformation()
  .width(1300).height(900).gravity("south_east").crop("crop").chain()
  .effect("pixelate_region").gravity("ocr_text")).imageTag("highway_sign.jpg");
JS:
Copy to clipboard
cloudinary.imageTag('highway_sign.jpg', {transformation: [
  {width: 1300, height: 900, gravity: "south_east", crop: "crop"},
  {effect: "pixelate_region", gravity: "ocr_text"}
  ]}).toHtml();
jQuery:
Copy to clipboard
$.cloudinary.image("highway_sign.jpg", {transformation: [
  {width: 1300, height: 900, gravity: "south_east", crop: "crop"},
  {effect: "pixelate_region", gravity: "ocr_text"}
  ]})
React:
Copy to clipboard
<Image publicId="highway_sign.jpg" >
  <Transformation width="1300" height="900" gravity="south_east" crop="crop" />
  <Transformation effect="pixelate_region" gravity="ocr_text" />
</Image>
Vue.js:
Copy to clipboard
<cld-image publicId="highway_sign.jpg" >
  <cld-transformation width="1300" height="900" gravity="south_east" crop="crop" />
  <cld-transformation effect="pixelate_region" gravity="ocr_text" />
</cld-image>
Angular:
Copy to clipboard
<cl-image public-id="highway_sign.jpg" >
  <cl-transformation width="1300" height="900" gravity="south_east" crop="crop">
  </cl-transformation>
  <cl-transformation effect="pixelate_region" gravity="ocr_text">
  </cl-transformation>
</cl-image>
.Net:
Copy to clipboard
cloudinary.Api.UrlImgUp.Transform(new Transformation()
  .Width(1300).Height(900).Gravity("south_east").Crop("crop").Chain()
  .Effect("pixelate_region").Gravity("ocr_text")).BuildImageTag("highway_sign.jpg")
Android:
Copy to clipboard
MediaManager.get().url().transformation(new Transformation()
  .width(1300).height(900).gravity("south_east").crop("crop").chain()
  .effect("pixelate_region").gravity("ocr_text")).generate("highway_sign.jpg");
iOS:
Copy to clipboard
imageView.cldSetImage(cloudinary.createUrl().setTransformation(CLDTransformation()
  .setWidth(1300).setHeight(900).setGravity("south_east").setCrop("crop").chain()
  .setEffect("pixelate_region").setGravity("ocr_text")).generate("highway_sign.jpg")!, cloudinary: cloudinary)

Stay tuned to learn how you can apply these same capabilities to your own site or app.

A winning combination: leading OCR technology + dynamic image transformation functionality

Extracting text from images programmatically is a technology that has existed at some level for many years and is usually referred to as OCR (Optical Character Recognition).

In recent years, advanced systems have been developed that are capable of producing a high degree of recognition accuracy for most fonts and languages. Although no system is 100% accurate, the better ones are getting close.

Text Detection and Extraction add-on At Cloudinary, our mission is to offer a comprehensive solution for all elements of image and media management, enabling web and app developers to invest their full focus on the main purpose of their own site, or app. That’s why we decided to offer our new OCR Text Detection and Extraction add-on, which streamlines our extensive image manipulation capabilities with one of the most advanced and precise OCR text extraction engines: Google’s Cloud Vision.

Some real use cases

Using image overlays to cover unwanted text

Suppose your website helps people find their next dream car. It’s a free service for the buyers of course. It’s also fair for the sellers who list all their cars for free, and pay a commission only if the car sells through your site. But some dealers forget to follow the website policy, and they list their direct phone number on the image. A problem? Not with the new OCR Text Recognition and Extraction add-on. Take a look at how easy it is to cover any embedded text in an uploaded image using a simple OCR transformation.

For example, the dynamic manipulation URL (and corresponding SDK code) shown below performs OCR detection and adds an an image as an overlay on top of any detected text. Everything is done on-the-fly in the cloud by simply adding 2 parameters to the code that builds the URL:

  • Set the overlay parameter to the quikcar_logo image (l_quikcar_logo in the URL)
  • Set the gravity (location for the overlay) to ocr_text (g_ocr_text in the URL)

Ruby:
Copy to clipboard
cl_image_tag("jeepsale.jpg", :overlay=>"quikcar_logo", :flags=>"region_relative", :width=>1.4, :gravity=>"ocr_text")
PHP:
Copy to clipboard
cl_image_tag("jeepsale.jpg", array("overlay"=>"quikcar_logo", "flags"=>"region_relative", "width"=>1.4, "gravity"=>"ocr_text"))
Python:
Copy to clipboard
CloudinaryImage("jeepsale.jpg").image(overlay="quikcar_logo", flags="region_relative", width=1.4, gravity="ocr_text")
Node.js:
Copy to clipboard
cloudinary.image("jeepsale.jpg", {overlay: "quikcar_logo", flags: "region_relative", width: "1.4", gravity: "ocr_text"})
Java:
Copy to clipboard
cloudinary.url().transformation(new Transformation().overlay(new Layer().publicId("quikcar_logo")).flags("region_relative").width(1.4).gravity("ocr_text")).imageTag("jeepsale.jpg");
JS:
Copy to clipboard
cloudinary.imageTag('jeepsale.jpg', {overlay: new cloudinary.Layer().publicId("quikcar_logo"), flags: "region_relative", width: "1.4", gravity: "ocr_text"}).toHtml();
jQuery:
Copy to clipboard
$.cloudinary.image("jeepsale.jpg", {overlay: new cloudinary.Layer().publicId("quikcar_logo"), flags: "region_relative", width: "1.4", gravity: "ocr_text"})
React:
Copy to clipboard
<Image publicId="jeepsale.jpg" >
  <Transformation overlay="quikcar_logo" flags="region_relative" width="1.4" gravity="ocr_text" />
</Image>
Vue.js:
Copy to clipboard
<cld-image publicId="jeepsale.jpg" >
  <cld-transformation overlay="quikcar_logo" flags="region_relative" width="1.4" gravity="ocr_text" />
</cld-image>
Angular:
Copy to clipboard
<cl-image public-id="jeepsale.jpg" >
  <cl-transformation overlay="quikcar_logo" flags="region_relative" width="1.4" gravity="ocr_text">
  </cl-transformation>
</cl-image>
.Net:
Copy to clipboard
cloudinary.Api.UrlImgUp.Transform(new Transformation().Overlay(new Layer().PublicId("quikcar_logo")).Flags("region_relative").Width(1.4).Gravity("ocr_text")).BuildImageTag("jeepsale.jpg")
Android:
Copy to clipboard
MediaManager.get().url().transformation(new Transformation().overlay(new Layer().publicId("quikcar_logo")).flags("region_relative").width(1.4).gravity("ocr_text")).generate("jeepsale.jpg");
iOS:
Copy to clipboard
imageView.cldSetImage(cloudinary.createUrl().setTransformation(CLDTransformation().setOverlay("quikcar_logo").setFlags("region_relative").setWidth(1.4).setGravity("ocr_text")).generate("jeepsale.jpg")!, cloudinary: cloudinary)

Original image with out text overlay Originally uploaded image Image that is immediately displayed on your site with your logo covering the detected text Image that is immediately displayed on your site with logo overlay

Blurring out a brand name

You maintain a blog where you and other users post regularly. To enhance engagement, you make sure to embed lots of interesting images in every article. You don’t want anybody to think that your posts are commercially biased, but these days, (almost) everything is branded. Using Cloudinary’s OCR add-on, it again takes just one line of SDK code (or a manually built URL) with a few parameters to blur out that brand name.

In this case, we take advantage of the blur_region effect at its top blurring strength (2000), and again use that ocr_text gravity so that all detected text regions are blurred:

Ruby:
Copy to clipboard
cl_image_tag("piano.jpg", :transformation=>[
  {:effect=>"blur_region:2000", :gravity=>"ocr_text"},
  {:width=>500, :crop=>"scale"}
  ])
PHP:
Copy to clipboard
cl_image_tag("piano.jpg", array("transformation"=>array(
  array("effect"=>"blur_region:2000", "gravity"=>"ocr_text"),
  array("width"=>500, "crop"=>"scale")
  )))
Python:
Copy to clipboard
CloudinaryImage("piano.jpg").image(transformation=[
  {'effect': "blur_region:2000", 'gravity': "ocr_text"},
  {'width': 500, 'crop': "scale"}
  ])
Node.js:
Copy to clipboard
cloudinary.image("piano.jpg", {transformation: [
  {effect: "blur_region:2000", gravity: "ocr_text"},
  {width: 500, crop: "scale"}
  ]})
Java:
Copy to clipboard
cloudinary.url().transformation(new Transformation()
  .effect("blur_region:2000").gravity("ocr_text").chain()
  .width(500).crop("scale")).imageTag("piano.jpg");
JS:
Copy to clipboard
cloudinary.imageTag('piano.jpg', {transformation: [
  {effect: "blur_region:2000", gravity: "ocr_text"},
  {width: 500, crop: "scale"}
  ]}).toHtml();
jQuery:
Copy to clipboard
$.cloudinary.image("piano.jpg", {transformation: [
  {effect: "blur_region:2000", gravity: "ocr_text"},
  {width: 500, crop: "scale"}
  ]})
React:
Copy to clipboard
<Image publicId="piano.jpg" >
  <Transformation effect="blur_region:2000" gravity="ocr_text" />
  <Transformation width="500" crop="scale" />
</Image>
Vue.js:
Copy to clipboard
<cld-image publicId="piano.jpg" >
  <cld-transformation effect="blur_region:2000" gravity="ocr_text" />
  <cld-transformation width="500" crop="scale" />
</cld-image>
Angular:
Copy to clipboard
<cl-image public-id="piano.jpg" >
  <cl-transformation effect="blur_region:2000" gravity="ocr_text">
  </cl-transformation>
  <cl-transformation width="500" crop="scale">
  </cl-transformation>
</cl-image>
.Net:
Copy to clipboard
cloudinary.Api.UrlImgUp.Transform(new Transformation()
  .Effect("blur_region:2000").Gravity("ocr_text").Chain()
  .Width(500).Crop("scale")).BuildImageTag("piano.jpg")
Android:
Copy to clipboard
MediaManager.get().url().transformation(new Transformation()
  .effect("blur_region:2000").gravity("ocr_text").chain()
  .width(500).crop("scale")).generate("piano.jpg");
iOS:
Copy to clipboard
imageView.cldSetImage(cloudinary.createUrl().setTransformation(CLDTransformation()
  .setEffect("blur_region:2000").setGravity("ocr_text").chain()
  .setWidth(500).setCrop("scale")).generate("piano.jpg")!, cloudinary: cloudinary)

Original, unblurred image Original, unblurred image Blurred brand name text Blurred brand name text

Advanced processing using extracted text

Say that your website is based on user generated content and your income is based on click-through rates. Your users are of-course also interested in maximizing views of their posts. It is a known and proven fact that images catch the eyes of users and increase engagement. But it's also known that images containing significant amounts of text are less engaging and may harm the overall experience. For example, Facebook limits the exposure of ads that are text-heavy.

Luckily, you can help your users to avoid uploading images with excessive text content by using the OCR add-on to analyze the percentage of an image that contains text.

When you include the ocr parameter in your upload command, the JSON response includes all of the detected text and the exact bounding boxes coordinates of each word or text element. Combining this data with some simple math, you can write some simple code to:

  • Allow images with less than 15% to be uploaded freely.
  • Provide a warning for images with 15%-30% text, recommending that they use a less text-heavy image, but still allow them to continue if they choose.
  • Reject images with more than 30% text.

Here's a look at an excerpt from an upload response showing the bounding box of an individual text element extracted from an image:

Copy to clipboard
              {
                "boundingPoly": {
                  "vertices": [
                    {
                      "y": 22,
                      "x": 760
                    },
                    {
                      "y": 22,
                      "x": 1039
                    },
                    {
                      "y": 90,
                      "x": 1039
                    },
                    {
                      "y": 90,
                      "x": 760
                    }
                  ]
                },
                "description": "Imagine"
              },
              {
                "boundingPoly": {
                  
                  
                  

And here's some simple sample code (using Ruby on Rails) that accomplishes the text percentage validation described above by calculating the space taken by each of the individual bounding boxes of all text detected in an image:

Copy to clipboard
if result['info']['ocr']['adv_ocr']['status'] == 'complete'
  data = result['info']['ocr']['adv_ocr']['data']
  boxes = data.first["textAnnotations"][1..-1].map{|poly| poly["boundingPoly"]
       ["vertices"]}.map{|vertices| vertices.values_at(0,2)}
  areas = boxes.map{|box| (box.first["x"]-box.second["x"])
       .abs * (box.first["y"]-box.second["y"]).abs}
  total_areas = areas.sum
  coverage = total_areas.to_f / (result["width"] * result["height"]) * 100

puts case
  when coverage < 15
    "Only #{coverage.round(2)}% of your image contains text. 
        This is a valid image!"
  when coverage < 30
    "#{coverage.round(2)}% of your image contains text. For better engagement, 
        it is recommended to upload an image with less text."
  else
    "We're sorry. #{coverage.round(2)}% of your image contains text. 
        Please use another image."
  end
end

If a customer uploaded the first image below, the above code would return 12.54% and thus would be allowed to continue, the second image would return ~16%, and thus would receive a warning, but the third image would return nearly 35%, and would be (politely) rejected.

extracted text found in 12.54% of image 12.54% text extracted text found in 16.36% of image 16.36% text extracted text found in 34.87% of image 34.87% text

In a word

In this article, we've demonstrated a few ways you can use the OCR Text Recognition and Extraction add-on to automatically blur, pixelate, overlay, and extract text from your images.

Want to know more? For a deeper look at the add-on's abilities and additional use-case scenarios with sample code, have a look at the add-on documentation.

Ready to give it a try? If you aren't already a Cloudinary customer, you are welcome to sign up for a free account and try the add-on along with the rest of the Cloudinary features.

Have some great ideas for how to make use of the OCR Text Detection and Extraction add-on in your site or app? We’d be happy to hear what you think and appreciate any feedback.

Recent Blog Posts

Maya Shavin: How I Built My Website

Besides working as a senior front-end developer at Cloudinary, I'm also a content creator, a blogger, and an open-source developer. Follow me at @mayashavin and on mayashavin.com.

In the beginning, my website, mayashavin.com, was mainly for showcasing the status of my development projects and keeping me organized with my speaking schedule. Initially, I built it with Vue.js, later on switching to Nuxt.js (aka Nuxt) for a higher SEO score, and deployed it with Netlify. After some time, I added a blog section with Netlify CMS as the content management system (CMS). Everything was fine until I added more content and features, which led to a significant decline in the site’s performance. Also, the site design needed a modern look. So, I gave the site a makeover.

Read more
Automation Frees Up PetRescue’s Staff to Help Pets Find Their Forever Homes

As we spend more time at home, many of us are adopting pets for the joy, companionship and a surprising range of health benefits. In Australia, where our nonprofit customer PetRescue is located, there’s a shortage of pets to adopt. Last August, the Guardian reported that dog shelters in Australia emptied and adoption fees for puppies were running as high as $AUS1800.

Read more
Cloudinary and Contentful Make Modern Content Management Easier

I am pleased to share that Cloudinary and Contentful have joined forces to further streamline the creation, processing, and delivery of online content through Cloudinary’s digital asset management (DAM) solution and advanced transformation and delivery capabilities for images and video. What’s more, the partnership delivers a headless approach to DAM. By leveraging APIs for media management tasks, marketers and developers alike benefit from an integrated stack of optimized assets for optimization and automation. As a result, page loads are fast and beautiful, and at scale—with less overhead and effort.

Read more
Introducing Cloudinary's Nuxt Module

Since its initial release in October 2016 by the Chopin brothers as a server-side framework that runs on top of Vue.js, Nuxt (aka Nuxt.js) has gained prominence in both intuitiveness and performance. The framework offers numerous built-in features based on a modular architecture, bringing ease and simplicity to web development. Not surprisingly, Nuxt.js has seen remarkable growth in adoption by the developer community along with accolades galore. At this writing, Nuxt has earned over 30K stars on GitHub and 96 active modules with over a million downloads per month. And the upward trend is ongoing.

Read more
How Quality and Quantity can go Hand in Hand

When it comes to quality versus quantity, you’ll often hear people say, “It’s the quality that counts, not the quantity”. While that’s true in many situations, there are also cases where you want both quality and quantity. You may have thousands of images on your website and you want them all to look great. This is especially important if your website allows users to upload their own content, for example, to sell their own products or services. You don't want their poor quality images to reflect badly on your brand.

Read more