Why to reshape MSER contours before detecting texts? - python

I am using MSER from opencv-python to detect text using the code from this stackoverflow question. Can anyone help me understand why the contour p is being reshaped to (-1, 1, 2) before computing the convex hull of the objects?
The code is as below:
import cv2
import numpy as np
#Create MSER object
mser = cv2.MSER_create()
#Your image path i-e receipt path
img = cv2.imread('/home/rafiullah/PycharmProjects/python-ocr-master/receipts/73.jpg')
#Convert to gray scale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
vis = img.copy()
#detect regions in gray scale image
regions, _ = mser.detectRegions(gray)
hulls = [cv2.convexHull(p.reshape(-1, 1, 2)) for p in regions]
cv2.polylines(vis, hulls, 1, (0, 255, 0))
cv2.imshow('img', vis)
cv2.waitKey(0)
mask = np.zeros((img.shape[0], img.shape[1], 1), dtype=np.uint8)
for contour in hulls:
cv2.drawContours(mask, [contour], -1, (255, 255, 255), -1)
#this is used to find only text regions, remaining are ignored
text_only = cv2.bitwise_and(img, img, mask=mask)
cv2.imshow("text only", text_only)
cv2.waitKey(0)

It doesn't matter if you reshape or not.
The reshaping is unnecessary. cv2.convexHull() can take either input format. The following images show that the results are the same whether the contours in regions are reshaped or not.
hulls = [cv2.convexHull(p.reshape(-1, 1, 2)) for p in regions]
hulls1 = [cv2.convexHull(p) for p in regions]
This is how the p contour changes when it is reshaped:
>>> p
array([[305, 382],
[306, 382],
[308, 380],
[309, 380]...
>>> p.reshape(-1, 1, 2)
array([[[305, 382]],
[[306, 382]],
[[308, 380]],
[[309, 380]]...

Related

OpenCV - Can't find correct contours in similar images

the task I want to do looks pretty simple: I take as input several images with an object centered in the photo and a little color chart needed for other purposes. My code normally works for the majority of the cases, but sometimes fails miserably and I just can't understand why.
For example (these are the source images), it works correctly on this https://imgur.com/PHfIqcb but not on this https://imgur.com/qghzO3V
Here's the code of the interested part:
img = cv2.imread(path)
height, width, channel = img.shape
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
kernel = np.ones((31, 31), np.uint8)
dil = cv2.dilate(gray, kernel, iterations=1)
_, th = cv2.threshold(dil, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
th_er1 = cv2.bitwise_not(th)
_, contours, _= cv2.findContours(th_er1, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
areas = [cv2.contourArea(c) for c in contours]
max_index = np.argmax(areas)
cnt=contours[max_index]
x,y,w,h = cv2.boundingRect(cnt)
After that I'm just going to crop the image accordingly to the given results (getting the biggest rectangle contour), basically cutting off the photo only the main object.
But as I said, using very similar images sometimes works and sometimes not.
Thank you in advance.
maybe you could try not using otsu's method, and just set threshold manually, if it's possible... ;)
You can use the Canny edge detector. In the two images, there is a good threshold value to isolate the object in the center of the image. After applying the threshold, we blur the results and apply the Canny edge detector before finding the contours:
import cv2
import numpy as np
def process(img):
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(img_gray, 190, 255, cv2.THRESH_BINARY_INV)
img_blur = cv2.GaussianBlur(thresh, (3, 3), 1)
img_canny = cv2.Canny(img_blur, 0, 0)
kernel = np.ones((5, 5))
img_dilate = cv2.dilate(img_canny, kernel, iterations=1)
return cv2.erode(img_dilate, kernel, iterations=1)
def get_contours(img):
contours, hierarchies = cv2.findContours(process(img), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
cnt = max(contours, key=cv2.contourArea)
cv2.drawContours(img, [cnt], -1, (0, 255, 0), 30)
x, y, w, h = cv2.boundingRect(cnt)
cv2.rectangle(img, (x, y), (x + w, y + h), (0, 0, 255), 30)
img = cv2.imread("image.jpeg")
get_contours(img)
cv2.imshow("Result", img)
cv2.waitKey(0)
Input images:
Output images:
The green outlines are the contours of the objects, and the red outlines are the bounding boxes of the objects.

How do I detect the corners of a low resolution shape?

I have a low resolution concave quadrilateral cursor. My goal is to find the point he points to. So I thought of finding 4 corners and getting the farthest corner from each other. usually works fine but doesn't always detect corners accurately. Using blur gives little results, but I'm not sure it's enough. What would you suggest I do to improve my results?
cursor = cv.bitwise_and(captureHSV, captureHSV, mask=mask)
#resizeCurs = cv.resize(cursor, (0, 0), fx=0.60, fy=0.60)
#blurCurs = blur = cv.blur(cursor, (3,3))
grayCurs = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
corners = cv.goodFeaturesToTrack(grayCurs, 4, 0.01, 1)
corners = np.int0(corners)
coordList = []
for corner in corners:
x, y = corner.ravel()
cv.circle(cursor, (x, y), 1, (255, 0, 0), 1)
coordList.append([x, y])
After detecting the canny edges of your image, converted to grayscale & blurred beforehand of course, you can dilate and erode the edges to get rid of noisy bumps and fill in little gaps.
But that wouldn't be enough to smooth out the results to 4 points, you'll need to use the cv2.approxPolyDP method to approximate the resulting contours.
Here is how it might go:
import cv2
import numpy as np
def process(img):
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img_blur = cv2.GaussianBlur(img_gray, (7, 7), 0)
img_canny = cv2.Canny(img_blur, 50, 50)
kernel = np.ones((0, 0))
img_dilate = cv2.dilate(img_canny, kernel, iterations=1)
img_erode = cv2.erode(img_dilate, kernel, iterations=1)
return img_erode
def get_contours(img):
contours, hierarchies = cv2.findContours(process(img), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
for cnt in contours:
if cv2.contourArea(cnt) > 10:
peri = cv2.arcLength(cnt, True)
approx = cv2.approxPolyDP(cnt, peri * 0.04, True)
cv2.drawContours(img, approx, -1, (0, 0, 255), 8)
img1 = cv2.imread("cursor1.png")
img2 = cv2.imread("cursor2.png")
get_contours(img1)
get_contours(img2)
cv2.imshow("Image 1", img1)
cv2.imshow("Image 2", img2)
cv2.waitKey(0)
Output:
Where the red dots are the parts that were drawn on by the program.

How to recolor image based on edge detection(canny)

I have a script, which is using for recoloring room walls based on color similarity. But I need to recolor a wall based on edge detection.
import cv2
import numpy as np
import sys
from PIL import Image
import numpy as np
from hex_to_rgb import color
def recolor(file_path, celor, lower_color, upper_color):
img = cv2.imread(file_path)
res = img.copy()
rgb = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
r2, g2, b2 = color(celor)
mask = cv2.inRange(rgb, lower_color, upper_color)
mask = mask/255
mask = mask.astype(np.bool)
res[:,:,:3][mask] = [b2, g2, r2] # opencv uses BGR
im_rgb = cv2.cvtColor(res, cv2.COLOR_BGR2RGB)
return im_rgb
file_path --> image
celor --> color, which you want to recolor
lower_color --> lower values of RGB
upper_color --> upper values of RGB
I am using Sobel edge detection to solve this problem. I tried with Canny edge detection also but it didn't give good results.
After edge detection, I applied the threshold to the image and found contours in the image. The problem here is that I am coloring the contour with the maximum area in this case. You will have to figure out a way to choose the contour you want to color.
img = cv2.imread("colourWall.jpg")
cImg = img.copy()
img = cv2.blur(img, (5, 5))
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
scale = 1
delta = 0
ddepth = cv.CV_16S
grad_x = cv.Sobel(gray, ddepth, 1, 0, ksize=3, scale=scale, delta=delta, borderType=cv.BORDER_DEFAULT)
grad_y = cv.Sobel(gray, ddepth, 0, 1, ksize=3, scale=scale, delta=delta, borderType=cv.BORDER_DEFAULT)
abs_grad_x = cv.convertScaleAbs(grad_x)
abs_grad_y = cv.convertScaleAbs(grad_y)
grad = cv.addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 0)
ret, thresh = cv2.threshold(grad, 10, 255, cv2.THRESH_BINARY_INV)
c, h = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
areas = [cv2.contourArea(c1) for c1 in c]
maxAreaIndex = areas.index(max(areas))
cv2.drawContours(cImg, c, maxAreaIndex, (255, 0, 0), -1)
plt.imshow(cImg)
plt.show()
Result:

How can i get the rgb color values from inside of a contour in image using opencv?

I know here already some questions were asked but they did't help me to solve my problem. I will appreciate any help to solve my problem.
I'm new to opencv.
I have an image and apply some code to get contours from image. Now i want to get the RGB color values from detected contours. How can i do that?
I do research on it and find that it could be solved by using contours so i try to implement contours and now finally i want to get the color values of the contours.
Here is my Code:
import cv2
import numpy as np
img = cv2.imread('C:/Users/Rizwan/Desktop/example_strip1.jpg')
img_hsv = cv2.cvtColor(255-img, cv2.COLOR_BGR2HSV)
lower_red = np.array([40, 20, 0])
upper_red = np.array([95, 255, 255])
mask = cv2.inRange(img_hsv, lower_red, upper_red)
contours, _ = cv2.findContours(mask, cv2.RETR_TREE,
cv2.CHAIN_APPROX_SIMPLE)
color_detected_img = cv2.bitwise_and(img, img, mask=mask)
print(len(contours))
for c in contours:
area = cv2.contourArea(c)
x, y, w, h = cv2.boundingRect(c)
ax = cv2.rectangle(img, (x, y), (x + w, y + h), (0, 0, 0), 2)
rect = cv2.minAreaRect(c)
box = cv2.boxPoints(rect)
box = np.int0(box)
im = cv2.drawContours(color_detected_img, [box], -1, (255, 0, 0), 2)
cv2.imshow("Cropped", color_detected_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
I expect the output should be the RGB values of the detected color inside the contours.
As asked in the comments, here's a possible solution to extract the BGR(!) values from the pixels of an image inside a before found contour. The proper detecting of the desired, colored stripes is omitted here as also discussed in the comments.
Having an image and a filled mask of a contour, for example from cv2.drawContours, we can simply use NumPy's boolean array indexing by converting the (most likely uint8) mask to an bool_ array.
Here's a short code snippet, that uses NumPy's savetxt to store all values in some txt file:
import cv2
import numpy as np
# Some dummy image
img = np.zeros((100, 100, 3), np.uint8)
img = cv2.rectangle(img, (0, 0), (49, 99), (255, 0, 0), cv2.FILLED)
img = cv2.rectangle(img, (50, 0), (99, 49), (0, 255, 0), cv2.FILLED)
img = cv2.rectangle(img, (50, 50), (99, 99), (0, 0, 255), cv2.FILLED)
# Mask of some dummy contour
mask = np.zeros((100, 100), np.uint8)
mask = cv2.fillPoly(mask, np.array([[[20, 20], [30, 70], [70, 50], [20, 20]]]), 255)
# Show only for visualization purposes
cv2.imshow('img', img)
cv2.imshow('mask', mask)
# Convert mask to boolean array
mask = np.bool_(mask)
# Use boolean array indexing to get all BGR values from img within mask
values = img[mask]
# For example, save values to txt file
np.savetxt('values.txt', values)
cv2.waitKey(0)
cv2.destroyAllWindows()
The dummy image looks like this:
The dummy contour mask looke like this:
The resulting values.txt has some >1000 entries, please check yourself. Attention: Values are BGR values; e.g. prior converting the image to RGB is needed to get RGB values.
Hope that helps!

How to obtain combined convex Hull of multiple separate shapes

I have 2 shapes (pic 1) and need to find one convexHull of both of them combined (pic2). More precisely I am interested in obtaining external corners (purple circles pic 2). The shapes are detached. The shape I trace is a square sheet of transparent plastic with two color stripes on the side. Stripes are very easy to trace (inRange).
One quick and dirty method I am thinking is to connect centers of the stripes with a white line and then obtain convexHull. I am also thinking on concatenating lists of vertexes of both shapes and obtain combined convexHull but I am not certain if this method will crash the convexHull function.
Is there any more elegant way to resolve this problem?
Please help
Pic 1
Pic 2
Issue resolved.
Works like a charm. Concatenating points of separate shapes don't crash convexHull.
I posted the code on GitHub https://github.com/wojciechkrukar/OpenCV/blob/master/RectangleDetector/RectangleDetector.ipynb
This is the result:
Here is the most important chunk of code:
_ , contours,hier = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
#size of contour points
length = len(contours)
#concatinate poits form all shapes into one array
cont = np.vstack(contours[i] for i in range(length))
hull = cv2.convexHull(cont)
uni_hull = []
uni_hull.append(hull) # <- array as first element of list
cv2.drawContours(image,uni_hull,-1,255,2);
import numpy as np
import cv2
img = cv2.imread('in1.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
(_, thresh) = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)
thresh = ~thresh
points = np.column_stack(np.where(thresh.transpose() > 0))
hull1 = cv2.convexHull(points)
result1 = cv2.polylines(img.copy(), [hull1], True, (0,0,255), 2)
cv2.imshow('result1', result1)
points2 = np.fliplr(np.transpose(np.nonzero(thresh)))
approx = cv2.convexHull(points2)
result2 = cv2.polylines(img.copy(), [approx], True, (255,255,0), 2)
cv2.imshow('result2', result2)
(contours, _) = cv2.findContours(thresh, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
points3 = [pt[0] for ctr in contours for pt in ctr]
points3 = np.array(points3).reshape((-1,1,2)).astype(np.int32)
hull3 = cv2.convexHull(points3)
result3 = cv2.drawContours(img.copy(), [hull3], -1, (0,255,0), 1, cv2.LINE_AA)
cv2.imshow('result3', result3)
points4 = list(set(zip(*np.where(img >= 128)[1::-1])))
points4 = np.array(points4).reshape((-1,1,2)).astype(np.int32)
hull4 = cv2.convexHull(points4)
result4 = cv2.drawContours(img.copy(), [hull4], -1, (0,255,255), 1, cv2.LINE_AA)
cv2.imshow('result4', result4)
result = np.hstack([result1, result2, result3, result4])
cv2.imwrite('result.jpg', result)

Categories

Resources