I found hough line implemantation in github.
And I try this code my computer.
When I plot hough space with matplot, there is build-up at shown in picture.
This cause wrong lines detection in image.
def hough_line(img, angle_step=1, lines_are_white=True, value_threshold=5):
# Rho and Theta ranges
thetas = np.deg2rad(np.arange(-90.0, 90.0, angle_step))
width, height = img.shape
diag_len = int(round(math.sqrt(width * width + height * height)))
rhos = np.linspace(-diag_len, diag_len, diag_len * 2)
# Cache some resuable values
cos_t = np.cos(thetas)
sin_t = np.sin(thetas)
num_thetas = len(thetas)
# Hough accumulator array of theta vs rho
accumulator = np.zeros((2 * diag_len, num_thetas), dtype=np.uint8)
# (row, col) indexes to edges
are_edges = img > value_threshold if lines_are_white else img < value_threshold
y_idxs, x_idxs = np.nonzero(are_edges)
# Vote in the hough accumulator
for i in range(len(x_idxs)):
x = x_idxs[i]
y = y_idxs[i]
for t_idx in range(num_thetas):
# Calculate rho. diag_len is added for a positive index
rho = diag_len + int(round(x * cos_t[t_idx] + y * sin_t[t_idx]))
accumulator[rho, t_idx] += 1
return accumulator, thetas, rhos
def show_hough_line(img, accumulator, thetas, rhos, save_path=None):
import matplotlib.pyplot as plt
fig, ax = plt.subplots(1, 2, figsize=(10, 10))
ax[0].imshow(img, cmap=plt.cm.gray)
ax[0].set_title('Input image')
ax[0].axis('image')
ax[1].imshow(
accumulator, cmap='jet',
extent=[np.rad2deg(thetas[-1]), np.rad2deg(thetas[0]), rhos[-1], rhos[0]])
ax[1].set_aspect('equal', adjustable='box')
ax[1].set_title('Hough transform')
ax[1].set_xlabel('Angles (degrees)')
ax[1].set_ylabel('Distance (pixels)')
ax[1].axis('image')
# plt.axis('off')
if save_path is not None:
plt.savefig(save_path, bbox_inches='tight')
plt.show()
And I run this code,
img = cv2.imread('05102009081.png')
img_dark = cv2.imread('05102009081-1.png')
img1 = cv2.bitwise_and(img, img_dark)
gray = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (5, 5), 0)
edges = cv2.Canny(blur, 100, 200)
accumulator, thetas, rhos = hough_line(edges)
#Thresholding with 100
a = (accumulator > 100).astype(int)
accumulator = accumulator * a
show_hough_line(edges, accumulator, thetas, rhos)
This is result without thresholding
This is result after thresholding
As you see when i apply thresholding this edges, There is some peak point approximatly 55. degree and between 10-50. pixels in the hough space, This cause wrong lines in image.
What is the problem ?
How can i solve this problem ?
Thanks in advance.
Related
I implemented the following code, to match nodes in a plant, using as template a small cropped image.
img_rgb = cv2.imread('Exp.2 - Florestópolis, 35DAE, 2017-2018, T4, R2, P4.jpg')
img_rgb = cv2.medianBlur(img_rgb, 7)
img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY)
template_image = cv2.imread('TemplateNode.jpg',0)
template_image = cv2.medianBlur(template_image, 5)
width, height = template_image.shape[::-1]
res = cv2.matchTemplate(img_gray, template_image, cv2.TM_CCOEFF_NORMED)
threshold = 0.6
locations = np.where(res >= threshold)
for position_tuple in zip(*locations[::-1]):
cv2.rectangle(img_rgb, position_tuple, (position_tuple[0] + width, position_tuple[1] + height), (0,255,0), 1)
However, it generates too many bounding boxes (tuples), around a same location, as show:
So, there is a work around to solve this issue?
Here is one possible approach. Code is unlikely an efficient one. I think k-means clustering from some package may work better. Idea is to group together locations, which are too close:
def group_locations(locations, min_radius):
x = locations[:, 0][ : , None]
dist_x = x - x.T
y = locations[:, 1][ : , None]
dist_y = y - y.T
dist = np.sqrt(dist_x**2 + dist_y**2)
np.fill_diagonal(dist, min_radius+1)
too_close = np.nonzero(dist <= min_radius)
groups = []
points = np.arange(len(locations))
i = 0
j = 0
while i < len(points):
groups.append([points[i]])
for j in range(len(too_close[0])):
if too_close[0][j] == points[i]:
groups[i].append(too_close[1][j])
points = np.delete(points, np.nonzero(points == too_close[1][j]))
i += 1
new_locations = []
for group in groups:
new_locations.append(np.mean(locations[group], axis=0))
return np.array(new_locations)
So you take your locations and group them before plotting:
locations = []
size = 600
for _ in range(50):
locations.append((random.randint(0, size), random.randint(0, size)))
locations = np.array(locations)
min_radius = 50
new_locations = group_locations(locations, min_radius)
#I run it second time as sometimes locations form chains which are longer than radius
new_locations = group_locations(new_locations, min_radius)
plt.scatter(locations[:, 0], locations[:, 1], c='blue', label='Original locations')
plt.scatter(new_locations[:, 0], new_locations[:, 1], c='red', marker='x', label='New grouped locations')
plt.legend()
plt.show()
actually tried it with image provided
img_rgb = cv2.imread('obsvu.jpg')
img_rgb = cv2.medianBlur(img_rgb, 7)
img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY)
template_image = cv2.imread('template.jpg',0)
template_image = cv2.medianBlur(template_image, 5)
width, height = template_image.shape[::-1]
res = cv2.matchTemplate(img_gray, template_image, cv2.TM_CCOEFF_NORMED)
threshold = 0.6
locations = np.where(res >= threshold)
new_locations = group_locations(np.array(locations).T, 50).T
for position_tuple in zip(*new_locations.astype(int)[::-1]):
cv2.rectangle(img_rgb, position_tuple, (position_tuple[0] + width, position_tuple[1] + height), (0,255,0), 5)
Original locations: 723
New locations: 6 (yep, template selection not the best)
I want to add circles at random spots on an image. The code adds the circle to the image
Attached is the code that I tried:
img = np.zeros([100,100],dtype=np.uint8)
img.fill(20)
def createCircle(width,height , rad ):
w = random.randint(1, height)
h = random.randint(1, height)
center = [int(w), int(h)]
radius = rad
Y, X = np.ogrid[:height, :width]
dist_from_center = np.sqrt((X - center[0])**2 + (Y-center[1])**2)
mask = dist_from_center <= radius
return mask
def addCircle(test_image):
m = createCircle(width = 100, height = 100 , rad = 8 )
masked_img = test_image.copy()
masked_img[~m] = 0
return masked_img
im = addCircle(test_image=img)
plt.imshow(im)
plt.show()
im1 = addCircle(test_image = im)
plt.imshow(im1)
plt.show()
When i apply addCircle function on the image img, it adds the circle to the image but when I apply addCircle function to im it does not add another circle to the image.
I want to add 4 circles to the same image at random places on the image but as of now I am only able to add one circle, the code doesn't work after that.
As mentioned in the comments... Your problem seems to be in this line masked_img[~m] = 0.
def addCircle(test_image):
m = createCircle(width = 100, height = 100 , rad = 8 )
masked_img = test_image.copy()
masked_img[m] = 0
return masked_img
# im = addCircle(test_image=img)
# plt.imshow(im)
# plt.show()
for i in range(4):
img = addCircle(test_image=img)
plt.imshow(img)
plt.show()
I got two detected contours in an image and need the diameter between the two vertical-edges of the top contour and the diameter between the vertical-edges of the lower contour. I achieved this with this code.
import cv2
import numpy as np
import math, os
import imutils
img = cv2.imread("1.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
gray = cv2.GaussianBlur(gray, (7, 7), 0)
edges = cv2.Canny(gray, 200, 100)
edges = cv2.dilate(edges, None, iterations=1)
edges = cv2.erode(edges, None, iterations=1)
cnts = cv2.findContours(edges.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
# sorting the contours to find the largest and smallest one
c1 = max(cnts, key=cv2.contourArea)
c2 = min(cnts, key=cv2.contourArea)
# determine the most extreme points along the contours
extLeft1 = tuple(c1[c1[:, :, 0].argmin()][0])
extRight1 = tuple(c1[c1[:, :, 0].argmax()][0])
extLeft2 = tuple(c2[c2[:, :, 0].argmin()][0])
extRight2 = tuple(c2[c2[:, :, 0].argmax()][0])
# show contour
cimg = cv2.drawContours(img, cnts, -1, (0,200,0), 2)
# set y of left point to y of right point
lst1 = list(extLeft1)
lst1[1] = extRight1[1]
extLeft1 = tuple(lst1)
lst2 = list(extLeft2)
lst2[1] = extRight2[1]
extLeft2= tuple(lst2)
# compute the distance between the points (x1, y1) and (x2, y2)
dist1 = math.sqrt( ((extLeft1[0]-extRight1[0])**2)+((extLeft1[1]-extRight1[1])**2) )
dist2 = math.sqrt( ((extLeft2[0]-extRight2[0])**2)+((extLeft2[1]-extRight2[1])**2) )
# draw lines
cv2.line(cimg, extLeft1, extRight1, (255,0,0), 1)
cv2.line(cimg, extLeft2, extRight2, (255,0,0), 1)
# draw the distance text
font = cv2.FONT_HERSHEY_SIMPLEX
fontScale = 0.5
fontColor = (255,0,0)
lineType = 1
cv2.putText(cimg,str(dist1),(155,100),font, fontScale, fontColor, lineType)
cv2.putText(cimg,str(dist2),(155,280),font, fontScale, fontColor, lineType)
# show image
cv2.imshow("Image", img)
cv2.waitKey(0)
Now I would also need the angle of the slope lines on the bottom side of the upper contour.
Any ideas how I can get this? Is it possible using contours?
Or is it necessary to use HoughLinesP and sort the regarding lines somehow?
And continued question: Maybe its also possible to get function which describes parabola slope of that sides ?
Thanks alot for any help!
There are several ways to obtain just the slopes. In order to know the slope, we can can use cv2.HoughLines to detect the bottom horizontal line, detect to end points of that line and from those, obtain the slopes. As an illustration,
lines = cv2.HoughLines(edges, rho=1, theta=np.pi/180, threshold=int(dist2*0.66) )
on edges in your code gives 4 lines, and if we force the angle to be horizontal
for line in lines:
rho, theta = line[0]
# here we filter out non-horizontal lines
if abs(theta - np.pi/2) > np.pi/180:
continue
a = np.cos(theta)
b = np.sin(theta)
x0 = a*rho
y0 = b*rho
x1 = int(x0 + 1000*(-b))
y1 = int(y0 + 1000*(a))
x2 = int(x0 - 1000*(-b))
y2 = int(y0 - 1000*(a))
cv2.line(img_lines,(x1,y1),(x2,y2),(0,0,255),1)
we get:
For the extended question concerns with the parabolas, we first compose a function that returns the left and right points:
def horizontal_scan(gray_img, thresh=50, start=50):
'''
scan horizontally for left and right points until we met an all-background line
#param thresh: threshold for background pixel
#param start: y coordinate to start scanning
'''
ret = []
thickness = 0
for i in range(start,len(gray_img)):
row = gray_img[i]
# scan for left:
left = 0
while left < len(row) and row[left]<thresh:
left += 1
if left==len(row):
break;
# scan for right:
right = left
while right < len(row) and row[right] >= thresh:
right+=1
if thickness == 0:
thickness = right - left
# prevent sudden drop, error/noise
if (right-left) < thickness//5:
continue
else:
thickness = right - left
ret.append((i,left,right))
return ret
# we start scanning from extLeft1 down until we see a blank line
# with some tweaks, we can make horizontal_scan run on edges,
# which would be simpler and faster
horizontal_lines = horizontal_scan(gray, start = extLeft1[1])
# check if horizontal_line[0] are closed to extLeft1 and extRight1
print(horizontal_lines[0], extLeft1, extRight1[0])
Note that we can use this function to find the end points of the horizontal line returned by HoughLines.
# last line of horizontal_lines would be the points we need:
upper_lowest_y, upper_lowest_left, upper_lowest_right = horizontal_lines[-1]
img_lines = img.copy()
cv2.line(img_lines, (upper_lowest_left, upper_lowest_y), extLeft1, (0,0,255), 1)
cv2.line(img_lines, (upper_lowest_right, upper_lowest_y), extRight1, (0,0,255),1)
and that gives:
Let's return to the extended question, where we have those left and right points:
left_points = [(x,y) for y,x,_ in horizontal_lines]
right_points = [(x,y) for y,_,x in horizontal_lines]
Obviously, they would not fit perfectly in a parabola, so we need some sort of approximation/fitting here. For that, we can build a LinearRegression model:
from sklearn.linear_model import LinearRegression
class BestParabola:
def __init__(self, points):
x_x2 = np.array([(x**2,x) for x,_ in points])
ys = np.array([y for _,y in points])
self.lr = LinearRegression()
self.lr.fit(x_x2,ys)
self.a, self.b = self.lr.coef_
self.c = self.lr.intercept_
self.coef_ = (self.c,self.b,self.a)
def transform(self,points):
x_x2 = np.array([(x**2,x) for x,_ in points])
ys = self.lr.predict(x_x2)
return np.array([(x,y) for (_,x),y in zip(x_x2,ys)])
And then, we can fit the given left_points, right_points to get the desired parabolas:
# construct the approximate parabola
# the parabollas' coefficients are accessible by BestParabola.coef_
left_parabola = BestParabola(left_points)
right_parabola = BestParabola(right_points)
# get points for rendering
left_parabola_points = left_parabola.transform(left_points)
right_parabola_points = right_parabola.transform(right_points)
# render with matplotlib, cv2.drawContours would work
plt.figure(figsize=(8,8))
plt.imshow(cv2.cvtColor(img,cv2.COLOR_BGR2RGB))
plt.plot(left_parabola_points[:,0], left_parabola_points[:,1], linewidth=3)
plt.plot(right_parabola_points[:,0], right_parabola_points[:,1], linewidth=3, color='r')
plt.show()
Which gives:
The left parabola is not perfect, but you should work out that if need be :-)
I have a bunch of images which look like the one below, with varying fringe thickness.
I would like to fit a sinusoidal function to this image via the python scipy.optimize.curve_fit function. However, as the image shows, the fringe patterns are only confined within a circular region. How then can I perform the fit?
I guess if I were you, I would want a function to approximate the fringes at 90 degrees to them...that way I could grab the period of the sin function. This would be a multi-step solution.
1) grab area where fringes are brightest
2) get the values of the image along the line that is normal to the fringes
3) calculate a best fit curve.
import cv2
import numpy as np
from matplotlib import pyplot as plt
import math
#function to rotate an image around a point
def rotateImage(image, angle):
image_center = tuple(np.array(image.shape[1::-1]) / 2)
rot_mat = cv2.getRotationMatrix2D(image_center, angle, 1.0)
result = cv2.warpAffine(image, rot_mat, image.shape[1::-1], flags=cv2.INTER_LINEAR)
return result
img = cv2.imread('sin.png', cv2.IMREAD_GRAYSCALE)
imgb = cv2.GaussianBlur(img, (7,7),0)
plt.imshow(imgb)
plt.show()
#grap bright values
v90 = np.percentile(imgb, 90)
#get mask for bright values
msk = imgb >= v90
msk = msk.astype(np.uint8)
plt.imshow(msk)
plt.show()
#get contours
cnts = cv2.findContours(msk, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2]
cnt = sorted(cnts, key=cv2.contourArea)
c = np.vstack((cnt[-2], cnt[-1])) #combine 2 largest
((cx, cy), radius) = cv2.minEnclosingCircle(c)
cv2.circle(img, (int(cx), int(cy)), int(radius), 255, 2)
plt.imshow(img)
plt.show()
#circle is just for show...use if you want.
imc = img.copy()
#take top 5 contours(4 would work too ymmv)
angles = []
for i in range(5):
c = cnt[-(i+1)]
ellipse = cv2.fitEllipse(c)
(x, y), (MA, ma), angle = cv2.fitEllipse(c)
cv2.ellipse(imc, ((x, y), (MA, ma), angle), 255, 2)
angles.append(angle)
#average angle of ellipse.
mangle = np.mean(angles)
#goal is create a line normal to the average angle of the ellipses.
#the 0.6 factor here is to just grab the inner region...which will avoid the rapid fall-off of the envelop gaussian-like function.
pt1 = (int(cx + .6*radius*math.cos(math.radians(mangle))), int(cy + .6*radius*math.sin(math.radians(mangle))))
pt2 = (int(cx - .6*radius*math.cos(math.radians(mangle))), int(cy - .6*radius*math.sin(math.radians(mangle))))
#show line
cv2.line(imc, pt1, pt2, 255, 2)
#put fat line on mask...will use this to sample from original image later
im4mask = np.zeros(imc.shape).astype(np.uint8)
cv2.line(im4mask, pt1, pt2, 255, 9)
plt.imshow(imc)
plt.show()
plt.imshow(im4mask)
plt.show()
#now do some rotating(to make the numpy slicing later easier)
imnew = rotateImage(imc, mangle)
plt.imshow(imnew)
plt.show()
im4maskrot = rotateImage(im4mask, mangle)
im4maskrot[im4maskrot > 20] = 255
plt.imshow(im4maskrot)
plt.show()
imgbrot = rotateImage(imgb, mangle)
plt.imshow(imgbrot)
plt.show()
#gather values from original
ys, xs = np.where(im4maskrot == 255)
minx = np.min(xs)
miny = np.min(ys)
maxx = np.max(xs)
maxy = np.max(ys)
print 'x ', minx, maxx
print 'y ', miny, maxy
crop = imgbrot[miny:maxy, minx:maxx]
print crop.shape
plt.imshow(crop)
plt.show()
plt.plot(range(crop.shape[1]), np.mean(crop, axis=0))
#now time to fit a curve.
#first with a gaussian
from scipy import optimize
def test_func(x, a, b, A, mu, sigma):
return A*np.exp(-(x-mu)**2/(2.*sigma**2)) + a * np.sin(b * x)
params, params_covariance = optimize.curve_fit(test_func, np.arange(crop.shape[1]), np.mean(crop, axis=0), p0=[10, 1/15., 60, 2, 150], maxfev=200000000)
print(params)
plt.figure(figsize=(6, 4))
plt.scatter(range(crop.shape[1]), np.mean(crop, axis=0), label='Data')
plt.plot(np.arange(crop.shape[1]), test_func(np.arange(crop.shape[1]), params[0], params[1], params[2], params[3], params[4]), label='Fitted function')
plt.legend(loc='best')
plt.show()
#and without a gaussian...the result is close because of only grabbing a short region.
def test_func(x, a, b, n):
return n + a * np.sin(b * x)
params, params_covariance = optimize.curve_fit(test_func, np.arange(crop.shape[1]), np.mean(crop, axis=0), p0=[10, 1/15., 60], maxfev=200000000)
print(params)
plt.figure(figsize=(6, 4))
plt.scatter(range(crop.shape[1]), np.mean(crop, axis=0), label='Data')
plt.plot(np.arange(crop.shape[1]), test_func(np.arange(crop.shape[1]), params[0], params[1], params[2]), label='Fitted function')
plt.legend(loc='best')
plt.show()
Note that the b parameter is the one concerned with periodicity and both values are fairly close to each other ( 0.0644 and 0.0637). Knowing that, I'd opt for the simpler curve fit as the starting parameters are fewer.
I have created some code to visually display the average gradient direction in a cell/kernel. My questions are:
Is my method of calculating the average gradient direction correct? I am aware of a different method (see *) but unsure which is better/more accurate.
Is my normalisation of a degrees value to a hue value correct? Ie, normalising a value that can be 0-359 to a value between 0-179 by simply dividing by 2?
Most importantly am I accurately calculating and representing the average gradient direction over a series of cells?
*Alt method to calculate the average gradient direction:
hMean = cv2.mean(sobelX)
vMean = cv2.mean(sobelY)
avg_dir = math.atan2(-vMean[0], hMean[0])
Draw gradient directions:
import cv2
import math
import numpy as np
np.set_printoptions(precision=3, threshold=np.inf, linewidth=np.inf, suppress=True)
def get_roi(src, pt1, pt2):
col1, col2 = (pt1[0], pt2[0]) if pt1[0] < pt2[0] else (pt2[0], pt1[0])
row1, row2 = (pt1[1], pt2[1]) if pt1[1] < pt2[1] else (pt2[1], pt1[1])
return src[row1:row2, col1:col2]
def get_gradient_direction_line(avg_dir, cellUpperLeft, cellW, cellH, scale=0.8):
halfScale = scale/2;
centrePt = (int(cellUpperLeft[0] + (cellW/2)), int(cellUpperLeft[1] + (cellH/2)))
strtPt = (int(centrePt[0] - (cellW * halfScale * math.cos(avg_dir))), int(centrePt[1] - (cellH * halfScale * math.sin(avg_dir))))
endPt = (int(centrePt[0] + (cellW * halfScale * math.cos(avg_dir))), int(centrePt[1] + (cellH * halfScale * math.sin(avg_dir))))
return [strtPt, endPt]
def get_gradient_directions_arrows(direction, kernel_w=3, kernel_h=3):
arrows = np.zeros(direction.shape, dtype=np.uint8)
n_cols = int(direction.shape[1] / kernel_w)
n_rows = int(direction.shape[0] / kernel_h)
for c in range(n_cols):
# Draw grid lines
cv2.line(arrows, (c*kernel_w, 0), (c*kernel_w, direction.shape[0]), (255,255,255), 1)
cv2.line(arrows, (0, c*kernel_h), (direction.shape[1], c*kernel_h), (255,255,255), 1)
for r in range(n_rows):
roiUpperleft = (c*kernel_w, r*kernel_h)
roi = get_roi(direction, roiUpperleft, ((c+1)*kernel_w, (r+1)*kernel_h))
avg_dir = cv2.mean(roi)[0]
arrow_pnts = get_gradient_direction_line(avg_dir, roiUpperleft, kernel_w, kernel_h)
cv2.arrowedLine(arrows, arrow_pnts[0], arrow_pnts[1], (255,255,255), 1)
return arrows
def get_gradient_directions_colours(direction, kernel_w=3, kernel_h=3):
# (red=0°; yellow=60°, green=120°, blue=240°...)
hsv = np.zeros((direction.shape[0], direction.shape[1], 3), dtype=np.uint8)
hsv = cv2.cvtColor(hsv, cv2.COLOR_BGR2HSV)
n_cols = int(direction.shape[1] / kernel_w)
n_rows = int(direction.shape[0] / kernel_h)
for c in range(n_cols):
# Draw grid lines
cv2.line(hsv, (c*kernel_w, 0), (c*kernel_w, direction.shape[0]), (180,255,255), 1)
cv2.line(hsv, (0, c*kernel_h), (direction.shape[1], c*kernel_h), (180,255,255), 1)
for r in range(n_rows):
roiUpperleft = (c*kernel_w, r*kernel_h)
roi = get_roi(direction, roiUpperleft, ((c+1)*kernel_w, (r+1)*kernel_h))
avg_dir = cv2.mean(roi)[0]
# avg_dir will be value between 0-359. HSV hue needs a value between 0-179
avg_dir /= 2
arrow_pnts = get_gradient_direction_line(avg_dir, roiUpperleft, kernel_w, kernel_h)
cv2.rectangle(hsv, roiUpperleft, ((c+1)*kernel_w, (r+1)*kernel_h), (avg_dir, 255,255), -1)
bgr = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
return bgr
def main():
src = cv2.imread('foo.jpg', 0)
# Calculate gradient magnitude and direction for the image
dX = cv2.Sobel(src, cv2.CV_32F, 1, 0)
dY = cv2.Sobel(src, cv2.CV_32F, 0, 1)
mag, direction = cv2.cartToPolar(dX, dY, angleInDegrees=True)
arrows = get_gradient_directions_arrows(direction, 20, 20)
colours = get_gradient_directions_colours(direction, 20, 20)
cv2.imshow('src', src)
cv2.imshow('arrows', arrows)
cv2.imshow('colours', colours)
cv2.waitKey(0)
main()