Latest YouTube Video

Monday, April 25, 2016

Watermarking images with OpenCV and Python

watermark_headerA 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:

Figure 1: Our example watermark image -- the PyImageSearch logo.

Figure 1: Our example watermark image — 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
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 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:

# 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())

# 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:

# 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())

# 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]

# 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:

# 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())

# 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]

# 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])

# 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.

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:
Figure 2: The output from our watermarking script.

Figure 2: The output from our watermarking script.

You can see each of the watermarked images below:

Figure 3: Watermarking images with OpenCV and Python.

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:

Figure 4: Creating watermarks with OpenCV and Python.

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:

Figure 5: Feed a giraffe...and watermarking the image with computer vision.

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:

Figure 6: Watermarking images with OpenCV.

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):

Figure 7: Creating watermarks with OpenCV is easy!

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:

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

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.

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!

Downloads:

If you would like to download the code and images used in this post, please enter your email address in the form below. Not only will you get a .zip of the code, I’ll also send you a FREE 11-page Resource Guide on Computer Vision and Image Search Engines, including exclusive techniques that I don’t post on this blog! Sound good? If so, enter your email address and I’ll send you the code immediately!

The post Watermarking images with OpenCV and Python appeared first on PyImageSearch.



from PyImageSearch http://ift.tt/1Nt8xYc
via IFTTT

No comments: