can't find right numbers of contours count - python

i'm trying to find specific contours having red outlines. Below is the code, I'm trying on this image :
import numpy as np
import cv2
image = cv2.imread('C:/Users/htc/Desktop/image.png')
original = image.copy()
image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
lower = np.array([0,50,50], dtype="uint8")
upper = np.array([10, 255,255], dtype="uint8")
mask = cv2.inRange(image, lower, upper)
# Find contours
cnts = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# Extract contours depending on OpenCV version
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
print(len(cnts))
# Iterate through contours and filter by the number of vertices
for c in cnts:
perimeter = cv2.arcLength(c, True)
approx = cv2.approxPolyDP(c, 0.04 * perimeter, True)
if len(approx) > 5:
cv2.drawContours(original, [c], -1, (36, 255, 12), -1)
cv2.imshow('mask', mask)
cv2.imshow('original', original)
cv2.waitKey()
Output
the length of contour i'm getting is 14 which is not correct. The correct output will be 3. Where i'm doing wrong?

If you can notice, there are breaks in your mask image due to which many contours are being detected. To correct this (if you only want the count), you can dilate the mask image obtained before finding the contours as shown below.
mask = cv2.inRange(image, lower, upper)
# Dilating the mask
kernel = np.ones((3, 3), dtype=np.uint8)
mask = cv2.dilate(mask, kernel, iterations=2)
# Find contours
cnts = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

Just add a step at the beginning where you blur the picture a little bit.
image = cv2.GaussianBlur(image, (3, 3), 0, 0, cv2.BORDER_DEFAULT)

Related

Draw bounding boxding box around whole block of text In image using python

