Creating ClockClock24 using opencv


ClockClock24

Do you know ClockClock24?
ClockClock24 is kinetic art by Humans since 1982. Twenty-four analog clocks form a large digital clock. Clever arrangement of the clock hands transforms the analog clocks into seven-segment displays.


<captured from https://www.humanssince1982.com/clockclock24>

 Here's an introduction video.



This fantastic clock can also be purchased directly online Moma Design Store. But the price is not easy. The price is surprisingly 5,400 $



<Moma online store>

I came across a page a few days ago that implemented Clock Clock 24 on the Raspberry Pi. The url is https://manu.ninja/clock-clock-24-on-a-raspberry-pi-using-web-technologies.





This work is implemented on the web using Javascript. I wanted to implement this work in Python using OpenCV.

ClockClock24 development using OpenCV

ClockClock24 uses a total of 24 clocks of 3(H) X 8(W). And 2X8 clocks display one of the numbers from 0 to 9. The numbers 0 to 9 have the following shape.



The list of degree angles these numbers create is shown in the Python list:


digit_1 = [ [(225, 225),(225, 225),(225, 225)],  [(180, 180),(  0, 180),(  0,   0)] ]
digit_2 = [ [( 90,  90),( 90, 180),(  0,  90)],  [(180, 270),(  0, 270),(270, 270)] ]
digit_3 = [ [( 90,  90),( 90,  90),( 90,  90)],  [(180, 270),(  0, 270),(  0, 270)] ]
digit_4 = [ [(180, 180),(  0,  90),(225, 225)],  [(180, 180),(  0, 180),(  0,   0)] ]
digit_5 = [ [( 90, 180),(  0,  90),( 90,  90)],  [(270, 270),(180, 270),(  0, 270)] ]
digit_6 = [ [( 90, 180),(  0, 180),(  0,  90)],  [(270, 270),(180, 270),(  0, 270)] ]
digit_7 = [ [( 90,  90),(225, 225),(225, 225)],  [(180, 270),(  0, 180),(  0,   0)] ]
digit_8 = [ [( 90, 180),(  0,  90),(  0,  90)],  [(180, 270),(  0, 270),(  0, 270)] ]
digit_9 = [ [( 90, 180),(  0,  90),( 90,  90)],  [(180, 270),(  0, 180),(  0, 270)] ]
digit_0 = [ [( 90, 180),(  0, 180),(  0,  90)],  [(180, 270),(  0, 180),(  0, 270)] ]


And the size of the canvas will be determined by the following values.


24 clocks will be implemented through the Clock class. And we will use the Clocks class to represent a number. This class will have 6 clock objects. Since we are going to display a total of 4 numbers, we will create 4 clocks objects and we will name them H0_clocks, H1_clocks, M0_clocks, M1_clocks.
In the name, H means hours and M means minutes.


The full source code is as follows.


import numpy as np
import cv2
import time
import math
from datetime import datetime
from PIL import Image, ImageDraw

FPS = 30.0
SLEEP = 1.0 / FPS


tmargin = 20
lmargin = 20
rmargin = 20
margin = 20
bmargin = 20
r = 100
arm_length = int(r * 0.9)
circle_thickness = 2
arm_thickness = 15
circle_color = (128,128,128)
arm_color = (96,92,255)
arm_color2 = (96,255,96)

Canvas_W = lmargin + rmargin + r * 2 * 8 + margin * 7
Canvas_H = tmargin + bmargin + r * 6 + margin * 2

