0

Facing problem while removing checkerboard pattern. I'm using cv2.Threshold but it selected unexpected pixels too (red marked) .

import cv2
import numpy as np

input = cv2.imread('image.png')
ret, logo_mask = cv2.threshold(input[:,:,0], 0, 255, cv2.THRESH_BINARY|cv2.THRESH_OTSU)
cv2.imshow(logo_mask)

Input image: input image

Output image: enter image description here

Anyone can help?

dip deb
  • 69
  • 3
  • 14
  • 1
    Try using `cv2.inRange` twice - once for the white pixels and once for the gray pixels. You may also ignore very small clusters (find clusters using `findContours` or `connectedComponentsWithStats`). We may also look for white clusters that touches gray clusters and gray clusters that touches white clusters. – Rotem Nov 11 '22 at 09:32
  • This kind of pattern is usually used by programs to show transparency. You don't have the original file with a separate alpha channel i suppose ? – nick Nov 11 '22 at 10:20
  • See https://stackoverflow.com/questions/74134195/how-to-replace-a-checked-pattern-in-a-png-image-with-transparent-in-python/74166276#74166276 – fmw42 Nov 11 '22 at 17:06

1 Answers1

3

Getting perfect results that covers all cases is challenging.

The following solution assumes that the white checkerboard color is (255, 255, 255), and gray is (230, 230, 230).
Another assumptions is that the clusters with that specific colors in the other parts of the image are very small.

We may use the following stages:

  • Find "white mask" and "gray mask" where color is (255, 255, 255) and (230, 230, 230).
  • Create unified mask using bitwise or.
  • Find contours, and remove small contours from the mask (assumed to be "noise").

Code sample:

import cv2
import numpy as np

input = cv2.imread('image.png')

white_mask = np.all(input == 255, 2).astype(np.uint8)*255  # cv2.inRange(input, (255, 255, 255), (255, 255, 255))
gray_mask = np.all(input == 230, 2).astype(np.uint8)*255  # gray_mask = cv2.inRange(input, (230, 230, 230), (230, 230, 230))

mask = cv2.bitwise_or(white_mask, gray_mask)  # Create unified mask

ctns = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[-2]  # Find contours

# Remove small contours from mask
for c in ctns:
    area = cv2.contourArea(c)  # Find the area of each contours
    if (area < 10):  # Ignore small contours (assume noise).
        cv2.drawContours(mask, [c], 0, 0, -1)

mask = cv2.dilate(mask, np.ones((3, 3), np.uint8))  # Dilate the mask - "cosmetics"

output = cv2.copyTo(input, 255-mask)  # Put black color in the masked part.

# Show images for testing
cv2.imshow('input', input)
cv2.imshow('mask', mask)
cv2.imshow('output', output)
cv2.waitKey()
cv2.destroyAllWindows()

white_mask:
enter image description here

gray_mask:
enter image description here

mask:
enter image description here

output:
enter image description here


In case there are large white areas or gray areas in the foreground part, the above solution may not work.

I thought of a process for finding only the areas that overlaps a boundary between white and gray rectangle.
It's not working, because there are small parts between the tree branches that are excluded.

The following code may give you inspirations:

import cv2
import numpy as np

input = cv2.imread('image.png')
#ret, logo_mask = cv2.threshold(input[:,:,0], 0, 255, cv2.THRESH_BINARY|cv2.THRESH_OTSU)

white_mask = np.all(input == 255, 2).astype(np.uint8)*255  # cv2.inRange(input, (255, 255, 255), (255, 255, 255))
gray_mask = np.all(input == 230, 2).astype(np.uint8)*255  # gray_mask = cv2.inRange(input, (230, 230, 230), (230, 230, 230))

cv2.imwrite('white_mask.png', white_mask)
cv2.imwrite('gray_mask.png', gray_mask)

# Apply opening for removing small clusters
opened_white_mask = cv2.morphologyEx(white_mask, cv2.MORPH_OPEN, np.ones((3, 3), np.uint8))
opened_gray_mask = cv2.morphologyEx(gray_mask, cv2.MORPH_OPEN, np.ones((3, 3), np.uint8))

cv2.imwrite('opened_white_mask.png', opened_white_mask)
cv2.imwrite('opened_gray_mask.png', opened_gray_mask)

white_mask_shell = cv2.dilate(opened_white_mask, np.ones((3, 3), np.uint8)) - opened_white_mask  # Dilate white_mask and keep only the "shell"
gray_mask_shell = cv2.dilate(opened_gray_mask, np.ones((3, 3), np.uint8)) - opened_gray_mask # Dilate gray_mask and keep only the "shell"
white_mask_shell = cv2.dilate(white_mask_shell, np.ones((3, 3), np.uint8)) # Dilate the "shell"
gray_mask_shell = cv2.dilate(gray_mask_shell, np.ones((3, 3), np.uint8)) # Dilate the "shell"

cv2.imwrite('white_mask_shell.png', white_mask_shell)
cv2.imwrite('gray_mask_shell.png', gray_mask_shell)

overlap_shell = cv2.bitwise_and(white_mask_shell, gray_mask_shell)
cv2.imwrite('overlap_shell.png', overlap_shell)

dilated_overlap_shell = cv2.dilate(overlap_shell, np.ones((17, 17), np.uint8))

mask = cv2.bitwise_or(cv2.bitwise_and(white_mask, dilated_overlap_shell), cv2.bitwise_and(gray_mask, dilated_overlap_shell))

cv2.imshow('input', input)
cv2.imshow('white_mask', white_mask)
cv2.imshow('gray_mask', gray_mask)
cv2.imshow('white_mask', white_mask)
cv2.imshow('gray_mask', gray_mask)
cv2.imshow('opened_white_mask', opened_white_mask)
cv2.imshow('opened_gray_mask', opened_gray_mask)
cv2.imshow('overlap_shell', overlap_shell)
cv2.imshow('dilated_overlap_shell', dilated_overlap_shell)
cv2.imshow('mask', mask)
cv2.waitKey()
cv2.destroyAllWindows()
Rotem
  • 30,366
  • 4
  • 32
  • 65