Regarding framing algorithm/extracting a certain object of interest in Python - python

Hello, for a personal project I need to crop out extract this underwater gate from an image, and leave out anything other than the gate. The image is colored here but I can assume that the image of the gate I receive will only be lined, with the gate being white lines and the background being black. Could anyone give me any advice about how to go about this solution? I'm a novice when it comes to OpenCV so I'm a bit lost.

Here's the main idea
Gaussian blur image and extract blue channel
Threshold image with cv2.threshold()
Erode to remove black lines and isolate gate with cv2.erode()
Find contours and filter for gate contour using cv2.findContours() and cv2.contourArea()
Create a mask and dilate image using cv2.dilate()
Crop gate using cv2.bitwise_and()
import cv2
import numpy as np
# Load in image and create copy
image = cv2.imread('1.png')
original = image.copy()
# Gaussian blur and extract blue channel
blur = cv2.GaussianBlur(image, (3,3), 0)
blue = blur[:,:,0]
# Threshold image and erode to isolate gate contour
thresh = cv2.threshold(blue,135, 255, cv2.THRESH_BINARY_INV)[1]
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3,3))
erode = cv2.erode(thresh, kernel, iterations=4)
# Create a mask and find contours
mask = np.zeros(original.shape, dtype=np.uint8)
cnts = cv2.findContours(erode, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
# Filter for gate contour using area and draw onto mask
for c in cnts:
area = cv2.contourArea(c)
if area > 6000:
cv2.drawContours(mask, [c], -1, (255,255,255), 2)
# Dilate to restore contour and mask it with original image
dilate = cv2.dilate(mask, kernel, iterations=7)
result = cv2.bitwise_and(original, dilate)
cv2.imshow('thresh', thresh)
cv2.imshow('erode', erode)
cv2.imshow('mask', mask)
cv2.imshow('dilate', dilate)
cv2.imshow('result', result)
cv2.waitKey()

Related

Remove stamp from bill python

Any ideas on how to remove the stamp from this bill prior to OCR processing?
Here is one way to do that in Python/OpenCV.
Read input
Threshold on yellow
Dilate to fill out rectangle
Get largest contour
Draw a white filled contour on the input image
Save the results
Input:
import cv2
import numpy as np
# read image
img = cv2.imread('form_with_label.jpg')
# threshold on yellow
lower=(0,200,200)
upper=(100,255,255)
thresh = cv2.inRange(img, lower, upper)
# apply dilate morphology
kernel = np.ones((9,9), np.uint8)
mask = cv2.morphologyEx(thresh, cv2.MORPH_DILATE, kernel)
# get largest contour
contours = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
big_contour = max(contours, key=cv2.contourArea)
x,y,w,h = cv2.boundingRect(big_contour)
# draw filled white contour on input
result = img.copy()
cv2.drawContours(result,[big_contour],0,(255,255,255),-1)
# save cropped image
cv2.imwrite('form_with_label_thresh.png',thresh)
cv2.imwrite('form_with_label_mask.png',mask)
cv2.imwrite('form_with_label_removed.png',result)
# show the images
cv2.imshow("THRESH", thresh)
cv2.imshow("MASK", mask)
cv2.imshow("RESULT", result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Thresholded Image:
Morphology Dilated Image:
Result:

How do I detect only the black rectangle that appears in the reference image with OpenCV

I need to detect only the black rectangle that appears there, but for some reason my code does not detect it but it does detect many other things.
import cv2
img=cv2.imread('vision.png') #read image
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
Blur=cv2.GaussianBlur(gray,(5,5),1) #apply blur to roi
Canny=cv2.Canny(Blur,10,50) #apply canny to roi
#Find my contours
contours =cv2.findContours(Canny,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE)[0]
cntrRect = []
for i in contours:
epsilon = 0.05*cv2.arcLength(i,True)
approx = cv2.approxPolyDP(i,epsilon,True)
if len(approx) == 4:
cv2.drawContours(img,cntrRect,-1,(0,255,0),2)
cv2.imshow('Image Rect ONLY',img)
cntrRect.append(approx)
cv2.waitKey(0)
cv2.destroyAllWindows()
How do I detect only the black rectangle that appears in the image
But this code detect more rectangles and I don't want whis, but I only want detect the black countour rectangle
Here is one way to do that in Python/OpenCV.
Threshold the image. Then use morphology to fill out the rectangle. Then get the largest contour and draw on the input.
Input:
import cv2
import numpy as np
# load image
img = cv2.imread("black_rectangle_outline.png")
# convert to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# threshold
thresh = cv2.threshold(gray, 30, 255, cv2.THRESH_BINARY)[1]
# apply close morphology
kernel = np.ones((111,111), np.uint8)
morph = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
# invert so rectangle is white
morph = 255 - morph
# get largest contour and draw on copy of input
result = img.copy()
contours = cv2.findContours(morph, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
big_contour = max(contours, key=cv2.contourArea)
cv2.drawContours(result, [big_contour], 0, (255,255,255), 1)
# write result to disk
cv2.imwrite("black_rectangle_outline_thresh.png", thresh)
cv2.imwrite("black_rectangle_outline_morph.png", morph)
cv2.imwrite("black_rectangle_outline_result.png", result)
# display results
cv2.imshow("THRESH", thresh)
cv2.imshow("MORPH", morph)
cv2.imshow("RESULT", result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Threshold Image:
Morphology Image:
Result:

Delete unused shapes in OpenCV

I've got a shape detection with OpenCV in Python going on; bolts and nuts. I take a picture, make it binary, and detect edges. Now the white area is always grainy because of dust and grime. My detection uses the largest areas as parts, which works great. But how can I delete the thousands of objects caused by dust? 
In short, I want to reduce the array of shapes to only the biggest ones for further processing.
Here is one way to do that as per my comment above using Python/OpenCV.
From your binary image get the contours. Then select the largest contour. Then draw a white filled contour on a black background image the same size as your input as a mask. Then use numpy to blacken everything in your image that is black in your mask.
Input:
import cv2
import numpy as np
# load image
img = cv2.imread("coke_bottle2.png")
hh, ww = img.shape[:2]
# convert to gray
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# threshold using inRange
thresh = cv2.threshold(gray, 50, 255, cv2.THRESH_BINARY)[1]
# apply morphology closing to fill black holes and smooth outline
# could use opening to remove white spots, but we will use contours
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (25,25))
thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
# get the largest contour
contours = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
big_contour = max(contours, key=cv2.contourArea)
# draw largest contour as white filled on black background as mask
mask = np.zeros((hh,ww), dtype=np.uint8)
cv2.drawContours(mask, [big_contour], 0, 255, -1)
# use mask to black all but largest contour
result = img.copy()
result[mask==0] = (0,0,0)
# write result to disk
cv2.imwrite("coke_bottle2_threshold.png", thresh)
cv2.imwrite("coke_bottle2_mask.png", mask)
cv2.imwrite("coke_bottle2_background_removed.jpg", result)
# display it
cv2.imshow("thresh", thresh)
cv2.imshow("mask", mask)
cv2.imshow("result", result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Threshold Image (contains small extraneous white regions):
Mask Image (only the largest filled contour):
Result:

How to remove noise artifacts from an image for OCR with Python OpenCV?

I have subsets of images that contains digits. Each subset is read by Tesseract for OCR. Unfortunately for some images the cropping from the original image isn't optimal.
Hence some artifacts/remains at the top and bottom of the image and hamper Tesseract to recognize characters on the image. Then I would like to get rid of these artifacts and get to a similar result:
First I considered a simple approach: I set the first row of pixels as the reference: if an artifact was found along the x-axis (i.e., a white pixel if the image is binarized), I removed it along the y-axis until the next black pixel. Code for this approach is the one below:
import cv2
inp = cv2.imread("testing_file.tif")
inp = cv2.cvtColor(inp, cv2.COLOR_BGR2GRAY)
_,inp = cv2.threshold(inp, 150, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
ax = inp.shape[1]
ay = inp.shape[0]
out = inp.copy()
for i in range(ax):
j = 0
while j in range(ay):
if out[j,i] == 255:
out[j,i] = 0
else:
break
j+=1
out = cv2.bitwise_not(out)
cv2.imwrite('output.png',out)
But the result isn't good at all:
Then I stumbled across the flood_fill function from scipy (here) but found out it was too much time consuming and still not efficient. A similar question was asked on SO here but didn't help so much. Maybe a k-nearest neighbor approach could be considered? I also found out that methods that consist in merging neighbors pixels under some criteria were called growing methods, among which the single linkage is the most common (here).
What would you recommend to remove the upper and lower artifacts?
Here's a simple approach:
Convert image to grayscale
Otsu's threshold to obtain binary image
Cerate special horizontal kernel and dilate
Detect horizontal lines, sort for largest contour, and draw onto mask
Bitwise-and
After converting to grayscale, we Otsu's threshold to get a binary image
# Read in image, convert to grayscale, and Otsu's threshold
image = cv2.imread('1.png')
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
Next we create a long horizontal kernel and dilate to connect the numbers together
# Create special horizontal kernel and dilate
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_CROSS, (70,1))
dilate = cv2.dilate(thresh, horizontal_kernel, iterations=1)
From here we detect horizontal lines and sort for the largest contour. The idea is that the largest contour will be the middle section of the numbers where the numbers are all "complete". Any smaller contours will be partial or cut off numbers so we filter them out here. We draw this largest contour onto a mask
# Detect horizontal lines, sort for largest contour, and draw on mask
mask = np.zeros(image.shape, dtype=np.uint8)
detected_lines = cv2.morphologyEx(dilate, cv2.MORPH_OPEN, horizontal_kernel, iterations=1)
cnts = cv2.findContours(detected_lines, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
cnts = sorted(cnts, key=cv2.contourArea, reverse=True)
for c in cnts:
cv2.drawContours(mask, [c], -1, (255,255,255), -1)
break
Now that we have the outline of the desired numbers, we simply bitwise-and with our original image and color the background white to get our result
# Bitwise-and to get result and color background white
mask = cv2.cvtColor(mask,cv2.COLOR_BGR2GRAY)
result = cv2.bitwise_and(image,image,mask=mask)
result[mask==0] = (255,255,255)
Full code for completeness
import cv2
import numpy as np
# Read in image, convert to grayscale, and Otsu's threshold
image = cv2.imread('1.png')
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
# Create special horizontal kernel and dilate
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_CROSS, (70,1))
dilate = cv2.dilate(thresh, horizontal_kernel, iterations=1)
# Detect horizontal lines, sort for largest contour, and draw on mask
mask = np.zeros(image.shape, dtype=np.uint8)
detected_lines = cv2.morphologyEx(dilate, cv2.MORPH_OPEN, horizontal_kernel, iterations=1)
cnts = cv2.findContours(detected_lines, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
cnts = sorted(cnts, key=cv2.contourArea, reverse=True)
for c in cnts:
cv2.drawContours(mask, [c], -1, (255,255,255), -1)
break
# Bitwise-and to get result and color background white
mask = cv2.cvtColor(mask,cv2.COLOR_BGR2GRAY)
result = cv2.bitwise_and(image,image,mask=mask)
result[mask==0] = (255,255,255)
cv2.imshow('thresh', thresh)
cv2.imshow('dilate', dilate)
cv2.imshow('result', result)
cv2.waitKey()

Filter out small grid lines from an image using python

I am trying to clear out the grids so that I can use the plot alone in a different process. I am trying to solve it using opencv. I have both actual and inverted image. I am not expert in python. Any help could be great.
Thanks in advance
Actual image
inverted image
You can use the opening or closing of your image (depending if you are using the normal or inverted image). Opening will first erode your image and then dilate it. This will remove small/thin objects assuming bright objects over black background.
For example, in the case of your inverted image, use
out = cv2.morphologyEx(src, MORPH_OPEN)
For more information check out this tutorial
Here's an approach that uses a combination of filtering techniques and masks.
Convert image to grayscale and median blur
Adaptive threshold image
Perform morphological transformations
Find contours and filter using contour area
Create a mask to keep the desired ROI sections
Bitwise-and to extract plot
Here's the result
import cv2
import numpy as np
image = cv2.imread('1.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.medianBlur(gray, 15)
thresh = cv2.adaptiveThreshold(blur,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV,11,3)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3))
erode = cv2.erode(thresh, kernel, iterations=1)
dilate = cv2.dilate(erode, kernel, iterations=3)
cnts = cv2.findContours(dilate, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
mask = np.zeros(image.shape, dtype=np.uint8)
for c in cnts:
area = cv2.contourArea(c)
if area > 850:
cv2.drawContours(mask, [c], -1, (255,255,255), -1)
mask = cv2.dilate(mask, kernel, iterations=1)
image = 255 - image
result = 255 - cv2.bitwise_and(mask, image)
cv2.imshow('result', result)
cv2.waitKey(0)
2nd Approach
Here's an alternative approach which is the same as the 1st approach but uses specialized horizontal and vertical kernels for filtering instead. This approach is probably more robust. Instead of using guess and check morphological transformations, we have dedicated kernels that filter out the horizontal/vertical grid lines.
vertical_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1,5))
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,1))
Here's the mask result after going through each kernel
The result is pretty much the same but slightly cleaner :)
import cv2
import numpy as np
image = cv2.imread('1.png',0)
blur = cv2.GaussianBlur(image, (5,5), 0)
thresh = cv2.threshold(blur, 130, 255, cv2.THRESH_BINARY_INV)[1]
vertical_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1,5))
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,1))
remove_horizontal = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, vertical_kernel)
remove_vertical = cv2.morphologyEx(remove_horizontal, cv2.MORPH_OPEN, horizontal_kernel)
cnts = cv2.findContours(remove_vertical, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
mask = np.ones(image.shape, dtype=np.uint8)
for c in cnts:
area = cv2.contourArea(c)
if area > 50:
cv2.drawContours(mask, [c], -1, (255,255,255), -1)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3))
mask = cv2.dilate(mask, kernel, iterations=1)
image = 255 - image
result = 255 - cv2.bitwise_and(mask, image)
cv2.imshow('result', result)
cv2.imwrite('result.png', result)
cv2.imshow('mask', mask)
cv2.waitKey(0)
Here is a pretty simple solution using Imagemagick. But the same concepts can be use from OpenCV. Sorry, I am not fluent with OpenCV.
Threshold the image
Perform morphologic close
Use connected components processing to remove extraneous features
Input:
kernel="5x5: 0,0,0,0,0 1,1,1,1,1 1,1,1,1,1 1,1,1,1,1 0,0,0,0,0"
convert img.png -threshold 75% \
-morphology close "$kernel" \
-define connected-components:area-threshold=100 \
-define connected-components:mean-color=true \
-connected-components 4 result.png
ADDITION:
Here is Python Wand code to do the same thing. Python Wand is based upon Imagemagick. It will require Wand 0.5.6 (when available) and Imagemagick 7.
#!/bin/python3.7
from wand.image import Image
with Image(filename='curve.png') as img:
krnl="5x5: 0,0,0,0,0 1,1,1,1,1 1,1,1,1,1 1,1,1,1,1 0,0,0,0,0"
img.threshold(threshold=0.75)
img.morphology(method='close',kernel=krnl)
img.connected_components(connectivity=4, area_threshold=100, mean_color=True)
img.save(filename='curve_proc.png')

Categories

Resources