OpenCV mask unwanted peaks - opencv

I have a problem with the contour, i.e. the mask I get after that. I am bothered by all the peaks, I marked them with a red circle, which remain after the graph. Is there any easy way to get rid of them?
h, w = img.shape[:2]
mask = np.zeros((h, w), np.uint8)
# Transform to gray colorspace and threshold the image
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
# Search for contours and select the biggest one and draw it on mask
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
cntsSorted = sorted(contours, key=lambda x: cv2.contourArea(x), reverse=True)
for n in range(0,2):
cnt = cntsSorted[n]
cv2.drawContours(mask, [cnt], 0, 255, -1)
# Perform a bitwise operation
color = cv2.bitwise_and(img, img, mask=mask)
x, y, w, h = cv2.boundingRect(cnt)
crop = color[y:(y + h)-3, x+3:x + w]

Related

OpenCV Annulus region of interest

I defined annulus ROI selection function and i would like to find contours in this area. But contours pixel values are neighbors to the zero and out of masked areas equal to zero. Therefore contours couldn't be catch thresholded image.
How can i define annulus ROI or find the contours if function is ok
def annulusROI(img, center, innerR, outerR):
"""
img: Image matrix
center: ROI center point [px] (x,y tuple)
innerR: ROI inner radius [px]
outerR: ROI outer radius [px]
mode: Mask selection for white (255, 255, 255), for black (0, 0, 0) [BGR tuple]
return roi matrix and left-top start point coordinate
"""
outRoi, rectC = rectangleROI(img, center, outerR*2, outerR*2)
mask1 = np.zeros_like(outRoi)
mask2 = np.zeros_like(outRoi)
mask1 = cv2.circle(mask1, (round(outerR),round(outerR)), innerR, (255, 255, 255), -1)
mask2 = cv2.circle(mask2, (round(outerR),round(outerR)), outerR, (255, 255, 255), -1)
mask = cv2.subtract(mask2, mask1)
roi = cv2.bitwise_and(outRoi, mask)
return roi, (center[0]-outerR, center[1]-innerR)
contour
thresholded
roi returned image
After thresholding and before getting the contours you can separate the region of interest from the outer area. Or even better you can cut your region of interest after thresholding, not before. Finally you can filter out the releavant contours by area size.
import cv2
# get image threshold
img = cv2.imread("img.png")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(gray, 64, 255, 0)
# separate annulus from outer area
h, w, _ = img.shape
center = (round(w / 2), round(h / 2))
innerR = 246
outerR = 306
cv2.circle(thresh, center, innerR, 255)
cv2.circle(thresh, center, outerR, 255)
# filter contours by relevant area size
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cc = [c for c in contours if 100 < cv2.contourArea(c) < 5000]
cv2.drawContours(img, cc, -1, (0, 0, 255))
cv2.imwrite("out.png", img)
Result:

Opencv: merge intersected contours into bigger rectangles

I would like to draw a big rectangle around the red sentences red as in the attached image. I was able to draw multiple rectangles around each character. Is it possible to merge intersected rectangles into a single big rectangle?
Here is my code:
image = cv2.imread('test-opencv.png')
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
lower = np.array([0, 50, 20])
upper = np.array([5, 255, 255])
mask = cv2.inRange(hsv, lower, upper)
cv2.imwrite('mask3.jpg', hsv)
# Gen lower mask (0-5) and upper mask (175-180) of RED
mask1 = cv2.inRange(hsv, (0, 50, 20), (5, 255, 255))
mask2 = cv2.inRange(hsv, (175, 50, 20), (180, 255, 255))
# Merge the mask and crop the red regions
mask = cv2.bitwise_or(mask1, mask2)
croped = cv2.bitwise_and(image, image, mask=mask)
ret, thresh = cv2.threshold(mask, 0, 255, 0)
contours, hierarchy = cv2.findContours(thresh, 3, 5)
for cnt in contours:
x, y, w, h = cv2.boundingRect(cnt)
image = cv2.rectangle(image, (x, y), (x+w+10, y+h+10), (0, 255, 0), 2)
cv2.imshow("mask", image)
cv2.waitKey()
Here is also the original image

opencv findContours, contourArea value not consistant

