Title: Watermarking images with OpenCV and Python - PyImageSearch

URL Source: https://pyimagesearch.com/2016/04/25/watermarking-images-with-opencv-and-python/

Published Time: 2016-04-25T14:00:20+00:00

Markdown Content:
![Image 1: watermark_header](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20450'%3E%3C/svg%3E)A few weeks ago, I wrote a blog post on **[creating transparent overlays with OpenCV](https://pyimagesearch.com/2016/03/07/transparent-overlays-with-opencv/)**. This post was meant to be a gentle introduction to a neat little trick you can use to improve the aesthetics of your processed image(s), such as creating a Heads-up Display (HUD) on live video streams.

But there’s another, _more practical reason_ that I wanted to introduce transparent overlays to you — _**watermarking images.**_ Watermarking an image or video is called _[digital watermarking](https://en.wikipedia.org/wiki/Digital_watermarking)_, and is the process of embedding a _unique_ and _identifying pattern_ onto the image itself.

For example, professional photographers tend to watermark digital proofs sent to clients (including relevant information such as their name and/or design studio) until the client agrees to purchase the photos, at which the original, unaltered images are released. This allows the photographer to distribute demos and samples of their work, without actually “giving away” the original compositions.

We also see digital watermarks in copyrighted video — in this case, a watermark is embedded into each frame of the video, thereby crediting the original producer of the work.

In both of these cases, the goal of watermarking is to create a unique and identifiable pattern on the image, giving attribution to the original creator, but _without destroying the contents of the image itself._

**To learn how to utilize OpenCV to watermark your own dataset of images, keep reading.**

![Image 2](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20128%20128'%3E%3C/svg%3E)

#### Looking for the source code to this post?

[Jump Right To The Downloads Section](#download-the-code)

The goal of this blog post is to demonstrate how to add watermarks to images using OpenCV and Python. To get started, we’ll need a watermark, which for the purposes of this tutorial, I’ve chosen to be the PyImageSearch logo:

![Image 3: Figure 1: Our example watermark image -- the PyImageSearch logo.](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20286%2069'%3E%3C/svg%3E)

**Figure 1:** Our example watermark image — the PyImageSearch logo.

This watermark is a [PNG image](https://en.wikipedia.org/wiki/Portable_Network_Graphics) with _**four channels**_: a Red channel, a Green channel, a Blue channel, and an _**Alpha channel**_ used to control the _transparency of each of the pixels in the image._

Values in our alpha channel can range _\[0, 255\]_, where a value of _255_ is _**100% opaque**_ (i.e., not transparent at all) while a value of _0_ is _**100% transparent**_. Values that fall between 0 and 255 have varying levels of transparency, where the smaller the alpha value, _the more transparent the pixel is._

In the above figure, all pixels that _are not_ part of the _white_ “PyImageSearch” logo are fully transparent, meaning that you can “see through them” to the background of what the image is laid on top of. In this case, I’ve set the image to have a _blue background_ so we can visualize the logo itself (obviously, you would not be able to see the white PyImageSearch logo if I placed it on a white background — hence using a blue background for this example).

Once we actually overlay the watermark on our image, the watermark will be semi-transparent, allowing us to (partially) see the background of the original image.

Now that we understand the process of watermarking, let’s go ahead and get started.

### Creating a watermark with OpenCV

Open up a new file, name it `watermark_dataset.py` , and let’s get started:

\# import the necessary packages
from imutils import paths
import numpy as np
import argparse
import cv2
import os

# construct the argument parse and parse the arguments
ap = argparse.ArgumentParser()
ap.add\_argument("-w", "--watermark", required=True,
	help="path to watermark image (assumed to be transparent PNG)")
ap.add\_argument("-i", "--input", required=True,
	help="path to the input directory of images")
ap.add\_argument("-o", "--output", required=True,
	help="path to the output directory")
ap.add\_argument("-a", "--alpha", type=float, default=0.25,
	help="alpha transparency of the overlay (smaller is more transparent)")
ap.add\_argument("-c", "--correct", type=int, default=1,
	help="flag used to handle if bug is displayed or not")
args = vars(ap.parse\_args())

**Lines 2-6** import our required Python packages. We’ll be making use of the [imutils package](https://github.com/jrosebr1/imutils) here, so if you do not already have it installed, let `pip` install it for you:

$ pip install imutils

**Lines 9-20** then handle parsing our required command line arguments. We require three command line arguments and can supply two additional (optional) ones. A full breakdown of each of the command line arguments can be found below:

*   `--watermark` : Here we supply the path to the image we wish to use as the watermark. We presume that (1) this image is a _PNG image_ with _alpha transparency_ and (2) our watermark is smaller (in terms of both width and height) then _all_ images in the dataset we are going to apply the watermark to.
*   `--input` : This is the path to our _input directory_ of images we are going to watermark.
*   `--output` : We then need to supply an _output directory_ to store our watermarked images.
*   `--alpha` : The optional `--alpha` value controls the level of transparency of the watermark. A value of _1.0_ indicates that the watermark should be _100% opaque_ (i.e., not transparent). A value of _0.0_ indicates that the watermark should be _100% transparent_. You’ll likely want to tune this value for your own datasets, but I’ve found that a value of 25% works well for most circumstances.
*   `--correct` : Finally, this switch is used to control whether or not we should preserve a “bug” in how OpenCV handles alpha transparency. The only reason I’ve included this switch is for a matter of education regarding the OpenCV library. Unless you want to investigate this bug yourself, you’ll likely be leaving this parameter alone.

Now that we have parsed our command line arguments, we can load our watermark image from disk:

\# load the watermark image, making sure we retain the 4th channel
# which contains the alpha transparency
watermark = cv2.imread(args\["watermark"\], cv2.IMREAD\_UNCHANGED)
(wH, wW) = watermark.shape\[:2\]

**Line 24** loads our `watermark` image from disk using the `cv2.imread` function. Notice how we are using the `cv2.IMREAD_UNCHANGED` flag — this value is supplied so we can read the alpha transparency channel of the PNG image (along with the standard Red, Green, and Blue channels).

**Line 25** then grabs the spatial dimensions (i.e., height and width) of the `watermark` image.

The next code block addresses some strange issues I’ve encountered when working with alpha transparency and OpenCV:

\# split the watermark into its respective Blue, Green, Red, and
# Alpha channels; then take the bitwise AND between all channels
# and the Alpha channels to construct the actaul watermark
# NOTE: I'm not sure why we have to do this, but if we don't,
# pixels are marked as opaque when they shouldn't be
if args\["correct"\] > 0:
	(B, G, R, A) = cv2.split(watermark)
	B = cv2.bitwise\_and(B, B, mask=A)
	G = cv2.bitwise\_and(G, G, mask=A)
	R = cv2.bitwise\_and(R, R, mask=A)
	watermark = cv2.merge(\[B, G, R, A\])

When I first implemented this example, I noticed some _extremely strange behavior_ on the part of `cv2.imread` and PNG filetypes with alpha transparency.

To start, I noticed that _even with_ the `cv2.IMREAD_UNCHANGED` flag, the transparency values in the alpha channel _were not respected_ by _any_ of the Red, Green, or Blue channels — these channels would appear to be either _fully opaque_ or _semi-transparent_, but never the _correct_ level of transparency that I presumed they would be.

However, upon investigating the alpha channel itself, I noticed there were no problems with the alpha channel directly — **the alpha channel was loaded and represented perfectly_._**

Therefore, to ensure that each of the Red, Green, and Blue channels respected the alpha channel, I took the bitwise `AND` between the individual color channels and the alpha channel, treating the alpha channel as a mask (**Lines 33-37**) — _**this resolved the strange behavior and allowed me to proceed with the watermarking process.**_

I’ve included the `--correct` flag here so that you can investigate what happens _when you do not_ apply this type of correction (more on in this the **“Watermarking results” section**).

Next, let’s go ahead and process our dataset of images:

\# loop over the input images
for imagePath in paths.list\_images(args\["input"\]):
	# load the input image, then add an extra dimension to the
	# image (i.e., the alpha transparency)
	image = cv2.imread(imagePath)
	(h, w) = image.shape\[:2\]
	image = np.dstack(\[image, np.ones((h, w), dtype="uint8") \* 255\])

	# construct an overlay that is the same size as the input
	# image, (using an extra dimension for the alpha transparency),
	# then add the watermark to the overlay in the bottom-right
	# corner
	overlay = np.zeros((h, w, 4), dtype="uint8")
	overlay\[h - wH - 10:h - 10, w - wW - 10:w - 10\] = watermark

	# blend the two images together using transparent overlays
	output = image.copy()
	cv2.addWeighted(overlay, args\["alpha"\], output, 1.0, 0, output)

	# write the output image to disk
	filename = imagePath\[imagePath.rfind(os.path.sep) + 1:\]
	p = os.path.sep.join((args\["output"\], filename))
	cv2.imwrite(p, output)

On **Line 40** we start looping over each of the images in our `--input` directory. For each of these images, we load it from disk and grab its width and height.

It’s important to understand that each `image` is represented as a NumPy array with shape _(h, w, 3)_, where the _3_ is the number of channels in our image — one for each of the Red, Green, and Blue channels, respectively.

However, since we are working with alpha transparency, we need to add a _4th dimension_ to the image to store the alpha values (**Line 45**). This alpha channel has the same spatial dimensions as our original image and all values in the alpha channel are set to _255_, indicating that the pixels are _fully opaque_ and _not transparent_.

**Lines 51 and 52** construct the `overlay` for our watermark. Again, the `overlay` has the exact same width and height of our input image.

_**Note:** To learn more about transparent overlays, [please refer to this blog post](https://pyimagesearch.com/2016/03/07/transparent-overlays-with-opencv/)._

Finally, **Lines 55 and 56** construct our watermarked image by applying the `cv2.addWeighted` function.

**Lines 59-61** then take our `output` image and write it to the `--output` directory.

### Watermarking results

To give our `watermark_dataset.py` script a try, download the source code and images associated with this post using the _“Downloads”_ form at the bottom of this tutorial. Then, navigate to the code directory and execute the following command:

$ python watermark\_dataset.py --watermark pyimagesearch\_watermark.png \\
	--input input --output output

After the script finishes executing, your `output` directory should contain the following five images:

![Image 4: Figure 2: The output from our watermarking script.](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20373'%3E%3C/svg%3E)

**Figure 2:** The output from our watermarking script.

You can see each of the watermarked images below:

![Image 5: Figure 3: Watermarking images with OpenCV and Python.](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20450'%3E%3C/svg%3E)

**Figure 3:** Watermarking images with OpenCV and Python.

In the above image, you can see the white PyImageSearch logo has been added as a watermark to the original image.

Below follows a second example of watermarking an image with OpeCV. Again, notice how the PyImageSearch logo appears (1) semi-transparent and (2) in the bottom-right corner of the image:

![Image 6: Figure 4: Creating watermarks with OpenCV and Python.](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20375%20500'%3E%3C/svg%3E)

**Figure 4:** Creating watermarks with OpenCV and Python.

About a year ago, I went out to Arizona to enjoy the Red Rocks. Along the way, I stopped at the Phoenix Zoo to pet and feed a giraffe:

![Image 7: Figure 5: Feed a giraffe...and watermarking the image with computer vision.](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20450'%3E%3C/svg%3E)

**Figure 5:** Feeding a giraffe…and watermarking the image with computer vision.

I’m also a _huge fan_ of mid-century modern architecture, so I had to visit [Taliesin West](http://www.franklloydwright.org/taliesin-west/plan-a-visit.html):

![Image 8: Figure 6: Watermarking images with OpenCV.](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20450'%3E%3C/svg%3E)

**Figure 6:** Watermarking images with OpenCV.

Finally, here’s a beautiful photo of the Arizona landscape (even if it was a bit cloudy that day):

![Image 9: Figure 7: Creating watermarks with OpenCV is easy!](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20450'%3E%3C/svg%3E)

Figure 7: Creating watermarks with OpenCV is easy!

Notice how in each of the above images, the “PyImageSearch” logo has been placed in the bottom-right corner of the output image. Furthermore, this watermark is _semi-transparent_, allowing us to see the contents of the background image through the foreground watermark.

### Strange behavior with alpha transparency

So, remember when I mentioned in **Lines 32-37** that some strange behavior with alpha transparency can happen if we don’t take the bitwise `AND` between each respective Red, Green, and Blue channel and the alpha channel?

**Let’s take a look at this strangeness.**

Execute the `watermark_dataset.py` script again, this time supplying the `--correct 0` flag to skip the bitwise `AND` step:

$ python watermark\_dataset.py --correct 0 --watermark pyimagesearch\_watermark.png \\
	--input input --output output

Then, opening an output image of your choosing, you’ll see something like this:

![Image 10: Figure 8: (Incorrectly) creating a watermark with OpenCV.](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20450'%3E%3C/svg%3E)

**Figure 8:** (Incorrectly) creating a watermark with OpenCV.

Notice how the _entire watermark image_ is treated as being semi-transparent _instead of only the corresponding alpha pixel values!_

Baffling, right?

I’m honestly not sure why this happens and I couldn’t find any information on the behavior in the OpenCV documentation. If anyone has any extra details on this issue, please leave a note in the comments section at the bottom of this post.

Otherwise, if you utilize alpha transparency in any of your own OpenCV image processing pipelines, make sure you take special care to mask each Red, Green, and Blue channels individually using your alpha mask.

### What's next? We recommend [PyImageSearch University](https://pyimagesearch.com/pyimagesearch-university/?utm_source=blogPost&utm_medium=bottomBanner&utm_campaign=What%27s%20next%3F%20I%20recommend).

**Course information:**  
84 total classes • 114+ hours of on-demand code walkthrough videos • Last updated: February 2024  
★★★★★ 4.84 (128 Ratings) • 16,000+ Students Enrolled

**I strongly believe that if you had the right teacher you could _master_ computer vision and deep learning.**

Do you think learning computer vision and deep learning has to be time-consuming, overwhelming, and complicated? Or has to involve complex mathematics and equations? Or requires a degree in computer science?

That’s _not_ the case.

All you need to master computer vision and deep learning is for someone to explain things to you in _simple, intuitive_ terms. _And that’s exactly what I do_. My mission is to change education and how complex Artificial Intelligence topics are taught.

If you're serious about learning computer vision, your next stop should be PyImageSearch University, the most comprehensive computer vision, deep learning, and OpenCV course online today. Here you’ll learn how to _successfully_ and _confidently_ apply computer vision to your work, research, and projects. Join me in computer vision mastery.

**Inside PyImageSearch University you'll find:**

*   ✓ **84 courses** on essential computer vision, deep learning, and OpenCV topics
*   ✓ **84 Certificates** of Completion
*   ✓ **114+ hours** of on-demand video
*   ✓ **Brand new courses released _regularly_**, ensuring you can keep up with state-of-the-art techniques
*   ✓ **Pre-configured Jupyter Notebooks in Google Colab**
*   ✓ Run all code examples in your web browser — works on Windows, macOS, and Linux (no dev environment configuration required!)
*   ✓ Access to **centralized code repos for _all_ 536+ tutorials** on PyImageSearch
*   ✓ **Easy one-click downloads** for code, datasets, pre-trained models, etc.
*   ✓ **Access** on mobile, laptop, desktop, etc.

[Click here to join PyImageSearch University](https://pyimagesearch.com/pyimagesearch-university/?utm_source=blogPost&utm_medium=bottomBanner&utm_campaign=What%27s%20next%3F%20I%20recommend)

Summary
-------

In this blog post we learned how to watermark an image dataset using OpenCV and Python. Using digital watermarks, you can overlay your own name, logo, or company brand overtop your original work, thereby protecting the content and attributing yourself as the original creator.

In order to create these digital watermarks with OpenCV, we leveraged PNG with alpha transparency. Utilizing alpha transparency can be quite tricky, _especially_ since it appears that OpenCV does not automatically mask transparent pixels for each channel.

Instead, you’ll need to _manually_ perform this masking yourself by taking the bitwise `AND` between the input channel and the alpha mask (as demonstrated in this blog post).

Anyway, I hope you enjoyed this tutorial!

**And before you go, be sure to enter your email address in the form below to be notified when new blog posts are published!**

![Image 11](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20410%20567'%3E%3C/svg%3E)

#### Download the Source Code and FREE 17-page Resource Guide

Enter your email address below to get a .zip of the code and a **FREE 17-page Resource Guide on Computer Vision, OpenCV, and Deep Learning.** Inside you'll find my hand-picked tutorials, books, courses, and libraries to help you master CV and DL!