17

I'm looking at a simple, programmatic way of detecting whether or not the user has drawn a circular shape. I'm working in C, but am happy to work from pseudo-code. A bit of Googling brings up a number of (hopefully) overly-complex methods.

I'm tracking the mouse coordinates as floats, and have created an array of vectors to track the mouse movement over time. Essentially I'm looking to detect when a circle has been drawn and then disgard all movement data not associated with that circle.

I have a basic idea of how this might be accomplished:

Track all movements using a polling function. Each time the function is polled the current mouse position is stored. Here, we loop through the historic position data and do a rough 'snap to position' to compare the two locations. If the new location is within a close enough distance to an old position, we remove all historic data before the old location.

While this works in theory, it's a mess in practice. Does anyone have any suggestions? Bonus points if the method suggested can detect whether it's been drawn clockwise or counter-clockwise.

jschmier
  • 15,458
  • 6
  • 54
  • 72
ndg
  • 2,585
  • 2
  • 33
  • 58
  • I did a similar thing with Multitouch for Java but I don't think that's a thing anymore. One could draw a line from point-to-point and calculate the https://en.wikipedia.org/wiki/Winding_number of the median of all the points, but I'm not sure how to pick the spot where random motion becomes circular. – Neil Sep 17 '19 at 20:58

5 Answers5

6

Based on your tracking/polling function, which pushes float pairs on a stack. This must be done on a regular timing interval.

  1. Do a threshold-based search for two equal entries in the list. Now you have two indexes in your stack; the first and the second equal entries. Consider this as a line.
  2. Get the absolute difference in indices. Then divide by two and get the coordinates of this point. (Center of the line.)
  3. You've got two points: thus you can get the radius of the circle, by getting the distance between the two points divided by two.
  4. Divide the number of step 2 by 2, now you've got the quarters.

    If the line at step 1 is vertical and the first point of the line is at the top: If the first quarter is left of the center-point, the circle was drawn counter-clockwise. If the first quarter is right of the center-point, the circle was drawn clockwise. If the first point of the line is at the bottom, reverse (i.e. ccw => cw and cw => ccw)

    If the line at step 1 is horizontal and the first point of the list is at the left: If the first quarter is above the center-point, the circle was drawn counter-clockwise. If the first quarter is below of the center-point, the circle was drawn clockwise. If the first point of the line is at the right, reverse.

  5. Check if it was a circle: iterate over all pairs of coordinates and calculate the distance to the center-point. Tweak the threshold of allowed distances from the calculated distance and the actual distance to the center-point.

In step 2 and 4 you can tweak this algorithm further by taking the average of several indices if the timing interval is very low (fast polling). For instance: there are 30 pairs in the array, then you average pairs at 0, 1 and 28, 29 to get the upper point. Do the same for all other points.

I hope this is easy enough.

