Fill the outside of contours OpenCV - python

I am trying to color in black the outside region of a contours using openCV and python language.
Here is my code :
contours, hierarchy = cv2.findContours(copy.deepcopy(img_copy),cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
areas = [cv2.contourArea(c) for c in contours]
max_index = np.argmax(areas)
cnt=contours[max_index]
# how to fill of black the outside of the contours cnt please? `

Here's how you can fill an image with black color outside of a set of contours:
import cv2
import numpy
img = cv2.imread("zebra.jpg")
stencil = numpy.zeros(img.shape).astype(img.dtype)
contours = [numpy.array([[100, 180], [200, 280], [200, 180]]), numpy.array([[280, 70], [12, 20], [80, 150]])]
color = [255, 255, 255]
cv2.fillPoly(stencil, contours, color)
result = cv2.bitwise_and(img, stencil)
cv2.imwrite("result.jpg", result)
UPD.: The code above exploits the fact that bitwise_and with 0-s produces 0-s, and won't work for fill colors other than black. To fill with an arbitrary color:
import cv2
import numpy
img = cv2.imread("zebra.jpg")
fill_color = [127, 256, 32] # any BGR color value to fill with
mask_value = 255 # 1 channel white (can be any non-zero uint8 value)
# contours to fill outside of
contours = [ numpy.array([ [100, 180], [200, 280], [200, 180] ]),
numpy.array([ [280, 70], [12, 20], [80, 150]])
]
# our stencil - some `mask_value` contours on black (zeros) background,
# the image has same height and width as `img`, but only 1 color channel
stencil = numpy.zeros(img.shape[:-1]).astype(numpy.uint8)
cv2.fillPoly(stencil, contours, mask_value)
sel = stencil != mask_value # select everything that is not mask_value
img[sel] = fill_color # and fill it with fill_color
cv2.imwrite("result.jpg", img)
Can fill with another image as well, for example, using img[sel] = ~img[sel] instead of img[sel] = fill_color would fill it with the same inverted image outside of the contours:

Related

Classifying black and silver images

I am struggling to classify the images at black and silver colores
How can I use OpenCV in order to classify them? Image with black color Image with Silver color?
What error is there below ?
import numpy as np
import cv2
# RGB color boundaries
black = ([0, 0, 0], [50, 50, 50])
boundaries = [black]
#
# Load an color image in grayscale
img = cv2.imread('zmFf4.jpg')
print(img.shape)
breakpoint()
for (lower, upper) in boundaries:
# create NumPy arrays from the boundaries
lower = np.array(lower, dtype="uint8")
upper = np.array(upper, dtype="uint8")
# find the colors within the specified boundaries and apply
# the mask
mask = cv2.inRange(img, lower, upper)
output = cv2.bitwise_and(img, img, mask=mask)
# show the images
cv2.imshow("images", np.hstack([img, output]))
cv2.waitKey(0)
You are not looking the silver range
Do as under
import numpy as np
import cv2
# RGB color boundaries
black = ([0, 0, 0], [50, 50, 50])
silver = ([192, 192, 192], [212, 212, 212])
boundaries = [black, silver]
#
# Load an color image in grayscale
img = cv2.imread('zmFf4.jpg')
print(img.shape)
breakpoint()
for (lower, upper) in boundaries:
# create NumPy arrays from the boundaries
lower = np.array(lower, dtype="uint8")
upper = np.array(upper, dtype="uint8")
# find the colors within the specified boundaries and apply
# the mask
mask = cv2.inRange(img, lower, upper)
output = cv2.bitwise_and(img, img, mask=mask)
# show the images
cv2.imshow("images", np.hstack([img, output]))
cv2.waitKey(0)

Numpy where() creates spots of different color than the ones defined

I'm writing a script that creates a mask for an image. My input image looks like this:
The original image is only 40x40px, here it is for reference:
I want to create a mask of the purple area in the center of the image. This is what I do:
# read the 40x40 image and convert it to RGB
input_image = cv2.cvtColor(cv2.imread('image.png'), cv2.COLOR_BGR2RGB)
# get the value of the color in the center of the image
center_color = input_image[20, 20]
# create the mask: pixels with same color = 255 (white), other pixels = 0 (black)
mask_bw = np.where(input_image == center_color, 255, 0)
# show the image
plt.imshow(mask_bw)
Most of the time this works perfectly fine, but for some images (like the one I attached to this question) I consistently get some blue areas in my mask like on the image below. This is reproducible and the areas are always the same for the same input images.
This is already weird enough, but if I try to remove the blue areas, this doesn't work either.
mask_bw[mask_bw != (255, 255, 255)] = 0 # this doesn't change anything..
Why is this happening and how do I fix this?
Additional info
tried with numpy version 1.17.3 and 1.17.4
Reproduced in my local environment and in a google colab notebook
The main problem is that you're trying to compare three channels but only setting the value for one channel. This is most likely causing the blue areas on the mask. When you use np.where() to set the other pixels to black, you are only setting this on the 1st channel instead of all three channels. You can visualize this by splitting each channel and printing the before/after arrays which will show you that the resulting array values are RGB(0,0,255). So to fix this problem, we need to compare each individual channel then set the desired area in white while setting any black areas on the mask to black for all three channels. Here is one way to do it:
import numpy as np
import cv2
image = cv2.imread('1.png')
center_color = image[20, 20]
b, g, r = cv2.split(image)
mask = (b == center_color[0]) & (g == center_color[1]) & (r == center_color[2])
image[mask] = 255
image[mask==0] = 0
cv2.imshow('image', image)
cv2.waitKey()
A hotfix to remove the blue areas using your current code would be to convert the image to grayscale (1-channel) then change all non-white pixels to black.
import numpy as np
import cv2
# Load image, find color, create mask
image = cv2.imread('1.png')
center_color = image[20, 20]
mask = np.where(image == center_color, 255, 0)
mask = np.array(mask, dtype=np.uint8)
# Convert image to grayscale, convert all non-white pixels to black
mask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
mask[mask != 255] = 0
cv2.imshow('mask', mask)
cv2.waitKey()
Here are two alternative methods to obtain a mask of the purple area
Method #1: Work in grayscale space
import numpy as np
import cv2
image = cv2.imread('1.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
center_color = gray[20, 20]
mask = np.array(np.where(gray == center_color, 255, 0), dtype=np.uint8)
cv2.imshow('mask', mask)
cv2.waitKey()
Method #2: Color thresholding
The idea is to convert the image to HSV color space then use a lower and upper color range to segment the image to create a binary mask
import numpy as np
import cv2
image = cv2.imread('1.png')
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
lower = np.array([0, 124, 0])
upper = np.array([179, 255, 255])
mask = cv2.inRange(hsv, lower, upper)
cv2.imshow('mask', mask)
cv2.waitKey()
Both methods should yield the same result
If you have a 3-channel image (i.e. RGB or BGR or somesuch) and you want to generate a single channel mask (i.e. you want 0/1 or True/False) for each pixel, then you effectively need to group the 3 values into a single using np.all() like this:
import cv2
import numpy as np
# Load image and get centre colour
image = cv2.imread('40x40.png')
cc = im[20, 20]
print(image.shape)
(40, 40, 3)
# Generate list of unique colours present in image so we know what we are dealing with
print(np.unique(im.reshape(-1,3), axis=0))
array([[140, 109, 142],
[151, 106, 140],
[160, 101, 137],
[165, 134, 157],
[175, 149, 171],
[206, 87, 109],
[206, 185, 193]], dtype=uint8)
# Generate mask of pixels matching centre colour
mask_bw = np.where(np.all(im==cc,axis=2), 255, 0)
# Check shape of mask - no 3rd dimension !!!
print(mask_bw.shape)
(40, 40)
# Check unique colours in mask
print(np.unique(mask_bw.reshape(-1,1), axis=0))
array([[ 0],
[255]])

How to fill a polygon in OpenCV?

The Python code given below draws a triangle, how can I fill it with a color inside? Or another easier way to draw a triangle in OpenCV?
pts = np.array([[100,350],[165,350],[165,240]], np.int32)
cv2.polylines(img,[pts],True,(0,255,255),2)
You have to use cv2.fillPoly().
Illustration for 2-channeled image
Change the second line to:
cv2.fillPoly(img, [pts], 255)
Code:
img = np.zeros([400, 400],dtype=np.uint8)
pts = np.array([[100,350],[165,350],[165,240]], np.int32)
cv2.fillPoly(img, [pts], 255)
cv2.imshow('Original', img)
Result:
Illustration for 3-channeled color image
img = cv2.imread('image_path')
pts = np.array([[170,50],[240, 40],[240, 150], [210, 100], [130, 130]], np.int32)
cv2.fillPoly(img, [pts], (255,150,255))
Result:

How can I count the number of rectangles with a minimal length?

I have the code below that I use to find if there is any dicontinuity on railways. The idea is to create bounding box and if there are 2 boxes, it means that everything is okay. Otherwise, if I have more than 2 boxes, it means that there is a discontinuity. So, at the end of my code, I put a function print len(contours).
The problem is that when I have my final image (with blue rectangles), we can see those I need (those that cover the railways), but also some really little rectangles I don't want. It means that the function print len(contours) send me back a message telling me that there are 19 rectangles for example.
My idea is to make a little function, asking the programme to count the number of rectangles with a minimal length or width (so it would count only the rectangles that cover the railways). I am a beginner in coding with Python and I have no idea about how to code this, does anyone can help me please?
Thank you in advance for your help.
Here is my code:
import numpy as np
import argparse
import cv2
image = cv2.imread('..\img.jpg')
frame = cv2.resize(image,(500,500))
"""boundaries = [
([17, 15, 100], [50, 56, 200]),
([86, 31, 4], [220, 88, 50]),
([25, 146, 190], [62, 174, 250]),
([103, 86, 65], [145, 133, 128])]"""
boundaries = [([100, 100, 100], [255, 255, 255])]
for (lower, upper) in boundaries:
# create NumPy arrays from the boundaries
lower = np.array(lower, dtype = "uint8")
upper = np.array(upper, dtype = "uint8")
# find the colors within the specified boundaries and apply
# the mask
mask = cv2.inRange(frame, lower, upper)
output = cv2.bitwise_and(frame, frame, mask = mask)
# show the images
cv2.namedWindow("images", cv2.WINDOW_NORMAL)
cv2.resizeWindow("images", 1000, 500)
cv2.imshow("images", np.hstack([frame, output]))
cv2.waitKey(0)
gray=cv2.cvtColor(output,cv2.COLOR_BGR2GRAY)
ret,th1 = cv2.threshold(gray,25,255,cv2.THRESH_BINARY)
contours,hierarchy = cv2.findContours(th1, cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
for cnt in contours:
x,y,w,h = cv2.boundingRect(cnt)
cv2.rectangle(output,(x,y),(x+w,y+h),(255,0,0),2)
cv2.imshow('image2',output)
cv2.waitKey(0)
print len(contours)
Here are my results (left image is the original, middle image allows us to see only the railways and right image detects the railways with boundary boxes) :
Results when there is not any discontinuity (the code says that there are 4 rectangles)
Results when there is a discontinuity (the code says that there are 19 rectangles)
In your result, we can see many noisy regions. So to get the counts of rectangles, you should do more pre-post-processing steps, such as morph-op on the binary, or removing the noisy regions by area or width-height-ratio.
In these two cases, doing morph-op is enough. But you can also do more processing on them.
croped images:
situation 1:
situation 2:
Source code:
## read and convert to gray
img = cv2.imread("lines.png")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
## threshed
th, threshed = cv2.threshold(gray, 100, 255, cv2.THRESH_BINARY|cv2.THRESH_OTSU)
## morph-op
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3))
morphed = threshed.copy()
morphed = cv2.morphologyEx(morphed, cv2.MORPH_ERODE, kernel, iterations=1)
morphed = cv2.morphologyEx(morphed, cv2.MORPH_DILATE, kernel, iterations=2)
## find contours
cnts = cv2.findContours(morphed, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)[-2]
print(len(cnts))
## removingt noisy regions by area
cnts = list(filter(lambda cnt: cv2.contourArea(cnt) > 100, cnts))
print(len(cnts))

python - opencv morphologyEx remove specific color

After remove captcha's background.
The image remain digits and noise.
Noise line is all in one color : RGB(127,127,127)
And then using morphology method.
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2, 2))
self.im = cv2.morphologyEx(self.im, cv2.MORPH_CLOSE, kernel)
Some part of digit will be remove.
How to use morphologyEx() remove only color in RGB(127,127,127) ?
In order to eliminate color within a particular range you have to use cv2.inRange() function.
Here is the code:
lower = np.array([126,126,126]) #-- Lower range --
upper = np.array([127,127,127]) #-- Upper range --
mask = cv2.inRange(img, lower, upper)
res = cv2.bitwise_and(img, img, mask= mask) #-- Contains pixels having the gray color--
cv2.imshow('Result',res)
This is what I got for the two images you have:
Image 1:
Image 2:
You carry on from here.
COLOR RANGE
color_dict_HSV = {'black': [[180, 255, 30], [0, 0, 0]],
'white': [[180, 18, 255], [0, 0, 231]],
'red1': [[180, 255, 255], [159, 50, 70]],
'red2': [[9, 255, 255], [0, 50, 70]],
'green': [[89, 255, 255], [36, 50, 70]],
'blue': [[128, 255, 255], [90, 50, 70]],
'yellow': [[35, 255, 255], [25, 50, 70]],
'purple': [[158, 255, 255], [129, 50, 70]],
'orange': [[24, 255, 255], [10, 50, 70]],
'gray': [[180, 18, 230], [0, 0, 40]]}
CREDITS:
Ali Hashemian
HOW TO REMOVE A COLOR FROM YOUR IMAGE USING OPENCV
Since most of you would like to do that, i.e. in my case the task was to remove blue color from the image, I used the following code, to remove blue ink stamps and, blue tick marks from my image in order for proper OCR using Tesseract.
[COLOR REMOVAL] CODE
import cv2
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
# image path:
#path = "D://opencvImages//"
#fileName = "out.jpg"
# Reading an image in default mode:
inputImage = cv2.imread('0.jpg')
# Convert RGB to grayscale:
grayscaleImage = cv2.cvtColor(inputImage, cv2.COLOR_BGR2GRAY)
# Convert the BGR image to HSV:
hsvImage = cv2.cvtColor(inputImage, cv2.COLOR_BGR2HSV)
# Create the HSV range for the blue ink:
# [128, 255, 255], [90, 50, 70]
lowerValues = np.array([90, 50, 70])
upperValues = np.array([128, 255, 255])
# Get binary mask of the blue ink:
bluepenMask = cv2.inRange(hsvImage, lowerValues, upperValues)
# Use a little bit of morphology to clean the mask:
# Set kernel (structuring element) size:
kernelSize = 3
# Set morph operation iterations:
opIterations = 1
# Get the structuring element:
morphKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (kernelSize, kernelSize))
# Perform closing:
bluepenMask = cv2.morphologyEx(bluepenMask, cv2.MORPH_CLOSE, morphKernel, None, None, opIterations, cv2.BORDER_REFLECT101)
# Add the white mask to the grayscale image:
colorMask = cv2.add(grayscaleImage, bluepenMask)
_, binaryImage = cv2.threshold(colorMask, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
cv2.imwrite('bwimage.jpg',binaryImage)
thresh, im_bw = cv2.threshold(binaryImage, 210, 230, cv2.THRESH_BINARY)
kernel = np.ones((1, 1), np.uint8)
imgfinal = cv2.dilate(im_bw, kernel=kernel, iterations=1)
cv2.imshow(imgfinal)
BEFORE [Original Image]
Blue Mark Extraction
Final Image
Here you can see that all of the tick marks are almost, removed the reason is that because there is always room for improvement, but this, as it seems, is the best we can get because even removing these little marks is not going to have a profound effect on the OCR using Tesseract.
HOPE THAT HELPS!
Here is my solution.
Your answer is obvious better than my.
def mop_close(self):
def morphological(operator=min):
height, width, _ = self.im.shape
# create empty image
out_im = np.zeros((height,width,3), np.uint8)
out_im.fill(255) # fill with white
for y in range(height):
for x in range(width):
try:
if self.im[y,x][0] ==127 and self.im[y,x][1] ==127 and self.im[y,x][2] ==127:
nlst = neighbours(self.im, y, x)
out_im[y, x] = operator(nlst,key = lambda x:np.mean(x))
else:
out_im[y,x] = self.im[y,x]
except Exception as e:
print(e)
return out_im
def neighbours(pix,y, x):
nlst = []
# search pixels around im[y,x] add them to nlst
for yy in range(y-1,y+1):
for xx in range(x-1,x+1):
try:
nlst.append(pix[yy, xx])
except:
pass
return np.array(nlst)
def erosion(im):
return morphological(min)
def dilation(im):
return morphological(max)
self.im = dilation(self.im)
self.im = erosion(self.im)
final result:

Categories