I have the image, i have removed the noise (dots in the background) and, I want to draw a bounding box around the block of text In image how can I do it using python OpenCV
Input image:
Noise Removed Image:
Here is the code used to remove noise in background Where i can change to save images with bounding boxes around the text
import cv2
import matplotlib.pyplot as plt
import glob
import os
def remove_dots(image_path,outdir):
image = cv2.imread(image_path)
mask = np.zeros(image.shape, dtype=np.uint8)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (3,3), 0)
thresh = cv2.adaptiveThreshold(blur,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV,51,9)
# Create horizontal kernel then dilate to connect text contours
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
dilate = cv2.dilate(thresh, kernel, iterations=2)
# Find contours and filter out noise using contour approximation and area filtering
cnts = cv2.findContours(dilate, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
peri = cv2.arcLength(c, True)
approx = cv2.approxPolyDP(c, 0.04 * peri, True)
x,y,w,h = cv2.boundingRect(c)
area = w * h
ar = w / float(h)
if area > 1200 and area < 50000 and ar <8:
cv2.drawContours(mask, [c], -1, (255,255,255), -1)
# Bitwise-and input image and mask to get result
mask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
result = cv2.bitwise_and(image, image, mask=mask)
result[mask==0] = (255,255,255) # Color background white
cv2.imwrite(os.path.join(outdir,os.path.basename(image_path)),result)
for jpgfile in glob.glob(r'C:\custom\TableDetectionWork\text_detection_dataset/*'):
print(jpgfile)
remove_dots(jpgfile,r'C:\custom\TableDetectionWork\textdetect/')
You can do that by using a horizontal morphology filter to merge the letters in a mask image. Then find the contours. Then get the bounding boxes.
Input:
import cv2
import numpy as np
img = cv2.imread("john.jpg")
# convert to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# threshold
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)[1]
# invert
thresh = 255 - thresh
# apply horizontal morphology close
kernel = np.ones((5 ,191), np.uint8)
morph = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
# get external contours
contours = cv2.findContours(morph, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
# draw contours
result = img.copy()
for cntr in contours:
# get bounding boxes
pad = 10
x,y,w,h = cv2.boundingRect(cntr)
cv2.rectangle(result, (x-pad, y-pad), (x+w+pad, y+h+pad), (0, 0, 255), 4)
# save result
cv2.imwrite("john_bbox.png",result)
# display result
cv2.imshow("thresh", thresh)
cv2.imshow("morph", morph)
cv2.imshow("result", result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Morphology Closed Image:
Bounding Boxes Image:
Here is the core of your code modified to do what you want in Python/OpenCV. It is just adding my code to the end of your code.
Input:
import cv2
import numpy as np
image = cv2.imread("john.jpg")
mask = np.zeros(image.shape, dtype=np.uint8)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (3,3), 0)
thresh = cv2.adaptiveThreshold(blur,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV,51,9)
# Create horizontal kernel then dilate to connect text contours
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
dilate = cv2.dilate(thresh, kernel, iterations=2)
# Find contours and filter out noise using contour approximation and area filtering
cnts = cv2.findContours(dilate, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
peri = cv2.arcLength(c, True)
approx = cv2.approxPolyDP(c, 0.04 * peri, True)
x,y,w,h = cv2.boundingRect(c)
area = w * h
ar = w / float(h)
if area > 1200 and area < 50000 and ar <8:
cv2.drawContours(mask, [c], -1, (255,255,255), -1)
# Bitwise-and input image and mask to get result
mask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
result = cv2.bitwise_and(image, image, mask=mask)
result[mask==0] = (255,255,255) # Color background white
# NEW CODE HERE TO END _____________________________________________________________
gray2 = cv2.cvtColor(result, cv2.COLOR_BGR2GRAY)
thresh2 = cv2.threshold(gray2, 128, 255, cv2.THRESH_BINARY)[1]
thresh2 = 255 - thresh2
kernel = np.ones((5 ,191), np.uint8)
close = cv2.morphologyEx(thresh2, cv2.MORPH_CLOSE, kernel)
# get external contours
contours = cv2.findContours(close, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
# draw contours
result2 = result.copy()
for cntr in contours:
# get bounding boxes
pad = 10
x,y,w,h = cv2.boundingRect(cntr)
cv2.rectangle(result2, (x-pad, y-pad), (x+w+pad, y+h+pad), (0, 0, 255), 4)
cv2.imwrite("john_bboxes.jpg", result2)
cv2.imshow("mask",mask)
cv2.imshow("thresh",thresh)
cv2.imshow("dilate",dilate)
cv2.imshow("result",result)
cv2.imshow("gray2",gray2)
cv2.imshow("thresh2",thresh2)
cv2.imshow("close",close)
cv2.imshow("result2",result2)
cv2.waitKey(0)
cv2.destroyAllWindows()
Bounding Boxes on Your Result:

find number of sides python

I have used below code to find number of sides in an image but it's not giving appropriate result
import cv2
image = cv2.imread('sheet.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
# Find contours and perform contour approximation
cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
peri = cv2.arcLength(c, True)
approx = cv2.approxPolyDP(c, 0.03 * peri, True)
print('number of sides:',len(approx))
Further i want to find exact number of side in an image and length of each side in the image .
Below is the image for reference
Along with this can we get the length of each side also like here we have 4 sides so the length of the respective side and the number of corners .
What if we have circular shape or arc in any shape, then how to find number of edges and it's length
Here is one way to do that in Python/OpenCV using Canny edges.
Input:
import cv2
import numpy as np
# load image
img = cv2.imread('quadrilateral.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# apply canny edge detection
edges = cv2.Canny(gray, 90, 130)
# apply morphology close
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
morph = cv2.morphologyEx(edges, cv2.MORPH_CLOSE, kernel)
# get contours and keep largest
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)
# draw contour
contour = img.copy()
cv2.drawContours(contour, [big_contour], 0, (0,0,255), 1)
# get number of vertices (sides)
peri = cv2.arcLength(big_contour, True)
approx = cv2.approxPolyDP(big_contour, 0.03 * peri, True)
print('number of sides:',len(approx))
# save results
cv2.imwrite("quadrilateral_edges.jpg", edges)
cv2.imwrite("quadrilateral_morphology.jpg", morph)
cv2.imwrite("quadrilateral_contour.jpg", contour)
# show result
cv2.imshow("edges", edges)
cv2.imshow("morph", morph)
cv2.imshow("contour", contour)
cv2.waitKey(0)
cv2.destroyAllWindows()
Edge image:
Morphology image (to close boundary):
Contour:
Textual Results:
number of sides: 4

How to highlight unusual marks on mobile devices

I'm working on sample task where I need to highlight unusual marks on any mobile devices. I'm trying with opencv python. But, I'm not getting actual contors for the unusual marks.
Input image is like:
And output image is expected as below:
I'm trying something like below, but it didn't work.
import cv2
from matplotlib import pyplot as plt
blurValue = 15
img_path = "input.jpg"
# reading the image
image = cv2.imread(img_path)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (blurValue, blurValue), 0)
edged = cv2.Canny(image, 100, 255)
#applying closing function
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (7, 7))
closed = cv2.morphologyEx(edged, cv2.MORPH_CLOSE, kernel)
lower = np.array([4, 20, 93])
upper = np.array([83, 79, 166])
# hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
# blur = cv2.GaussianBlur(hsv, (blurValue, blurValue), 0)
mask = cv2.inRange(closed, lower, upper)
result_1 = cv2.bitwise_and(frame, frame, mask = mask)
cnts = cv2.findContours(result_1.copy(), cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
peri = cv2.arcLength(c, True)
approx = cv2.approxPolyDP(c, 0.02 * peri, True)
cv2.drawContours(image, [approx], -1, (0, 255, 0), 2)
plt.imshow(image)
plt.title("image")
plt.show()
Any help will be appreciated. Thank you.
My suggestion would be to use adaptive thresholding and filter on area (and possibly other characteristics). Here is my code and results using Python OpenCV.
Input:
import cv2
import numpy as np
# read image
img = cv2.imread("iphone.jpg")
# convert img to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# apply gaussian blur
blur = cv2.GaussianBlur(gray, (29,29), 0)
# do adaptive threshold on gray image
thresh = cv2.adaptiveThreshold(blur, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 51, 3)
# apply morphology open then close
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (17, 17))
open = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
close = cv2.morphologyEx(open, cv2.MORPH_CLOSE, kernel)
# Get contours
cnts = cv2.findContours(close, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
result = img.copy()
for c in cnts:
area = cv2.contourArea(c)
if area < 10000 and area > 5000:
cv2.drawContours(result, [c], -1, (0, 255, 0), 2)
# write results to disk
cv2.imwrite("iphone_thresh.jpg", thresh)
cv2.imwrite("iphone_close.jpg", close)
cv2.imwrite("iphone_markings.jpg", result)
# display it
cv2.imshow("IMAGE", img)
cv2.imshow("THRESHOLD", thresh)
cv2.imshow("CLOSED", close)
cv2.imshow("RESULT", result)
cv2.waitKey(0)
Thresholded Image:
Morphology Processed Image:
Final Result:
I would also suggest that you align the image with a known clean iPhone image and create a mask of the camera and logo, etc., markings so that you can filter the results to exclude those (and perhaps even the border of the camera outline).

Extract handwritten characters from a boxed form field image

I am trying to extract handwritten characters from field boxes
My desired output would be the character segments with the boxes removed. So far, I've tried defining contours and filtering by area but that hasn't yielded any good results.
# Reading image and binarization
im = cv2.imread('test.png')
char_gray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
char_bw = cv2.adaptiveThreshold(char_gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 75, 10)
# Applying erosion and dilation
kernel = np.ones((5,5), np.uint8)
img_erosion = cv2.erode(char_bw, kernel, iterations=1)
img_dilation = cv2.dilate(img_erosion, kernel, iterations=1)
# Find Canny edges
edged = cv2.Canny(img_dilation, 100, 200)
# Finding Contours
edged_copy = edged.copy()
im2, cnts, hierarchy = cv2.findContours(edged_copy, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
print("Number of Contours found = " + str(len(cnts)))
# Draw all contours
cv2.drawContours(im, cnts, -1, (0, 255, 0), 3)
# Filter using area and save
for no, c in enumerate(cnts):
area = cv2.contourArea(c)
if area > 100:
contour = c
(x, y, w, h) = cv2.boundingRect(contour)
img = im[y:y+h, x:x+w]
cv2.imwrite(f'./cnts/cnt-{no}.png', img_dilation)
Here's a simple approach:
Obtain binary image. We load the image, enlarge using imutils.resize(), convert to grayscale, and perform Otsu's thresholding to obtain a binary image
Remove horizontal lines. We create a horizontal kernel then perform morphological opening and remove the horizontal lines using cv2.drawContours
Remove vertical lines. We create a vertical kernel then perform morphological opening and remove the vertical lines using cv2.drawContours
Here's a visualization of each step:
Binary image
Detected lines/boxes to remove highlighted in green
Result
Code
import cv2
import numpy as np
import imutils
# Load image, enlarge, convert to grayscale, Otsu's threshold
image = cv2.imread('1.png')
image = imutils.resize(image, width=500)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
# Remove horizontal
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (25,1))
detect_horizontal = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, horizontal_kernel, iterations=2)
cnts = cv2.findContours(detect_horizontal, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
cv2.drawContours(image, [c], -1, (255,255,255), 5)
# Remove vertical
vertical_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1,25))
detect_vertical = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, vertical_kernel, iterations=2)
cnts = cv2.findContours(detect_vertical, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
cv2.drawContours(image, [c], -1, (255,255,255), 5)
cv2.imshow('thresh', thresh)
cv2.imshow('image', image)
cv2.waitKey()

How to extract bottom part from engineering drawing image using python?

My input image
To extract highlighted part
My desired output
Please someone help and give me a suggestion. My images looks like this. This is just sample one. I need to crop the bottom template part and do OCR. I have attached my desire output picture. Please have a look. How to implement it using python?
PS: The sheet size will differ and there may the chance of template to dislocate. but mostly it will be in lower left corner
Here's a potential approach:
Obtain binary image. We convert to grayscale, Gaussian blur, then Otsu's threshold
Fill in potential contours. We iterate through contours and filter using contour approximation to determine if they are rectangular.
Perform morphological operations. We morph open to remove non-rectangular contours using a rectangular kernel.
Filter and extract desired contour. Find contours and filter using contour approximation, aspect ratio, and contour area to isolate the desired contour. Then extract using Numpy slicing.
Binary image
Filled in contours
Morphological operation to remove non-rectangular contours
Desired contour highlighted in green
Extracted ROI
Code
import cv2
# Grayscale, blur, and threshold
image = cv2.imread('1.png')
original = image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (3,3), 0)
thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
# Fill in potential contours
cnts = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
peri = cv2.arcLength(c, True)
approx = cv2.approxPolyDP(c, 0.05 * peri, True)
if len(approx) == 4:
cv2.drawContours(thresh, [c], -1, (255,255,255), -1)
# Remove non rectangular contours
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (40,10))
close = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)
# Filtered for desired contour
cnts = cv2.findContours(close, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
peri = cv2.arcLength(c, True)
approx = cv2.approxPolyDP(c, 0.05 * peri, True)
x,y,w,h = cv2.boundingRect(approx)
aspect_ratio = w / float(h)
area = cv2.contourArea(approx)
if len(approx) == 4 and w > h and aspect_ratio > 2.75 and area > 45000:
cv2.drawContours(image, [c], -1, (36,255,12), -1)
ROI = original[y:y+h, x:x+w]
cv2.imwrite('image.png', image)
cv2.imwrite('ROI.png', ROI)
cv2.waitKey()

Categories