I have a test image (see the 1st image below), and a very simple code to blur and make canny edge detection of this image, then use findcontours to get contours.
image = cv2.imread("testimage.jpg")
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(image, (11,11), 0)
cv2.imshow("Blurred", blurred)
canny = cv2.Canny(blurred, 50, 130)
cv2.imshow("Canny", canny)
(_, conts, _) = cv2.findContours(canny.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
img = image.copy()
for c in conts:
M = cv2.moments(c)
cX = int(M["m10"] / M["m00"])
cY = int(M["m01"] / M["m00"])
cv2.drawContours(img, [c], -1, (0,255,0), 1)
cv2.putText(img, "area:"+str(cv2.contourArea(c)), (cX-20,cY-20),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2)
cv2.imshow("Contours", img)
cv2.waitKey(0)
I put the value of each contour on the image. As you may see, the contours look similar, but there is one contour with area value extremely low (only 16.0).
What might be the reason for this? And how to get consistent values among these contours?

How to extract foreground text from an image?

How to use opencv to perfectly extract the digits text from the below image? The color of the text are dynamic.
It's simple. The follow code can be highly optimized (I did it fast - #Silencer could do this) and work (tested) also with other images (for some you will have to tweak some values).
import cv2
import numpy as np
# import image
image = cv2.imread('image.png')
cv2.imshow('original', image)
cv2.waitKey(0)
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
cv2.imshow('hsv', hsv[:, :, 1])
cv2.waitKey(0)
# this calculate the histogram of the image you input
# if this is under/below a certain value (which depend of the colors in the image), a certain thresh will be choosed among another
hist, bins = np.histogram(hsv.ravel(), 256, [0, 256])
print(hist[-1])
if hist[-1] > 15000:
# binary
ret, thresh = cv2.threshold(hsv[:, :, 0], 55, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)
cv2.imshow('second', thresh)
cv2.waitKey(0)
# dilation
kernel = np.ones((1, 1), np.uint8)
img_dilation = cv2.dilate(thresh, kernel, iterations=1)
cv2.imshow('dilated', img_dilation)
cv2.waitKey(0)
# find contours
im2, ctrs, hier = cv2.findContours(img_dilation.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# sort contours
sorted_ctrs = sorted(ctrs, key=lambda ctr: cv2.boundingRect(ctr)[0])
for i, ctr in enumerate(sorted_ctrs):
# Get bounding box
x, y, w, h = cv2.boundingRect(ctr)
# Getting ROI
roi = image[y:y + h, x:x + w]
# show ROI
# cv2.imshow('segment no:'+str(i),roi)
cv2.rectangle(image, (x, y), (x + w, y + h), (0, 255, 0), 2)
# cv2.waitKey(0)
if w > 15 and h > 15:
cv2.imwrite('roi{}.png'.format(i), roi)
cv2.imshow('marked areas', image)
cv2.waitKey(0)
else:
# binary
ret, thresh = cv2.threshold(hsv[:, :, 0], 55, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
cv2.imshow('second', thresh)
cv2.waitKey(0)
# dilation
kernel = np.ones((1, 1), np.uint8)
img_dilation = cv2.dilate(thresh, kernel, iterations=1)
cv2.imshow('dilated', img_dilation)
cv2.waitKey(0)
# find contours
im2, ctrs, hier = cv2.findContours(img_dilation.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# sort contours
sorted_ctrs = sorted(ctrs, key=lambda ctr: cv2.boundingRect(ctr)[0])
for i, ctr in enumerate(sorted_ctrs):
# Get bounding box
x, y, w, h = cv2.boundingRect(ctr)
# Getting ROI
roi = image[y:y + h, x:x + w]
# show ROI
# cv2.imshow('segment no:'+str(i),roi)
cv2.rectangle(image, (x, y), (x + w, y + h), (0, 255, 0), 2)
# cv2.waitKey(0)
if w > 15 and h > 15:
cv2.imwrite('roi{}.png'.format(i), roi)
cv2.imshow('marked areas', image)
cv2.waitKey(0)
I would look at this post from pyimage search which essentially does this for you: https://www.pyimagesearch.com/2017/07/17/credit-card-ocr-with-opencv-and-python/. There are definitely classifiers that work better but it's a good starting point.

Character segmentation in photo opencv

I have the above number plate image with me. My goal is to segment each character individually and pass into my neural network. I have tried to find countours and use bounding rectangles to segment these characters using the following code:
img = cv2.imread('download.jpeg')
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gray_img = cv2.GaussianBlur(gray_img, (5,5), 0)
ret, im_th = cv2.threshold(gray_img, 90, 255, cv2.THRESH_BINARY_INV)
im_th = cv2.adaptiveThreshold(gray_img, 255,
cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY,75, 10)
im_th = cv2.bitwise_not(im_th)
ctrs, hier = cv2.findContours(im_th.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(img, ctrs, -1, (0,255,0), 3)
rects = [cv2.boundingRect(ctr) for ctr in ctrs]
print len(rects)
for rect in rects:
cv2.rectangle(img,(rect[0], rect[1]), (rect[0] + rect[2], rect[1] + rect[3]), (0,255,0),3)
length = int(rect[3] * 1.6)
pt1 = int(rect[1] + rect[3] // 2 - length // 2)
pt2 = int(rect[0] + rect[2] // 2 - length // 2)
roi = img[pt1:pt1+length, pt2:pt2+length]
The above code creates regions that include bounding rectangles other than the characters. Although I could manually filter out these regions, it would vary from image to image. How would I go about this if I need to only extract the regions with characters?
#read image
img = cv2.imread('input_image.png')
#grayscale
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
cv2.waitKey(0)
#binarize
ret,thresh = cv2.threshold(gray,127,255,cv2.THRESH_BINARY_INV)
cv2.waitKey(0)
#find contours
im2,ctrs, hier = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
#sort contours
sorted_ctrs = sorted(ctrs, key=lambda ctr: cv2.boundingRect(ctr)[0])
for i, ctr in enumerate(sorted_ctrs):
# Get bounding box
x, y, w, h = cv2.boundingRect(ctr)
# Getting ROI
roi = img[y:y+h, x:x+w]
# show ROI
#cv2.imwrite('roi_imgs.png', roi)
cv2.imshow('charachter'+str(i), roi)
cv2.rectangle(img,(x,y),( x + w, y + h ),(90,0,255),2)
cv2.waitKey(0)
cv2.imshow('marked areas',img)
cv2.waitKey(0)

Resources