Related
Picture Link since I cannot upload it> Thank you https://github.com/HassanAdamm that I can be able to continue the further code but still cannot display the correct second hand of the Analog Clock with OpenCV. Hour and Minute Hands are successfully done with HoughLineP(). I am unable to separate the seconds hand from the image. Below is my working code and hope you guys can help me with this!
import cv2
import math
import numpy as np
import tkinter as tk
from matplotlib import pyplot as plt
from math import sqrt, acos, degrees
# Reading the input image and convert the original RGB to a grayscale image
kernel = np.ones((5, 5), np.uint8)
img1 = cv2.imread('input1.jpg')
img = cv2.imread('input1.jpg', 0)
img_gray = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
# Appling a binary threshold to the image
ret, thresh = cv2.threshold(img_gray, 50, 255, cv2.THRESH_BINARY)
# Create mask
height, width = img.shape
mask = np.zeros((height, width), np.uint8)
edges = cv2.Canny(thresh, 100, 200)
# Circle Detection
cimg = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
circles = cv2.HoughCircles(img_gray, cv2.HOUGH_GRADIENT, 1.2, 100)
for i in circles[0,:]:
i[2] = i[2] + 4
# cv2.cicle(image, center_coordinates, radius, color, thickness)
cv2.circle(mask, (int(i[0]),int(i[1])), int(i[2]), (255,255,255), thickness = -1)
# Copy that image using that mask
masked_data = cv2.bitwise_and(img1, img1, mask = mask)
# Apply threshold
_,thresh = cv2.threshold(mask, 1, 255, cv2.THRESH_BINARY)
# Find Contour
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
x, y, w, h = cv2.boundingRect(contours[0])
# Crop masked_data
crop = masked_data[y + 30 : y + h -30, x + 30 : x + w - 30]
height, width, channel = crop.shape
blur_crop = cv2.GaussianBlur(crop, (5, 5), 0)
edges = cv2.Canny(blur_crop, 50, 150)
# Line segments
line_image = np.copy(crop) * 0
lines = cv2.HoughLinesP(edges, 1, np.pi/180, 15, np.array([]), 100, 10)
l = []
xl1, xl2, yl1, yl2 = 0, 0, 0, 0 #long -> l
xm1, xm2, ym1, ym2 = 0, 0, 0, 0 #medium -> m
xs1, xs2, ys1, ys2 = 0, 0, 0, 0 #short -> s
# Getting the values from the line
for line in lines:
x1, y1, x2, y2 = line[0]
dx = x2 - x1
if (dx < 0):
dx = dx* (-1)
dy = y2 - y1
if (dy < 0):
dy = dy* (-1)
hypo = sqrt(dx**2 + dy**2)
l.append(hypo)
l.sort(reverse=True)
s, m, h = 0, 0, 0
for f in range(len(l)):
for line in lines:
# getting the values from the line
x1, y1, x2, y2 = line[0]
#cv2.line(crop, (x1, y1), (x2, y2), (0, 255, 0), 3)
dx = x2 - x1
if (dx < 0):
dx = dx* (-1)
dy = y2 - y1
if (dy < 0):
dy = dy* (-1)
hypo2 = sqrt(dx**2 + dy**2)
if (hypo2 == l[0]):
m = hypo2
xl1 = x1
xl2 = x2
yl1 = y1
yl2 = y2
# getting line region
cv2.line(crop, (xl1, yl1), (xl2, yl2), (255, 0, 0), 3)
if (m == l[0]):
if (hypo2 == l[f]):
if ((sqrt((xl2 - x2)**2 + (yl2 - y2)**2)) > 20):
if ((sqrt((xl1 - x1)**2 + (yl1 - y1)**2)) > 20):
xs1 = x1
xs2 = x2
ys1 = y1
ys2 = y2
# getting line region
cv2.line(crop, (xl1, yl1), (xl2, yl2), (0, 255, 0), 5)
h = 1
break
# Calculate center point
xcenter = width/2
ycenter = height/2
# Determine the cooridnates of the end point (farther from the center)
def coordinates (x1, y1, x2, y2):
a = abs(xcenter - x1)
b = abs(xcenter - x2)
if (a > b):
x_coor = x1
y_coor = y1
else:
x_coor = x2
y_coor = y2
return x_coor, y_coor
xhour, yhour = coordinates(xs1, ys1, xs2, ys2)
xmin, ymin = coordinates(xl1, yl1, xl2, yl2)
xsec, ysec = coordinates(xl1, yl1, xl2, yl2)
cv2.line(crop, (xs1, ys1), (xs2, ys2), (0, 255, 0), 5)
# Calculate the Hour, Minute, Second-hands by the law of cosines
def law_of_cosines (x, y):
l1 = sqrt(((xcenter - x)**2) + ((ycenter - y)**2))
l2 = ycenter
l3 = sqrt(((xcenter - x)**2) + ((0 - y)**2))
cos_theta = ( (l1**2) + (l2**2) - (l3**2) )/(2*l1*l2)
theta_radian = acos(cos_theta)
theta = math.degrees(theta_radian)
return theta
theta_hour = law_of_cosines(xhour, yhour)
theta_min = law_of_cosines(xmin, ymin)
theta_sec = law_of_cosines(xsec, ysec)
def right_or_not (x):
if (x > xcenter):
right = 1
else:
right = 0
return right
hour_right = right_or_not(xhour)
min_right = right_or_not(xmin)
sec_right = right_or_not(xsec)
def time_cal (x, y, z):
if (z == xhour):
if (x == 1):
a = int(y/30)
else:
a = 12 - int(y/30)
if a == 0:
a = 12
else:
if (x == 1):
a = int(y/6)
else:
a = 60 - int(y/6)
if (z == xcenter):
a = 30
return a
hour = time_cal(hour_right, theta_hour, xhour)
minute = time_cal(min_right, theta_min, xmin)
sec = time_cal(sec_right, theta_sec, xsec)
# Display window
canvas = tk.Tk()
canvas.title("Analog to Digital")
canvas.geometry("500x250")
digit = tk.Label(canvas, font = ("ds-digital", 65, "bold"), bg = "white", fg = "blue", bd = 80)
digit.grid(row = 0, column = 1)
# Display result
def display(hour, minute, sec):
value = "{0:0=2d}:{1:0=2d}:{2:0=2d}".format(hour, minute, sec)
digit.config(text=value)
print(value)
display(hour, minute, sec)
canvas.mainloop()
for line in lines:
for x1,y1,x2,y2 in line:
cv2.line(line_image, (x1,y1), (x2,y2), (255,0,0), 1)
lines_edges = cv2.addWeighted(crop, 0.8, line_image, 1, 0)
cv2.imshow('Line Image', line_image)
cv2.imshow('Crop', crop)
cv2.waitKey(0)
There are lot of possible trap in this kind of things. Because each hand generate two lines, but not exactly parallel, and some interaction may make them appear shorter, etc.
But in your case, I bet the problem is far simpler:
xhour, yhour = coordinates(xs1, ys1, xs2, ys2)
xmin, ymin = coordinates(xl1, yl1, xl2, yl2)
xsec, ysec = coordinates(xl1, yl1, xl2, yl2)
I am pretty sure, one of those should be coordinates(xm1, ym1, xm2, ym2)
Edit after your comment. So, we are in a worse place. Because what you have is a computer vision problem, not just a python problem.
And there is not clear solution for that. But a few hint of what you could do.
You could identify the center of the clock (you've already done it, to draw a circle, I think), and also use the distance to the center rather than the length of the line.
You can take advantage of that, to filter lines that don't go through the center, or almost so
Since lines are the border of the hands, and those are slightly triangle, how close they come to the center is an indication of which hand it is. The hour and minute hands lines don't cross exactly the center of the circle. The second hand lines came closer to the center.
Besides, you should expect 2 lines at least (more in reality, that's how hough works) per hand. One over the center, another under. So you can take advantage of that to enhance reliability of the angle computation (by computing the median line, that goes through the center), and the length computation. And avoid counting twice the same hand
Also, you could compute angles from all lines: if there are 3 clearly separated angles, you know that all the angles you are looking for are there. The minutes and seconds for the long hand (and you can discriminate between those because of the more triangle and thick shape our hour, and more narrow shape of seconds. Which result in bigger variability of lines direction for hours than for seconds). The hour hand for the short one.
You can also try to take advantage of the "tail" of the seconds hand, and try to check if you find some dark pixels in the opposite direction of a hand. If you don't, it is not the second hand.
You could also use morphological operators, to erode black pixels before canny/hough. So that you know that the second hand has disappeared, because it is too narrow. You'll find 2 hands from there. And then redo the job without erosion. The extra hand you find is the second hand
Of course, there is the case when some hands are superposed to deal with. If you are confident that, after trying some of the ideas, you would have found 3 hands if there were 3, then, you can trust that 2 hands are at the same position. You could also use your knowledge of previous detection (you know how the hands are supposed to move)
At last, if you are not specially wanting to use line detection only, you could also simply watch the pixels on some circles. A circle whose center is the center of the clock, and whose radius is as big as possible but not big enough to include the digits, should be crossed by two hands (hours and seconds), and it will be quite easy to spot that one (minutes) is thicker than the other (seconds). If there is only one, then you know that hours and seconds are the same. A smaller circle should be crossed by 3 hands. The extra one is hour hand. If you can't find an extra one, and have 2 hands (the same as on the bigger circle) then the hour hand is superposed with either the minute hand or the second hand. If it is the second hand, then it should get a lot thicker.
I have an image which have a table and some other data. I need to draw borders for table to separate out each cell.
My image looks like this
What i am trying:
1) dilating the image to create continuous spots, which looks like
2) finding contours and drawing
Issue: I am not able to draw correctly because it looks like my table cells are too close and while dilating they are becoming a continuous spot
**I took this code from Internet and was trying to modify But it did not work out well for this image
code :
import os
import cv2
import imutils
# This only works if there's only one table on a page
# Important parameters:
# - morph_size
# - min_text_height_limit
# - max_text_height_limit
# - cell_threshold
# - min_columns
def pre_process_image(img, save_in_file, morph_size=(7, 7)):
# get rid of the color
pre = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Otsu threshold
pre = cv2.threshold(pre,250, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
# dilate the text to make it solid spot
cpy = pre.copy()
struct = cv2.getStructuringElement(cv2.MORPH_RECT, morph_size)
cpy = cv2.dilate(~cpy, struct, anchor=(-1, -1), iterations=1)
# cpy = cv2.dilate(img,kernel,iterations = 1)
pre = ~cpy
# pre=cpy
if save_in_file is not None:
cv2.imwrite(save_in_file, pre)
return pre
def find_text_boxes(pre, min_text_height_limit=3, max_text_height_limit=30):
# Looking for the text spots contours
contours = cv2.findContours(pre, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
# contours = contours[0] if imutils.is_cv2() else contours[1]
contours = contours[0]
# Getting the texts bounding boxes based on the text size assumptions
boxes = []
for contour in contours:
box = cv2.boundingRect(contour)
h = box[3]
if min_text_height_limit < h < max_text_height_limit:
boxes.append(box)
return boxes
def find_table_in_boxes(boxes, cell_threshold=10, min_columns=2):
rows = {}
cols = {}
# Clustering the bounding boxes by their positions
for box in boxes:
(x, y, w, h) = box
col_key = x // cell_threshold
row_key = y // cell_threshold
cols[row_key] = [box] if col_key not in cols else cols[col_key] + [box]
rows[row_key] = [box] if row_key not in rows else rows[row_key] + [box]
# Filtering out the clusters having less than 2 cols
table_cells = list(filter(lambda r: len(r) >= min_columns, rows.values()))
# Sorting the row cells by x coord
table_cells = [list(sorted(tb)) for tb in table_cells]
# Sorting rows by the y coord
table_cells = list(sorted(table_cells, key=lambda r: r[0][1]))
return table_cells
def build_lines(table_cells):
if table_cells is None or len(table_cells) <= 0:
return [], []
max_last_col_width_row = max(table_cells, key=lambda b: b[-1][2])
max_x = max_last_col_width_row[-1][0] + max_last_col_width_row[-1][2]
max_last_row_height_box = max(table_cells[-1], key=lambda b: b[3])
max_y = max_last_row_height_box[1] + max_last_row_height_box[3]
hor_lines = []
ver_lines = []
for box in table_cells:
x = box[0][0]
y = box[0][1]
hor_lines.append((x, y, max_x, y))
for box in table_cells[0]:
x = box[0]
y = box[1]
ver_lines.append((x, y, x, max_y))
(x, y, w, h) = table_cells[0][-1]
ver_lines.append((max_x, y, max_x, max_y))
(x, y, w, h) = table_cells[0][0]
hor_lines.append((x, max_y, max_x, max_y))
return hor_lines, ver_lines
if __name__ == "__main__":
in_file = os.path.join("data", "page1.jpg")
pre_file = os.path.join("data", "pre.png")
out_file = os.path.join("data", "out.png")
img = cv2.imread(os.path.join(in_file))
pre_processed = pre_process_image(img, pre_file)
text_boxes = find_text_boxes(pre_processed)
cells = find_table_in_boxes(text_boxes)
hor_lines, ver_lines = build_lines(cells)
# Visualize the result
vis = img.copy()
# for box in text_boxes:
# (x, y, w, h) = box
# cv2.rectangle(vis, (x, y), (x + w - 2, y + h - 2), (0, 255, 0), 1)
for line in hor_lines:
[x1, y1, x2, y2] = line
cv2.line(vis, (x1, y1), (x2, y2), (0, 0, 255), 1)
for line in ver_lines:
[x1, y1, x2, y2] = line
cv2.line(vis, (x1, y1), (x2, y2), (0, 0, 255), 1)
cv2.imwrite(out_file, vis)
Very interesting application.
Raw dialating may not be the best way to do it.
I do recommend using OCR routing. Like below
The output is sthing like this
So as long as there is two row which are closer to each other. eg, row1-row2< npixel. then it is close line. the find the center position between (row1+height1) and row2. The line should be pretty accurate.
in my sample if |292-335| < 50. then draw a line between (292+27 + 335) /2
means it's between asset line and the property line.
For the OCR package, you can try with tesseract if you insist with python.
https://pypi.org/project/pytesseract/
See here for python text coordinate Tesseract OCR Text Position
Tesseract.PageIteratorLevel myLevel = /*TODO*/;
using (var page = Engine.Process(img))
using (var iter = page.GetIterator())
{
iter.Begin();
do
{
if (iter.TryGetBoundingBox(myLevel, out var rect))
{
var curText = iter.GetText(myLevel);
// Your code here, 'rect' should containt the location of the text, 'curText' contains the actual text itself
}
} while (iter.Next(myLevel));
}
rect contains the part you wanted x y height width
The demo I show it here is actually using sth similar to windows OCR sample
https://github.com/microsoft/Windows-universal-samples/tree/master/Samples/OCR
Feel free to try any of the methods to get the table line you wanted.
I have an image of a blue colored cross drawn in MS Paint.
https://imgur.com/cMjZrra
I want to be able to extract the four separate arms of the cross from the image, and store them in four separate images.
What I have tried is to use cv2.inRange() method to detect the color blue, as per the following code :
import cv2
import numpy as np
img=cv2.imread("PECross.png")
blue=[
([250,0,0],[255,0,0])]
for (lower, upper) in blue:
lower=np.array(lower, dtype="uint8")
upper=np.array(upper, dtype="uint8")
mask=cv2.inRange(img,lower,upper)
output=cv2.bitwise_and(img,img,mask=mask)
cv2.imshow("Out",output)
cv2.waitKey(0)
cv2.destroyAllWindows()
and then display the extracted blue colors. It extracts the entire cross because its colored the same, but I want to extract the four arms of the cross separately.
What code do I need to add, to extract the four arms of the cross separately?
Here is the code. It uses hough lines to detect lines than simply crops the image regions given by the lines
img = cv2.imread('PECross.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
lines_coords = []
edges = cv2.Canny(gray, 50,150,3)
lines = cv2.HoughLines(edges,1,np.pi/180,200)
for rho,theta in lines.squeeze():
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))
lines_coords.append((x1,y1,x2,y2))
vertical_left = lines_coords[0]
vertical_right = lines_coords[1]
horizontal_up = lines_coords[2]
horizontal_down = lines_coords[3]
x1,y1,x2,y2=vertical_left
left_arm = img[:, :x1]
x1,y1,x2,y2=vertical_right
right_arm = img[:, x1:]
x1,y1,x2,y2=horizontal_up
upper_arm = img[:y1, :]
x1,y1,x2,y2=horizontal_down
lower_arm = img[y1:, :]
cv2.imwrite('left_arm.png', left_arm)
cv2.imwrite('right_arm.png', right_arm)
cv2.imwrite('upper_arm.png', upper_arm)
cv2.imwrite('lower_arm.png', lower_arm)
I am having trouble with Hough Line transformation. I am trying to identify the major lines in a kitchen. I first just used Canny, but it was picking up more noise than I wanted and wasn't picking up the meeting of the wall and ceiling. However, the Hough Line transformation is only identifying one line that it should not be identifying at all. Any help would be appreciated.
My input:
kitchen_sample.jpg
My output:
kitchen_lines.jpg
And here is my code:
import cv2
import numpy as np
image = cv2.imread('kitchen_sample.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray, 50, 150, apertureSize=3)
lines = cv2.HoughLines(edges, 1, np.pi / 180, 200)
for rho, theta in lines[0]:
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(image, (x1, y1), (x2, y2), (0, 0, 255), 2)
cv2.imwrite('kitchen_lines.jpg', image)
You were probably looking at the old opencv tutorial page which probably has a mistake in it (or something changed with versioning, didn't track opencv-python).
Here's a new & correct one
All you need to change is replace
for rho, theta in lines[0]:
with
for line in lines:
rho,theta = line[0]
But anyway it would take you some time to get desired output.
What I would recommend you is using HoughLinesP which would easily give you what you likely need
lines = cv2.HoughLinesP(edges,1,np.pi/180,100,minLineLength=100,maxLineGap=10)
for line in lines:
x1,y1,x2,y2 = line[0]
cv2.line(image,(x1,y1),(x2,y2),(0,255,0),2)
I'm using openCV to detect the distance between two lines and their position relative to the centre point of an image. Doesn't need to be an exact distance - just a contextual value of some sort (pixels would be fine)
My code which I have working detecting the two lines is this;
import PIL
import time
import io
import picamera
import cv2
import numpy as np
image_count = 0
with picamera.PiCamera() as camera:
camera.start_preview()
camera.resolution = (340, 240)
time.sleep(2)
while(True):
try:
stream = io.BytesIO()
image_counter+=1
camera.capture(stream, format='png')
data = np.fromstring(stream.getvalue(), dtype=np.uint8)
image = cv2.imdecode(data, 1)
grey_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
edge_image = cv2.Canny(grey_image, 50, 150, apertureSize = 3)
lines = cv2.HoughLines(edge_image, 1, np.pi/180, 95)
if(lines.any):
for rho, theta in lines[0]
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(image, (x1, y1), (x2, y2), (0,0,255), 2)
cv2.imwrite('lined_image_' + str(image_counter) + '.png, image)
except:
print 'loop error'
It detects lines such as in this image;
I've been trying to work out how to do this numerically but it's convoluted and probably wrong - there must be an easier way but I can't see it with my inexperience using open CV.
How can I find the distance between the centre point of the image and the innermost red lines you see? (at the point where the lines intersects the horizontal line which intersects both in and the images centre point)
Thanks!
If you were to use HoughLinesP, you'd directly get start and end points of the lines. Then, when
Dx is (x2-x1) and Dy is (y2-y1), your required distance d from the centre point (x0,y0) is
If you intend to stick with HoughLines, you can easily transform rho and theta to get the equation of a line, and use one of the many formulae described here, which is also where the above formula has been borrowed from.