digit_basic = [[(45, -45),(45, -45),(45, -45)],  [(45, -45), ( 45, -45),( 45, -45)] ]
digit_1 = [ [(225, 225),(225, 225),(225, 225)],  [(180, 180),(  0, 180),(  0,   0)] ]
digit_2 = [ [( 90,  90),( 90, 180),(  0,  90)],  [(180, 270),(  0, 270),(270, 270)] ]
digit_3 = [ [( 90,  90),( 90,  90),( 90,  90)],  [(180, 270),(  0, 270),(  0, 270)] ]
digit_4 = [ [(180, 180),(  0,  90),(225, 225)],  [(180, 180),(  0, 180),(  0,   0)] ]
digit_5 = [ [( 90, 180),(  0,  90),( 90,  90)],  [(270, 270),(180, 270),(  0, 270)] ]
digit_6 = [ [( 90, 180),(  0, 180),(  0,  90)],  [(270, 270),(180, 270),(  0, 270)] ]
digit_7 = [ [( 90,  90),(225, 225),(225, 225)],  [(180, 270),(  0, 180),(  0,   0)] ]
digit_8 = [ [( 90, 180),(  0,  90),(  0,  90)],  [(180, 270),(  0, 270),(  0, 270)] ]
digit_9 = [ [( 90, 180),(  0,  90),( 90,  90)],  [(180, 270),(  0, 180),(  0, 270)] ]
digit_0 = [ [( 90, 180),(  0, 180),(  0,  90)],  [(180, 270),(  0, 180),(  0, 270)] ]
clock_digits = []
clock_digits.append(digit_0)
clock_digits.append(digit_1)
clock_digits.append(digit_2)
clock_digits.append(digit_3)
clock_digits.append(digit_4)
clock_digits.append(digit_5)
clock_digits.append(digit_6)
clock_digits.append(digit_7)
clock_digits.append(digit_8)
clock_digits.append(digit_9)


class Clock():
    def __init__(self, x, y, circle_color, circle_thick, arm_color, arm_thick, lmar):
        self.x = x
        self.y = y
        self.circle_color = circle_color
        self.circle_thickness = circle_thick
        self.arm_color = arm_color
        self.arm_thickness = arm_thick
        self.center = (lmar + r + (margin + 2*r) * x, tmargin + r + (margin + 2*r) * y)
        self.angle1 = -45
        self.angle2 = 45
        self.target_angle1 = 0
        self.target_angle2 = 0

    def draw_circle(self, img):
        cv2.circle(img, self.center, r, self.circle_color, self.circle_thickness)

    def set_angle(self, angle1, angle2):
        self.angle1 = angle1
        self.angle2 = angle2
    def step_angle(self):
        self.angle1 += self.step
        if(self.angle1 >  self.target_angle1):
            self.angle1 = self.target_angle1
            self.finish_angle1 = True
        self.angle2 += self.step
        if(self.angle2 >  self.target_angle2):
            self.angle2 = self.target_angle2
            self.finish_angle2 = True        
    '''
    for animation, set the last angle
    '''    
    def set_target_angle(self, angle1, angle2, step):
        self.target_angle1 = angle1
        self.target_angle2 = angle2
        self.finish_angle1 = False
        self.finish_angle2 = False
        self.step = step

    def draw_arms(self, img):
        angle1 = np.radians(self.angle1)
        angle2 = np.radians(self.angle2)
        center = (lmargin + r + (margin + 2*r) * self.x, tmargin + r + (margin + 2*r) * self.y)
        pt = (self.center[0], self.center[1] - arm_length) # 12 Hour direction

        qx = int(self.center[0] + math.cos(angle1) * (pt[0] - self.center[0]) - math.sin(angle1) * (pt[1] - self.center[1]))
        qy = int(self.center[1] + math.sin(angle1) * (pt[0] - self.center[0]) + math.cos(angle1) * (pt[1] - self.center[1]))
        cv2.line(img, self.center, (qx,qy), self.arm_color, self.arm_thickness)
        qx = int(self.center[0] + math.cos(angle1) * (pt[0] - self.center[0]) - math.sin(angle2) * (pt[1] - self.center[1]))
        qy = int(self.center[1] + math.sin(angle1) * (pt[0] - self.center[0]) + math.cos(angle2) * (pt[1] - self.center[1]))
        cv2.line(img, self.center, (qx,qy), self.arm_color, self.arm_thickness)

    def init_clock(self, img):
        self.set_angle(-45, 45)
        self.draw_circle(img)


