A few weeks ago, I wrote a blog post on creating 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, 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.
Looking for the source code to this post?
Jump right to the downloads section.
Watermarking images with OpenCV and Python
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:
This watermark is a PNG image 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
# 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 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
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))
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.
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.
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:
You can see each of the watermarked images below:
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:
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:
I’m also a huge fan of mid-century modern architecture, so I had to visit Taliesin West:
Finally, here’s a beautiful photo of the Arizona landscape (even if it was a bit cloudy that day):
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:
Notice how the entire watermark image is treated as being semi-transparent instead of only the corresponding alpha pixel values!
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.
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!