Remove white part of binarized image

Question:

I have the following code to binarize an image using opencv, I can binarize and save, but before saving I wanted to remove all the white pixels from the edge of the image until I find the line of black pixels as in the image. It’s possible? and how to do?


Square with white background

import cv2
import numpy as np
from tkinter import filedialog
 

image1 = filedialog.askopenfilename()

image1 = cv2.imread(image1)
 
img = cv2.cvtColor(image1, cv2.COLOR_BGR2GRAY)
 
ret, bin = cv2.threshold(img, 120, 255, cv2.THRESH_TOZERO)

cv2.imwrite('bin.png', bin)

cv2.imshow('Imagem binarizada', bin)
cv2.waitKey(0)
cv2.destroyAllWindows()
Asked By: Lucas gomes

||

Answers:

There are multiple ways to crop the shape. I’m using a direct approach, based on the only sample you posted. Basically you need to find the target external contour (i.e., the square) on an inverted binary image.

There are multiple contours though, but you can filter them based on two simple metrics: aspect ratio (the relation between width and height – the square should be close to 1.0) and area (the square is the biggest blob on your image). Once you have the shape, you can get use its bounding box data to crop it using numpy slicing:

# Imports:
import numpy as np
import cv2

# Image path
path = "D://opencvImages//"
fileName = "square.png"

# Reading an image in default mode:
inputImage = cv2.imread(path + fileName)

# Deep Copy:
inputImageCopy = inputImage.copy()

# Convert RGB to grayscale:
grayscaleImage = cv2.cvtColor(inputImage, cv2.COLOR_BGR2GRAY)

# (Otsu) Threshold:
thresh, binaryImage = cv2.threshold(grayscaleImage, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)

This bit just reads the image and converts it from BGR to inverted binary, this is the result:

Alright, next, find all the contours on this binary image and apply the properties filter:

# Find the EXTERNAL contours on the binary image:
contours, _ = cv2.findContours(binaryImage, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

# Look for the outer bounding boxes (no children):
for _, c in enumerate(contours):

    # Get the bounding rectangle:
    boundRect = cv2.boundingRect(c)

    # Draw the rectangle on the input image:
    # Get the dimensions of the bounding rect:
    rectX = int(boundRect[0])
    rectY = int(boundRect[1])
    rectWidth = int(boundRect[2])
    rectHeight = int(boundRect[3])

    # Compute contour area:
    contourArea = rectHeight * rectWidth

    # Compute aspect ratio:
    referenceRatio = 1.0
    contourRatio = rectWidth/rectHeight
    epsilon = 0.1
    ratioDifference = abs(referenceRatio - contourRatio)
    print((ratioDifference, contourArea))

    # Apply contour filter:
    if ratioDifference <= epsilon:
        minArea = 1000
        if contourArea > minArea:
            # Crop contour:
            croppedShape = inputImageCopy[rectY:rectY+rectHeight,rectX:rectX+rectWidth]
            cv2.imshow("Shape Crop", croppedShape)
            cv2.waitKey(0)

            # (Optional) Set color and draw:
            color = (0, 0, 255)
            cv2.rectangle(inputImageCopy, (int(rectX), int(rectY)),
                          (int(rectX + rectWidth), int(rectY + rectHeight)), color, 2)

    # (Optional) Show image:
    cv2.imshow("Bounding Rectangle", inputImageCopy)
    cv2.waitKey(0)

The most relevant parts of the last snippet are the contour selection method and properties filter. I’m looking for external contours (no nested contours) so the "retrieval" method is set to cv2.RETR_EXTERNAL. Next, the properties filter. I get the bounding box of every found contour and compute its area and aspect ratio. Both values should be above a minimum threshold. If the contour satisfies both conditions, crop the shape using its bounding box.

This is the filtered contour:

This is the cropped shape:

Answered By: stateMachine
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.