class Clocks():
    def __init__(self):
        self.clocks = []
    def add(self, clock):
        self.clocks.append(clock)
    def set_digit(self, n):    
        for c in self.clocks:
            c.set_angle(clock_digits[n][c.x][c.y][0], clock_digits[n][c.x][c.y][1])
    def set_target_digit(self, n):    
        for c in self.clocks:
            c.set_target_angle(clock_digits[n][c.x][c.y][0], clock_digits[n][c.x][c.y][1], self.step_angle)
    def draw_digit(self, img, n):
        for c in self.clocks:
            c.set_angle(clock_digits[n][c.x][c.y][0], clock_digits[n][c.x][c.y][1])
            c.draw_arms(img)
    '''
    t : animation time(sec) for drawing the digit
    '''        
    def animation_start(self, img, n, t):
        self.step_angle = 360 / (t * FPS)
        self.set_target_digit(n)
        for c in self.clocks:
            c.set_angle(digit_basic[c.x][c.y][0], digit_basic[c.x][c.y][1])
            c.draw_arms(img)
    def animation_next(self, img):
        for c in self.clocks:
            c.step_angle()
            c.draw_arms(img)            
        return True        

def make_canvas(h, w, color):
    canvas = np.zeros([h,w,3], dtype=np.uint8)
    canvas.fill(color)
    return canvas


def initialize_clocks(img):
    global H0_clocks, H1_clocks, M0_clocks, M1_clocks
    for x in range(2):
        for y in range(3):
            clock = Clock(x, y, circle_color, circle_thickness, arm_color, arm_thickness, lmargin)
            clock.init_clock(img)
            H0_clocks.add(clock)
    for x in range(2):
        for y in range(3):
            clock = Clock(x, y, circle_color, circle_thickness, arm_color, arm_thickness, lmargin + 4*r + margin * 2)
            clock.init_clock(img)
            H1_clocks.add(clock)
    for x in range(2):
        for y in range(3):
            clock = Clock(x, y, circle_color, circle_thickness, arm_color2, arm_thickness, lmargin + 8*r + margin * 4)
            clock.init_clock(img)
            M0_clocks.add(clock)
    for x in range(2):
        for y in range(3):
            clock = Clock(x, y, circle_color, circle_thickness, arm_color2, arm_thickness, lmargin + 12*r + margin * 6)
            clock.init_clock(img)
            M1_clocks.add(clock)

H0_clocks = Clocks()
H1_clocks = Clocks()
M0_clocks = Clocks()
M1_clocks = Clocks()

canvas = make_canvas(Canvas_H, Canvas_W, 0)  
initialize_clocks(canvas)

t =10
now = datetime.now()

tcanvas = canvas.copy()
H0_clocks.animation_start(tcanvas, int(now.hour / 10), t)
H1_clocks.animation_start(tcanvas, now.hour % 10, t)
M0_clocks.animation_start(tcanvas, int(now.minute / 10), t)
M1_clocks.animation_start(tcanvas, now.minute % 10, t)
for i in range(int(t * FPS)):
    start = time.time()
    tcanvas = canvas.copy()
    H0_clocks.animation_next(tcanvas)
    H1_clocks.animation_next(tcanvas)
    M0_clocks.animation_next(tcanvas)
    M1_clocks.animation_next(tcanvas)
    sleep_tm = (SLEEP - (time.time() - start)) * 1000
    cv2.imshow("digit [%02d:%02d]"%(now.hour, now.minute), tcanvas)
    # print('[%d] sleep:%f'%(i, sleep_tm))
    cv2.waitKey(max(1, int(sleep_tm)))
cv2.waitKey(0)
cv2.destroyAllWindows()

Run the code!


python3 clock_adv.py


You should see a clockclock24 animation that tells you the current time.



Wrapping up

I made the speed and direction of the hour and minute hands the same. However, at https://codepen.io/Lorti/pen/XpQewQ/, the two needles are rotated in opposite directions, and the rotation speed is adjusted to rotate slowly at the last moment. If you want this luxurious effect, you can make various effects without difficulty by modifying the above code. If you are interested, please try it yourself.

You can download the source codes here(https://github.com/raspberry-pi-maker/OpenCV)














댓글

이 블로그의 인기 게시물

Image Processing #7 - OpenCV Text

Playing YouTube videos using OpenCV

OpenCV Installation - Rasbian Buster, Jessie, DietPi Buster