How to detect a circle and then lines in the circle? (Reading time off clock image)

2 views (last 30 days)
Hey All,
I'm trying to develop computer vision algorithm to read the time off of an analog clock. Any help would be appreciated. I've attached the sample image to read the time off of. I'm new to MATLAB and image proccessing with moderate coding experience in other languages. So far I've though of the following ideas:
  • Create two circles of varying radii on the clock and find where the clock hands intercept them. Then, take the angles from the origin of the circle and use some simple math to deduce time. (Don't know how to identify clock from background and create said circles though)
  • Use circular hough transform to find the clock face and then hough lines transform to find the clock-hands lines in the ciruclar region of interest and then extract the time from the angles of lines. Again, I've no experience in this sorta thing.
Thanks!

Answers (1)

DGM
DGM on 26 Sep 2024
Edited: DGM on 26 Sep 2024
I'm going to do this in a particular way because I want to, not because it's robust or univerally appropriate.
Let's start by trying to correct for perspective distortion.
% the source image
inpict = imread('image.jpg');
inpict = im2gray(inpict);
% these are the coordinates of the box corners [x y]
% you can get these using getpts() or impixelinfo() or datatips
boxm = [40 229; 829 253; 831 380; 34 356];
% assert that this is where they're supposed to be
% any coordinates that define a rectangle
boxf = [min(boxm(:,1)) min(boxm(:,2));
max(boxm(:,1)) min(boxm(:,2));
max(boxm(:,1)) max(boxm(:,2));
min(boxm(:,1)) max(boxm(:,2))];
% apply the transformation
TF = fitgeotrans(boxm,boxf,'projective');
outview = imref2d(size(inpict));
inpict = imwarp(inpict,TF,'fillvalues',255,'outputview',outview);
% show the corrected image
imshow(inpict)
That has the skew and keystone fixed, but the clock face is still elongated. We'll fix that in a bit. Next we can binarize the image. With a bit of sorting, we can get the clock face, stretch it, and get the hands.
% binarize
mask = imextendedmax(inpict,60);
mask = imclearborder(mask);
% show the mask
imshow(mask)
% find probable clock face
S = regionprops(mask,'centroid','boundingbox','majoraxislength','minoraxislength', ...
'area','convexarea','image');
numobj = numel(S);
% sort by area
[area idx] = sort(vertcat(S.Area),'descend');
S = S(idx);
% find ellipse-ness (clock face isn't circular due to foreshortening and remaining distortion)
ell = vertcat(S.ConvexArea)./(pi*vertcat(S.MinorAxisLength).*vertcat(S.MajorAxisLength))*4;
ell = 1 - abs(1 - ell);
% find distance to image center
D = sqrt(sum((vertcat(S.Centroid) - 0.5*fliplr(size(mask))).^2,2));
% pick which blob we think is the clock face
selectedblob = find(ell > 0.95 & D < prctile(D,10),1);
if isempty(selectedblob)
fprintf('no blob was selected\n')
return;
end
% correct for foreshortening
% the clock face is finally circular
% so angles should be uniformly spaced
% though due to depth, the pivot is not quite at the apparent center
% in this case, we're only off by about 4px, so i'm ignoring that
bbsize = S(selectedblob).BoundingBox(3:4);
clockmask = imresize(S(selectedblob).Image,[1 1].*max(bbsize));
clockmask = clockmask | ~bwconvhull(clockmask);
clockmask = bwareafilt(~clockmask,1); % assuming both hands are one blob
% show the clock face mask
imshow(clockmask)
Now comes the annoying part -- trying to figure out where the hands are. Given the popularity of goofy hand shapes that make it difficult for even humans to read accurately, we can expect that this is not going to be a universal approach. I'm avoiding using Hough transform, since we can't expect that hands are actually straight lines. In my experience, it's way more fiddly to try to get consistently useful results out of houghlines() anyway. I'm just going to use a radon transform and trust that the hands are dense objects. That leaves us with some ambiguity, but we can resolve it with some extra work.
% take radon xform over 180d sweep
% this gives us primary angles,
% but doesn't tell us which direction the hands point
theta = 0:180;
[R xp] = radon(clockmask,theta);
Rx = max(R,[],1);
plot(theta - 90,Rx)
xlabel('angle')
% find the two most prominent peaks
[pky,pkx,~,p] = findpeaks(Rx,theta-90);
[~,idx] = sort(p,'descend');
hth = pkx(idx(1:2));
hlen = pky(idx(1:2));
% sort by apparent hand length [hour minute]
[hlen idx] = sort(hlen,'ascend');
hth = hth(idx);
% figure out which way each hand is actually pointing
hsz = [5 150]; % sample box size [w l]
P0 = [0 -hsz(1)/2; hsz(2) -hsz(1)/2; hsz(2) hsz(1)/2; 0 hsz(1)/2];
for hand = 1:2
th = -hth(hand);
pxcount0 = countpxatangle(th,P0,clockmask);
pxcount1 = countpxatangle(th + 180,P0,clockmask);
if pxcount1 > pxcount0
hth(hand) = hth(hand) + 180;
end
end
% calculate time from angle
t = mod(90 - hth,360)/360.*[12 60];
t(1) = mod(floor(t(1))-1,12) + 1 % [hour min]
t = 1×2
1.0000 26.8333
<mw-icon class=""></mw-icon>
<mw-icon class=""></mw-icon>
% sample the mask using a polygon selection
% this should help avoid issues with odd hand shapes
function pxcount = countpxatangle(th,P0,clockmask)
sz = size(clockmask);
R = [cosd(th) -sind(th); sind(th) cosd(th)];
P = (R*P0.').' + sz([2 1])/2;
mk = poly2mask(P(:,1),P(:,2),sz(1),sz(2));
pxcount = nnz(mk & clockmask);
end
I attached an extra modified version of the same image which also works.
Will this work for images of other clocks in other positions? Not without making it work.
Will this work for this image at all other times of day? Ignoring the variability of lighting throughout the day, the answer is no. In cases where the hands overlap or are opposed to each other, the method of figuring out which hand is which won't work. When there are fewer than two clear peaks in Rx, it might become difficult to even tell whether the hands are overlapping or nearly-overlapping -- i.e. it would be unclear whether we need to discard the least prominent peak.
Like I said, I'm not out to make anything practical. It's an example that demonstrates a possible approach. It would obviously need more work.

Products


Release

R2020a

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!