Pindatjuh
  • 10,550
  • 1
  • 41
  • 68
  • If gesture detection is the main topic here I would actually go with some sort of an event which triggers a timer. I'm currently working on custom gestures using Qt and I use the pressing of a mouse's button as the starting point for gathering samples. This timer will then be stopped once another specific event occurs (for example releasing the mouse's button).During the starting and ending point in time one can detect if the mouse is moving and gather the cursor's positions. This way you will have less poor data plus well defined sets of points each describing a movement from start to finish. – rbaleksandar Dec 06 '16 at 12:44
6

You are definitely on the right track IMHO. Basically you need to compare each mouse point with the previous mouse point and calculate the angle between them (as envisioned on a unit circle where the first point is at the origin). For this you can use the formula:

double angle = atan2(y2 - y1, x2 - x1) * 180 / PI;

if (angle < 0)
    angle += 360;

What you end up with is that for clockwise movement, the angle will cycle in a positive direction, whereas for counterclockwise movement the angle will cycle in a negative direction. You can figure out if the current angle is greater or less than the previous one with the following logic:

if (angle2 > 270 && angle1 < 90)
{
    angle1 += 360
}
else if (angle1 > 270 && angle2 < 90)
{
    angle2 += 360
}

bool isPositive = (angle2-angle1 > 0);

If you get a certain number of vectors all with angles that are increasing (isPositive is true, let's say, 10 times), you can assume a clockwise circle is being drawn; if the tendency is negative (isPositive is false 10 times) it's a counterclockwise circle. :)

Mike
  • 61
  • 1
  • 2
  • Nice but this actually can be applied to any shape where the border points go in a clock-/counterclockwise direction. You can draw a square, a triangle etc. with the same logic and perhaps still get a circle. This is due to the motion of the hand and mouse. It is highly unlikely that the user can draw a perfect line (as a side of a square for instance) hence curves are the probable result which will result in false positive. This is of course not such a big issue if only circles are to be detected and nothing else which follows the same logical path. – rbaleksandar Dec 06 '16 at 12:48
1

Here's an algorithm to see if an array of points fits a circle:

  1. calculate the centroid of the points (average of all the x and y coordinates)

  2. calculate the distance of all points to the centroid

  3. find the maximum and minimum distances

  4. if maximum - minimum < tolerance, circular section detected

NB This will detect a section of a circle as well so you will need to determine that enough of an angle is swept through for it to be a full circle.

To do this:

  1. calculate centroid as above
  2. calculate angle between centroid and each point (use atan2 function)
  3. map angles to segments (I find 12 30 degree segments works for me; just divide angle by 30 and round down to integer - assuming you are working in degrees here)
  4. if all segments contain at least 1 point, then it is a circle (i.e. your mapped segments array contains all values between 0 and 11)

  5. bonus: increasing angle is anti-clockwise; decreasing is clockwise

Andrew Eades
  • 127
  • 1
  • 6
  • Wouldn't that give a false positive in the case of no movement at all? – flarn2006 Dec 08 '19 at 05:20
  • In the circle algorithm, you need at least 1 point in each segment so no. And in the case where you want to detect a section, you can only run the algorithm if you have x number of distinct points. Depending on the system, you may or may not be given point data for no movement. e.g. on iOS, you get data from touches moved. – Andrew Eades Dec 09 '19 at 07:33
0

Haven't tried this, but the idea came to mind reading your question, so might as well share it with you:

I'm assuming the circle has to be drawn within a reasonable amount of time, given a steady "sample-rate" of the mouse that would leave a known-size array of 2D vectors (points). Add them all and divide by the count of 2D vectors to get an estimate of the "center" point in the array. Then form vectors from this center-point to the points in the array and do dot-products (normalizing by vector length), making sure the sign of the dot-products remain identical for a range of points means those points all move in the same direction, a positive sign will indicate counter-clockwise movement, negative is just the opposite. If the accumulated angle exceeds 2 PI, a circular movement was drawn..

Good luck.

S.C. Madsen
  • 5,100
  • 5
  • 32
  • 50
  • Using time to restrict the movement is a nice idea. I'm also using it. If a user wants to draw a circle his intuition will tell him to do this as best as possible hence the likelihood for border loops or some weird non-circular motions is reduced quite a bit. The problem is that timing might feel ok for one user but be completely off for another one making this solution far from perfect. – rbaleksandar Dec 06 '16 at 12:50
0

1 - Pick any 3 of the points

2 - If the points are collinear +/- 'some buffer' then it isn't a circle.

3 - Use the method described on Wikipedia for finding the circumscribed circle for a triangle to find the midpoint and radius of your candidate circle

The circumcenter of a triangle can be constructed by drawing any two of the three perpendicular bisectors. For three non-collinear points, these two lines cannot be parallel, and the circumcenter is the point where they cross. Any point on the bisector is equidistant from the two points that it bisects, from which it follows that this point, on both bisectors, is equidistant from all three triangle vertices. The circumradius is the distance from it to any of the three vertices.

4 - Check the distance to the remaining points. If those points are within the 'candidate circle radius' +/- 'some buffer allowance' then it is a circle.

5 - To determine direction, simply calculate the angle between the first and 2nd points from the midpoint. A negative angle is right. A positive angle is left. (Could be reversed depending on the coordinate system you are using)

Dunk
  • 1,704
  • 1
  • 14
  • 19