Home > OS >  Cannot read the second arm of the Analog Clock correctly by using opencv python
Cannot read the second arm of the Analog Clock correctly by using opencv python

Time:10-12

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)

CodePudding user response:

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.
  • Related