picture to be detected - different color
For a picture like above, the lines are blue, I use mask to get the contours of these lines, please see below code:
img = cv2.imread("./more5.png")#"https://i.stack.imgur.com/LiLPv.png"
blueLower = np.array([50,50,50])
blueUpper = np.array([130,255,255])
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
blue_mask = cv2.inRange(hsv, blueLower, blueUpper)
blue = cv2.bitwise_and(img ,img ,mask=blue_mask)
contours, hierarchy = cv2.findContours(preprocess(blue), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
Lines detected
complete code:
import numpy as np
import cv2
def preprocess(img):
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img_blur = cv2.GaussianBlur(img_gray, (5, 5), 1)
img_canny = cv2.Canny(img_blur, 50, 50) # edge cascade
kernel = np.ones((3, 3))
img_dilate = cv2.dilate(img_canny, kernel, iterations=1)
img_erode = cv2.erode(img_dilate, kernel, iterations=1)
return img_erode
# BGR to HSV to LAB to ...
def find_tip(points, convex_hull):
length = len(points)
indices = np.setdiff1d(range(length), convex_hull)
for i in range(2):
j = indices[i] + 2
if j > length - 1:
j = length - j
if np.all(points[j] == points[indices[i - 1] - 2]):
return tuple(points[j])
img = cv2.imread("./more5.png")
blueLower = np.array([50,50,50])
blueUpper = np.array([130,255,255])
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
blue_mask = cv2.inRange(hsv, blueLower, blueUpper)
blue = cv2.bitwise_and(img ,img ,mask=blue_mask)
contours, hierarchy = cv2.findContours(preprocess(blue), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
print(f"Contours size {len(contours)}") #
for cnt in contours:
peri = cv2.arcLength(cnt, True) #
approx = cv2.approxPolyDP(cnt, 0.004 * peri, True) #
hull = cv2.convexHull(approx, returnPoints=False)
sides = len(hull) #
if 6 > sides > 3 and sides + 2 == len(approx):
arrow_tip = find_tip(approx[:,0,:], hull.squeeze())
if arrow_tip:
cv2.drawContours(img, [cnt], -1, (0, 255, 0), 3)
cv2.circle(img, arrow_tip, 3, (0, 0, 255), cv2.FILLED)
cv2.imshow("Image", img)
cv2.waitKey(0)
However, if the line and the connected shape is the SAME color (please see the picture below), this method does NOT work anymore, how can I detect the lines from such a picture? Thanks for any ideas!
picture to be detected - same color
If your graph is a tree (it does not has closed circuits), then only the lines are connected to the same area on both sides.
If you paint the external area of the same color, only the lines will have the same color on both sides
Then you can do a convolution to have the lines averaged with the external space color, and the other lines averaged with the internal spaces color
import matplotlib.pyplot as plt
import numpy as np
#download the example image from this webpage
from PIL import Image as Pim
from io import BytesIO
import requests
photoURL="https://i.stack.imgur.com/LiLPv.png"
response = requests.get(photoURL)
image = np.array(Pim.open(BytesIO(response.content)).convert('L')) # Convert to greyscale
#mask to only 2 colors
gray=128
black=0
Monochrome = lambda t: gray if t!=image[0,0] else black #If color<>upper left color, then it is 0, else is 1
vfunc = np.vectorize(Monochrome)
image=vfunc(image)
#fill the external space with the same color as the upper left pixel
import cv2
UpperLeftPixel=(0,0)
AdjacentColor=255#White
imajeAdjacent=cv2.floodFill(image, None, UpperLeftPixel, AdjacentColor)
#convolution to average colors of lines with neighbor spaces
averagingFilter=np.ones(shape=(9,9))/9**2
import scipy
from scipy.signal import convolve2d as conv2d
image=conv2d(image,averagingFilter, mode="same")
plt.imshow(image)
plt.show()
Related
I have a mask for a dental x-ray here where all teeth are overlapping with each other. I want to count the number of teeth present in the image for that I want to separate overlapping tooths so I can use contour-based approach to count the number of tooths, I tried following approach but it is giving result like this
. how can I extract boundary for each tooth?
from skimage.feature import peak_local_max
from skimage.morphology import watershed
import matplotlib.pyplot as plt
from scipy import ndimage
import numpy as np
import cv2
def getImageEdge(input_image):
img_gray = input_image
image_black = np.zeros(shape=input_image.shape, dtype="uint8")
thresh = cv2.threshold(img_gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
thresh_copy = thresh.copy()
D = ndimage.distance_transform_edt(thresh_copy)
localMax = peak_local_max(D, indices = False, min_distance = 12, labels = thresh)
markers = ndimage.label(localMax, structure = np.ones((3, 3)))[0]
labels = watershed(-D, markers, mask = thresh_copy)
for label in np.unique(labels):
if label == 0:
continue
mask = np.zeros(img_gray.shape, dtype = "uint8")
mask[labels == label] = 255
contours, hierarchy = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(image_black, contours, -1, (255, 255, 255), 1)
return image_black
inputImage = cv2.imread("/content/dentalMask.bmp")
inputImage = cv2.cvtColor(inputImage, cv2.COLOR_BGR2GRAY)
outputImage = getImageEdge(inputImage)
plt.imshow(inputImage)
plt.show()
plt.imshow(outputImage)
plt.show()
EDITED:
Based on the answer from fmw42 I have added one more image where it is showing more overlapping and failed in simple thresholding and contour-based approach.
input
output
Given your example, the following works for me in Python/OpenCV by simply thresholding and getting the contours.
Input:
import cv2
import numpy as np
# read image
img = cv2.imread("teeth.png")
# convert img to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# threshold gray image
#thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY)[1]
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)[1]
# Get contours
cntrs = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cntrs = cntrs[0] if len(cntrs) == 2 else cntrs[1]
result = img.copy()
for c in cntrs:
cv2.drawContours(result, [c], -1, (0,0,255), 1)
count = len(cntrs)
print("")
print("count =",count)
print("")
# write results to disk
cv2.imwrite("teeth_thresh.png", thresh)
cv2.imwrite("tide_contours.png", result)
# display it
cv2.imshow("thresh", thresh)
cv2.imshow("result", result)
cv2.waitKey(0)
Contours:
Resulting Count:
count = 32
Without knowledge of the exact layout of teeth in a mouth, this task is impossible. No image processing technique can help.
Because in case of touching teeth, you can't tell two touching teeth from a two-rooted tooth.
I have tried to search and found two solutions that could solve the problem I have but are not working by my side. I want to remove the jagged edges or smooth the image attached. I have tried to implement the following codes
import cv2
import numpy as np
import os
os.chdir("F:/Examples")
image = cv2.imread("image1.jpeg")
blur = cv2.GaussianBlur(image, (21, 21), 0)
mask = np.zeros(image.shape[:2], dtype=np.uint8)
output = np.where(mask==np.array([255, 255, 255]), blur, image)
cv2.imwrite("", output)
The above codes returns unchanged image as if the applied mask didn't work. Here is the input image
I also tried to implement some solutions from different links including (How to blur the image according to segmentation map) without success. Any help will be acknowledged
While the edges or the external contour should be clear as in this image
I almost managed to solve it.
The main problem is that the contour is partially curved, and partially with straight lines.
Explanations are in the comments:
import numpy as np
import cv2
from scipy.interpolate import splprep, splev
im = cv2.imread('image1.jpeg')
bk = im.copy()
# Fill background with black color
cv2.floodFill(im, None, seedPoint=(1,1), newVal=(0, 0, 0), loDiff=(5, 5, 5), upDiff=(5, 5, 5))
gray = cv2.cvtColor(im, cv2.COLOR_RGB2GRAY)
ret, thresh_gray = cv2.threshold(gray, 5, 255, cv2.THRESH_BINARY)
# Use "open" morphological operation for removing small contours (noise)
thresh_gray = cv2.morphologyEx(thresh_gray, cv2.MORPH_OPEN, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5)));
# Background
bk[(thresh_gray > 0)] = 0
bk = cv2.morphologyEx(bk, cv2.MORPH_DILATE, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (20,20)));
#cv2.imshow('bk', bk)
# Foreground
fg = im.copy()
tmm_fg = cv2.morphologyEx(fg, cv2.MORPH_DILATE, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (20,20)));
fg_gray = cv2.cvtColor(fg, cv2.COLOR_RGB2GRAY)
fg[(fg_gray==0)] = tmm_fg[(fg_gray==0)]
#thresh_gray = cv2.morphologyEx(thresh_gray, cv2.MORPH_CLOSE, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (50,50)));
# Find contours (there is only one contour)
# _, contours, _ = cv2.findContours(thresh_gray, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) # 3 outputs works only in OpenCV 3. [-2:] is used for compatibility.
contours, _ = cv2.findContours(thresh_gray, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[-2:] # https://stackoverflow.com/questions/48291581/how-to-use-cv2-findcontours-in-different-opencv-versions
c = contours[0]
# Smooth contour
# https://agniva.me/scipy/2016/10/25/contour-smoothing.html
x,y = c.T
x = x.tolist()[0]
y = y.tolist()[0]
tck, u = splprep([x,y], u=None, s=1.0, per=1)
u_new = np.linspace(u.min(), u.max(), 150)
x_new, y_new = splev(u_new, tck, der=0)
res_array = [[[int(i[0]), int(i[1])]] for i in zip(x_new,y_new)]
smoothened = np.asarray(res_array, dtype=np.int32)
# Build a mask
mask = np.zeros_like(thresh_gray)
cv2.drawContours(mask, [smoothened], -1, 255, -1)
# For testig
test_im = cv2.cvtColor(thresh_gray, cv2.COLOR_GRAY2RGB)
cv2.drawContours(test_im, [smoothened], 0, (0, 255, 0), 1)
res = bk
res[(mask > 0)] = fg[(mask > 0)]
cv2.imshow('test_im', test_im)
cv2.imshow('res', res)
cv2.waitKey(0)
cv2.destroyAllWindows()
I want to detect apples by thresholding. For this, I've converted the image to HSV then and calculated the lower and upper limit for InRange() function. Getting a binary mask out of it. Since apples will be touching each others, I'm using watershed algorithm to separate them.
The input image looks like this:
After InRange() operation and erosion, the gray image looks like this:
Applying the watershed algorithm, the output looks like this:
The problem with it is that the bottom left apples are wrongly detected. There are only 2 apples and three contours are shown and also the circle of one of them is way too much bigger. Any help ?
Here is my code,
import cv2
import numpy as np
import imutils
from scipy import ndimage
from skimage.feature import peak_local_max
from skimage.morphology import watershed
img = cv2.imread('4.jpg')
img = imutils.resize(img, width=640)
# img = cv2.pyrMeanShiftFiltering(img, 21, 51)
hsv = cv2.cvtColor(img,cv2.COLOR_BGR2HSV)
lower_1 = np.array([0,50,20])
upper_1 = np.array([80,255,255])
mask1 = cv2.inRange(hsv, lower_1, upper_1)
lower_2 = np.array([160,50,20])
upper_2 = np.array([179,255,255])
mask2 = cv2.inRange(hsv, lower_2, upper_2)
gray = mask1+mask2
kernel = np.ones((7,7),np.uint8)
gray = cv2.erode(gray,kernel,iterations = 1)
# gray = cv2.morphologyEx(gray, cv2.MORPH_OPEN, kernel)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
D = ndimage.distance_transform_edt(thresh)
localMax = peak_local_max(D, indices=False, min_distance=20,
labels=thresh)
markers = ndimage.label(localMax, structure=np.ones((3, 3)))[0]
labels = watershed(-D, markers, mask=thresh)
print("[INFO] {} unique segments found".format(len(np.unique(labels)) - 1))
for label in np.unique(labels):
if label == 0:
continue
mask = np.zeros(gray.shape, dtype="uint8")
mask[labels == label] = 255
cnts = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
c = max(cnts, key=cv2.contourArea)
((x, y), r) = cv2.minEnclosingCircle(c)
if r > 25 and r < 55:
cv2.circle(img, (int(x), int(y)), int(r), (0, 255, 0), 2)
cv2.putText(img, "{}".format(round(r)), (int(x) - 10, int(y)),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)
cv2.imshow('thresh', thresh)
cv2.imshow('gray', gray)
cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
I have used the function named cv2.findContours() to draw the outline, however some places could not be identified which made the contours discontinuous. Here is my code and the result.
Thanks a lot if someone could teach me how to merge the contours of the same object?
import numpy as np
import os
import cv2
image = cv2.imread("/home/rafael/Desktop/2.jpg")
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
lower = np.array([50, 10, 10])
upper = np.array([120, 255, 255])
mask = cv2.inRange(hsv, lower, upper)
res = cv2.bitwise_and(image, image, mask = mask)
kernel = np.ones((5, 5), np.uint8)
d_im = cv2.dilate(mask, kernel, iterations = 1)
e_im = cv2.erode(d_im, kernel, iterations = 1)
src, contours, hierarchy = cv2.findContours(e_im, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
temp = []
tmp = []
num = 0
for i in range(len(contours)):
if len(contours[i]) < 35:
temp.append(i)
for i in temp:
del contours[i - num]
num = num + 1
cv2.drawContours(image, contours, -1, (0, 0, 255), 2)
cv2.imwrite('/home/rafael/Desktop/13.jpg', image)
Result
The full picture
The original Picture
I would like to increase the letters size in the image contains letter distributed in multiple lines and keep their coordinates the same also remove the lines in between the letters .
For Example:
I have applied Morphological Transformations. It helps but not enough for character recognition using tesseract OCR the problem in this image in letter 4
I have applied erode and cv2.MORPH_CLOSE
kernel = np.ones((2,2),np.uint8)
erosion = cv2.erode(img,kernel,iterations = 1)
kernel = np.ones((3,3),np.uint8)
closing = cv2.morphologyEx(erosion, cv2.MORPH_CLOSE, kernel)
and I get this output :
Edited
The Input Image
https://ibb.co/i2FdkT
https://ibb.co/igiQX8
The Complete code i used
import cv2
import numpy as np
img = cv2.imread('total_2.png',0)
edges = cv2.Canny(img,50,150,apertureSize = 3)
minLineLength=100
lines = cv2.HoughLinesP(image=edges,rho=1,theta=np.pi/180, threshold=100,lines=np.array([]), minLineLength=minLineLength,maxLineGap=80)
a,b,c = lines.shape
for i in range(a):
x = lines[i][0][0] - lines [i][0][2]
y = lines[i][0][1] - lines [i][0][3]
if x!= 0 and abs(y/x) <1:
cv2.line(img, (lines[i][0][0], lines[i][0][1]), (lines[i][0][2], lines[i][0][3]), (255, 255, 255), 1, cv2.LINE_AA)
se = cv2.getStructuringElement(cv2.MORPH_ELLIPSE , (4,3))
gray = cv2.morphologyEx(img, cv2.MORPH_CLOSE, se)
img = cv2.fastNlMeansDenoising(gray, None, 65, 5, 21)
img = cv2.threshold(img, 128, 255, cv2.THRESH_BINARY_INV)[1]
img = cv2.bitwise_not(img)
k1 = np.zeros((3, 3), np.uint8)
img = cv2.erode(img, k1, iterations = 1)
ret,img = cv2.threshold(img,0,255,0)
element = cv2.getStructuringElement(cv2.cv2.MORPH_RECT,(3,3))
kernel = np.ones((1,1),np.uint8)
#dilation = cv2.dilate(img,element,iterations = 1)
erosion = cv2.erode(img,element,iterations = 1)
kernel = np.ones((3,3),np.uint8)
#opening = cv2.morphologyEx(erosion, cv2.MORPH_OPEN, element)
closing = cv2.morphologyEx(erosion, cv2.MORPH_CLOSE, element)
Another approach with cv2.findContours
# threshold the gray image to binarize, and negate it
_,binary = cv2.threshold(image, 150, 255, cv2.THRESH_BINARY)
if flag :
binary = cv2.bitwise_not(binary)
# find external contours of all shapes
_,contours,_ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
# create a mask for floodfill function, see documentation
h,w= image.shape
mask = np.zeros((h+2,w+2), np.uint8)
# determine which contour belongs to a square or rectangle
for cnt in contours:
poly = cv2.approxPolyDP(cnt, 0.05*cv2.arcLength(cnt,True),True)
if len(poly) == 4:
# if the contour has 4 vertices then floodfill that contour with black color
cnt = np.vstack(cnt).squeeze()
_,binary,_,_ = cv2.floodFill(binary, mask, tuple(cnt[0]), 0)
# convert image back to original color
if flag:
binary = cv2.bitwise_not(binary)
The problem with cv2.findContours is that I have other images I apply the same preprocessing technique over them but they are unboxed and this approach destroys some letters.
For Example this Image :
My ideal solution would be increasing the letter sizes and also make the letter more clear