4

I have hundreds of images of jewelry products. Some of them have "best-seller" tag on them. The position of the tag is different from image to image. I want iterate over all images, and if an image has this tag then remove it. The resulted image will render the background over the removed object's pixels.

Example of an image with Tag/sticker/object:

enter image description here

Tag/sticker/object to remove:

enter image description here

import numpy as np
import cv2 as cv

img = plt.imread('./images/001.jpg')
sticker = plt.imread('./images/tag.png',1)
diff_im = cv2.absdiff(img, sticker)

I want the resulted image to be like this:

nathancy
  • 42,661
  • 14
  • 115
  • 137
murtio
  • 153
  • 2
  • 7
  • try removing pixels with colour as pink : https://stackoverflow.com/questions/9755017/python-pil-remove-sections-of-an-image-based-on-its-colour :: this can help – Rahul Agarwal May 14 '19 at 14:10
  • thanks @RahulAgarwal; the problem with this approach is that the tag is not always pinky :) – murtio May 15 '19 at 00:12

2 Answers2

6

Here's an method using a modified scale-invariant Template Matching approach. The overall strategy:

  • Load template, convert to grayscale, perform canny edge detection
  • Load original image, convert to grayscale
  • Continuously rescale image, apply template matching using edges, and keep track of the correlation coefficient (higher value means better match)
  • Find coordinates of best fit bounding box then erase unwanted ROI

To begin, we load in the template and perform Canny edge detection. Applying template matching with edges instead of the raw image removes color variation differences and gives a more robust result. Extracting edges from template image:

enter image description here

Next we continuously scale down the image and apply template matching on our resized image. I maintain aspect ratio with each resize using a old answer. Here's a visualization of the strategy

enter image description here

The reason we resize the image is because standard template matching using cv2.matchTemplate will not be robust and may give false positives if the dimensions of the template and the image do not match. To overcome this dimension issue, we use this modified approach:

  • Continuously resize the input image at various smaller scales
  • Apply template matching using cv2.matchTemplate and keep track of the largest correlation coefficient
  • The ratio/scale with the largest correlation coefficient will have the best matched ROI

Once the ROI is obtained, we can "delete" the logo by filling in the rectangle with white using

cv2.rectangle(final, (start_x, start_y), (end_x, end_y), (255,255,255), -1)

Detected -> Removed

import cv2
import numpy as np

# Resizes a image and maintains aspect ratio
def maintain_aspect_ratio_resize(image, width=None, height=None, inter=cv2.INTER_AREA):
    # Grab the image size and initialize dimensions
    dim = None
    (h, w) = image.shape[:2]

    # Return original image if no need to resize
    if width is None and height is None:
        return image

    # We are resizing height if width is none
    if width is None:
        # Calculate the ratio of the height and construct the dimensions
        r = height / float(h)
        dim = (int(w * r), height)
    # We are resizing width if height is none
    else:
        # Calculate the ratio of the 0idth and construct the dimensions
        r = width / float(w)
        dim = (width, int(h * r))

    # Return the resized image
    return cv2.resize(image, dim, interpolation=inter)

# Load template, convert to grayscale, perform canny edge detection
template = cv2.imread('template.png')
template = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY)
template = cv2.Canny(template, 50, 200)
(tH, tW) = template.shape[:2]
cv2.imshow("template", template)

# Load original image, convert to grayscale
original_image = cv2.imread('1.png')
final = original_image.copy()
gray = cv2.cvtColor(original_image, cv2.COLOR_BGR2GRAY)
found = None

# Dynamically rescale image for better template matching
for scale in np.linspace(0.2, 1.0, 20)[::-1]:

    # Resize image to scale and keep track of ratio
    resized = maintain_aspect_ratio_resize(gray, width=int(gray.shape[1] * scale))
    r = gray.shape[1] / float(resized.shape[1])

    # Stop if template image size is larger than resized image
    if resized.shape[0] < tH or resized.shape[1] < tW:
        break

    # Detect edges in resized image and apply template matching
    canny = cv2.Canny(resized, 50, 200)
    detected = cv2.matchTemplate(canny, template, cv2.TM_CCOEFF)
    (_, max_val, _, max_loc) = cv2.minMaxLoc(detected)

    # Uncomment this section for visualization
    '''
    clone = np.dstack([canny, canny, canny])
    cv2.rectangle(clone, (max_loc[0], max_loc[1]), (max_loc[0] + tW, max_loc[1] + tH), (0,255,0), 2)
    cv2.imshow('visualize', clone)
    cv2.waitKey(0)
    '''

    # Keep track of correlation value
    # Higher correlation means better match
    if found is None or max_val > found[0]:
        found = (max_val, max_loc, r)

# Compute coordinates of bounding box
(_, max_loc, r) = found
(start_x, start_y) = (int(max_loc[0] * r), int(max_loc[1] * r))
(end_x, end_y) = (int((max_loc[0] + tW) * r), int((max_loc[1] + tH) * r))

# Draw bounding box on ROI to remove
cv2.rectangle(original_image, (start_x, start_y), (end_x, end_y), (0,255,0), 2)
cv2.imshow('detected', original_image)

# Erase unwanted ROI (Fill ROI with white)
cv2.rectangle(final, (start_x, start_y), (end_x, end_y), (255,255,255), -1)
cv2.imshow('final', final)
cv2.waitKey(0)
nathancy
  • 42,661
  • 14
  • 115
  • 137
  • Many thanks @nathancy. A huge applause for the resize method. The approach works great. Just one question: the final image colors is little bit changed, how can I maintain the same colors of the original? – murtio May 15 '19 at 00:13
  • I'm not sure what you mean by the final image colors got changed. They look the same in the screenshot images. Could you elaborate on where the final image colors are changed? The area where the logo was removed may be slightly different because we are filling in the ROI with white which may be different than the surrounding background color – nathancy May 15 '19 at 00:34
  • My bad; I was using plt for plotting/showing the image, thus the colors were changed. Many thanks. – murtio May 15 '19 at 07:41
  • @nathancy what if there was more than one match, this will only delete the last match correct? – 0xA Feb 19 '20 at 19:58
  • @forthelulx currently it only selects the single best match. You could modify it to catch multiple by setting a threshold. If its better then some predefined threshold then consider it a match – nathancy Feb 19 '20 at 20:23
  • @nathancy yupp, i just loop through until found[0] < threshold. Thanks. anyway, maybe a small edit can help people (: – 0xA Feb 19 '20 at 22:31
1

Use cv.matchTemplate. An example is provided in the documentation.

After finding the object just draw the rectangle with a negative thickness to have it filled in white.

Costantino Grana
  • 3,132
  • 1
  • 15
  • 35
  • Thanks; it seems templtate is the right way to go. @nathancy's thankfully added the resize method. – murtio May 15 '19 at 00:11