Search code examples
pythonpython-3.xopencv3.0opencv

Crop colour image according to OTSU threshold


I have a colour image which I have sucessfully applied the OTSU thresholding method on its greyscale form to obtain the outline of the biscuit:

Original Colour image:

Image

OTSU Thresholded image:

OTSU

What I would like to do is extract from the colour image only the pixels within the black portion of the OTSU thresholded image, and save that as a new picture. So far, I have tried to extract using the BITWISE_NOT method and using 'thresh_inv' as the mask, however that only results in this image (greyscale + extra black background). I have also tried using the cannny contours method to identify the rough outline of the biscuit circle, and then drawing the contours over a blank image to hopefully try and overlay that over the original colour picture. This has also not worked.

Attempt_1

Here is my code so far, I would greatly appreciate any help as I've been trying to figure it out for ages.

import numpy as np
import cv2 as cv2

picture = cv2.imread('Cropped_6.png',0)
blurred_picture = cv2.GaussianBlur(picture, (15,15), cv2.BORDER_DEFAULT)
# canny_picture = cv2.Canny(blurred_picture, 41,41)
# cv2.imshow('Blurred', canny_picture)

# Simple Thresholding
threshold, thresh = cv2.threshold(blurred_picture, 105, 255, cv2.THRESH_OTSU)
# contours, hierarchies = cv2.findContours(thresh, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
thresh_inv = cv2.bitwise_not(thresh)
foreground = cv2.bitwise_and(picture, picture, mask=thresh_inv)

cv2.imwrite('Attempt_1.png',foreground)

# cv2.drawContours(blank_picture, contours[:0], -1, (0,0,255), 1)
cv2.imshow('Original', picture)
cv2.waitKey(0) 

This is an example of what I'd end up with as a result (just the biscuit with a transparent background in the rectangle containing it, since afaik even circles are stored as rectangular images):

Desired result:

Ideal Result


Solution

  • 1st load the image as rgb. Then convert it into binary. since it has 3 channel then you need to stack your mask to form a array with 3 channel Then perform binary and operation on them.

    Try:

    import numpy as np
    import cv2 as cv2
    import matplotlib.pyplot as plt
    
    picture_rgb = cv2.imread('cropped6.png',1)
    picture = cv2.cvtColor(picture_rgb, cv2.COLOR_BGR2GRAY)
    
    blurred_picture = cv2.GaussianBlur(picture, (15,15), cv2.BORDER_DEFAULT)
    # canny_picture = cv2.Canny(blurred_picture, 41,41)
    # cv2.imshow('Blurred', canny_picture)
    
    # Simple Thresholding
    threshold, thresh = cv2.threshold(blurred_picture, 105, 255, cv2.THRESH_OTSU)
    # contours, hierarchies = cv2.findContours(thresh, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
    thresh_inv = cv2.bitwise_not(thresh)
    stacked = np.dstack((thresh_inv,thresh_inv,thresh_inv))
    img = cv2.bitwise_and(picture_rgb, stacked)
    foreground = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    foreground[np.all(foreground == (0, 0, 0), axis=-1)] = (255,255,255)
    
    cv2.imwrite('Attempt_1.png',foreground)
    
    # # cv2.drawContours(blank_picture, contours[:0], -1, (0,0,255), 1)
    # cv2.imshow('Original', picture)
    # cv2.waitKey(0) 
    plt.imshow(foreground, cmap='gray')
    

    To transparent it: (source: how to make white pixels transparent)

    from PIL import Image
    
    img = Image.fromarray(np.uint8(foreground)).convert('RGBA')
    img = img.convert("RGBA")
    datas = img.getdata()
    
    newData = []
    for item in datas:
        if item[0] == 255 and item[1] == 255 and item[2] == 255:
            newData.append((255, 255, 255, 0))
        else:
            newData.append(item)
    
    img.putdata(newData)
    img.save("img2.png", "PNG")