Нарисуйте идеальный круг от пользовательского касания

У меня есть этот практический проект, который позволяет пользователю рисовать на экране, когда они касаются пальцами. Очень простое приложение, которое я сделал как упражнение назад. Мой маленький кузен взял на себя смелость рисовать вещи своим пальцем с моим iPad в этом приложении (рисунки детей: круг, линии и т.д., Что бы ни приходило ему в голову). Затем он начал рисовать круги, а затем он попросил меня сделать это "хорошим кругом" (из моего понимания: сделайте круг нарисован круглым, как мы знаем независимо от того, насколько стабильно мы пытаемся нарисовать что-то пальцем на экране, круг никогда не будет таким округленным, как круг должен быть).

Итак, мой вопрос заключается в том, что есть ли какой-либо способ в коде, где мы можем сначала обнаружить линию, нарисованную пользователем, которая образует круг и генерирует примерно одинаковый размер круга, делая его идеально круглым на экране. Не прямолинейная прямая линия - это то, что я знаю, как это сделать, но что касается круга, я не совсем понимаю, как это делать с помощью кварца или других методов.

Мое рассуждение состоит в том, что начало и конечная точка линии должны касаться или пересекать друг друга после того, как пользователь поднимает палец, чтобы оправдать тот факт, что он пытался на самом деле нарисовать круг.

Ответ 1

Иногда очень полезно потратить некоторое время на изобретательство колеса. Как вы, возможно, уже заметили, существует много фреймворков, но реализовать сложное, но при этом полезное решение не сложно, не вводя всю эту сложность. (Пожалуйста, не поймите меня неправильно, для какой-либо серьезной цели лучше использовать некоторые зрелые и проверенные для стабильности рамки).

Сначала я представлю свои результаты, а затем объясню им простую и понятную идею.

enter image description here

Вы увидите, что в моей реализации нет необходимости анализировать каждую точку и выполнять сложные вычисления. Идея состоит в том, чтобы обнаружить некоторую ценную метаинформацию. Я буду использовать tangent в качестве примера:

enter image description here

Позвольте идентифицировать простой и простой шаблон, типичный для выбранной формы:

enter image description here

Поэтому не так сложно реализовать механизм обнаружения круга, основанный на этой идее. См. Рабочую демонстрацию ниже (Извините, я использую Java как самый быстрый способ обеспечить этот быстрый и немного грязный пример):

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.HeadlessException;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class CircleGestureDemo extends JFrame implements MouseListener, MouseMotionListener {

    enum Type {
        RIGHT_DOWN,
        LEFT_DOWN,
        LEFT_UP,
        RIGHT_UP,
        UNDEFINED
    }

    private static final Type[] circleShape = {
        Type.RIGHT_DOWN,
        Type.LEFT_DOWN,
        Type.LEFT_UP,
        Type.RIGHT_UP};

    private boolean editing = false;
    private Point[] bounds;
    private Point last = new Point(0, 0);
    private List<Point> points = new ArrayList<>();

    public CircleGestureDemo() throws HeadlessException {
        super("Detect Circle");

        addMouseListener(this);
        addMouseMotionListener(this);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        setPreferredSize(new Dimension(800, 600));
        pack();
    }

    @Override
    public void paint(Graphics graphics) {
        Dimension d = getSize();
        Graphics2D g = (Graphics2D) graphics;

        super.paint(g);

        RenderingHints qualityHints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        qualityHints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        g.setRenderingHints(qualityHints);

        g.setColor(Color.RED);
        if (cD == 0) {
            Point b = null;
            for (Point e : points) {
                if (null != b) {
                    g.drawLine(b.x, b.y, e.x, e.y);
                }
                b = e;
            }
        }else if (cD > 0){
            g.setColor(Color.BLUE);
            g.setStroke(new BasicStroke(3));
            g.drawOval(cX, cY, cD, cD);
        }else{
            g.drawString("Uknown",30,50);
        }
    }


    private Type getType(int dx, int dy) {
        Type result = Type.UNDEFINED;

        if (dx > 0 && dy < 0) {
            result = Type.RIGHT_DOWN;
        } else if (dx < 0 && dy < 0) {
            result = Type.LEFT_DOWN;
        } else if (dx < 0 && dy > 0) {
            result = Type.LEFT_UP;
        } else if (dx > 0 && dy > 0) {
            result = Type.RIGHT_UP;
        }

        return result;
    }

    private boolean isCircle(List<Point> points) {
        boolean result = false;
        Type[] shape = circleShape;
        Type[] detected = new Type[shape.length];
        bounds = new Point[shape.length];

        final int STEP = 5;

        int index = 0;        
        Point current = points.get(0);
        Type type = null;

        for (int i = STEP; i < points.size(); i += STEP) {
            Point next = points.get(i);
            int dx = next.x - current.x;
            int dy = -(next.y - current.y);

            if(dx == 0 || dy == 0) {
                continue;
            }

            Type newType = getType(dx, dy);
            if(type == null || type != newType) {
                if(newType != shape[index]) {
                    break;
                }
                bounds[index] = current;
                detected[index++] = newType;
            }
            type = newType;            
            current = next;

            if (index >= shape.length) {
                result = true;
                break;
            }
        }

        return result;
    }

    @Override
    public void mousePressed(MouseEvent e) {
        cD = 0;
        points.clear();
        editing = true;
    }

    private int cX;
    private int cY;
    private int cD;

    @Override
    public void mouseReleased(MouseEvent e) {
        editing = false;
        if(points.size() > 0) {
            if(isCircle(points)) {
                cX = bounds[0].x + Math.abs((bounds[2].x - bounds[0].x)/2);
                cY = bounds[0].y;
                cD = bounds[2].y - bounds[0].y;
                cX = cX - cD/2;

                System.out.println("circle");
            }else{
                cD = -1;
                System.out.println("unknown");
            }
            repaint();
        }
    }

    @Override
    public void mouseDragged(MouseEvent e) {
        Point newPoint = e.getPoint();
        if (editing && !last.equals(newPoint)) {
            points.add(newPoint);
            last = newPoint;
            repaint();
        }
    }

    @Override
    public void mouseMoved(MouseEvent e) {
    }

    @Override
    public void mouseEntered(MouseEvent e) {
    }

    @Override
    public void mouseExited(MouseEvent e) {
    }

    @Override
    public void mouseClicked(MouseEvent e) {
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                CircleGestureDemo t = new CircleGestureDemo();
                t.setVisible(true);
            }
        });
    }
}

Не следует пытаться реализовать подобное поведение в iOS, так как вам просто нужно несколько событий и координат. Что-то вроде следующего (см. пример):

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch* touch = [[event allTouches] anyObject];
}

- (void)handleTouch:(UIEvent *)event {
    UITouch* touch = [[event allTouches] anyObject];
    CGPoint location = [touch locationInView:self];

}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    [self handleTouch: event];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    [self handleTouch: event];    
}

Есть несколько улучшений.

Начать в любой момент

Текущее требование - начать рисовать круг из верхней средней точки из-за следующего упрощения:

        if(type == null || type != newType) {
            if(newType != shape[index]) {
                break;
            }
            bounds[index] = current;
            detected[index++] = newType;
        }

Обратите внимание, что используется значение по умолчанию index. Простой поиск через доступные "части" формы удалит это ограничение. Обратите внимание, что для обнаружения полной формы вам понадобится круговой буфер:

enter image description here

По часовой стрелке и против часовой стрелки

Чтобы поддерживать оба режима, вам нужно будет использовать круговой буфер из предыдущего улучшения и поиска в обоих направлениях:

enter image description here

Нарисуйте эллипс

У вас есть все, что вам нужно, в массиве bounds.

enter image description here

Просто используйте эти данные:

cWidth = bounds[2].y - bounds[0].y;
cHeight = bounds[3].y - bounds[1].y;

Другие жесты (необязательно)

Наконец, вам просто нужно правильно обработать ситуацию, когда dx (или dy) равна нулю, чтобы поддерживать другие жесты:

enter image description here

Обновление

Этот небольшой PoC получил достаточно пристальное внимание, поэтому я немного обновил код, чтобы он работал плавно и предоставлял некоторые подсказки для рисования, выделял опорные точки и т.д.:

введите описание изображения здесь

Вот код:

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.HeadlessException;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class CircleGestureDemo extends JFrame {

    enum Type {

        RIGHT_DOWN,
        LEFT_DOWN,
        LEFT_UP,
        RIGHT_UP,
        UNDEFINED
    }

    private static final Type[] circleShape = {
        Type.RIGHT_DOWN,
        Type.LEFT_DOWN,
        Type.LEFT_UP,
        Type.RIGHT_UP};

    public CircleGestureDemo() throws HeadlessException {
        super("Circle gesture");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLayout(new BorderLayout());
        add(BorderLayout.CENTER, new GesturePanel());
        setPreferredSize(new Dimension(800, 600));
        pack();
    }

    public static class GesturePanel extends JPanel implements MouseListener, MouseMotionListener {

        private boolean editing = false;
        private Point[] bounds;
        private Point last = new Point(0, 0);
        private final List<Point> points = new ArrayList<>();

        public GesturePanel() {
            super(true);
            addMouseListener(this);
            addMouseMotionListener(this);
        }

        @Override
        public void paint(Graphics graphics) {
            super.paint(graphics);

            Dimension d = getSize();
            Graphics2D g = (Graphics2D) graphics;

            RenderingHints qualityHints = new RenderingHints(RenderingHints.KEY_ANTIALIASING,
                    RenderingHints.VALUE_ANTIALIAS_ON);
            qualityHints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);

            g.setRenderingHints(qualityHints);

            if (!points.isEmpty() && cD == 0) {
                isCircle(points, g);
                g.setColor(HINT_COLOR);
                if (bounds[2] != null) {
                    int r = (bounds[2].y - bounds[0].y) / 2;
                    g.setStroke(new BasicStroke(r / 3 + 1));
                    g.drawOval(bounds[0].x - r, bounds[0].y, 2 * r, 2 * r);
                } else if (bounds[1] != null) {
                    int r = bounds[1].x - bounds[0].x;
                    g.setStroke(new BasicStroke(r / 3 + 1));
                    g.drawOval(bounds[0].x - r, bounds[0].y, 2 * r, 2 * r);
                }
            }

            g.setStroke(new BasicStroke(2));
            g.setColor(Color.RED);

            if (cD == 0) {
                Point b = null;
                for (Point e : points) {
                    if (null != b) {
                        g.drawLine(b.x, b.y, e.x, e.y);
                    }
                    b = e;
                }

            } else if (cD > 0) {
                g.setColor(Color.BLUE);
                g.setStroke(new BasicStroke(3));
                g.drawOval(cX, cY, cD, cD);
            } else {
                g.drawString("Uknown", 30, 50);
            }
        }

        private Type getType(int dx, int dy) {
            Type result = Type.UNDEFINED;

            if (dx > 0 && dy < 0) {
                result = Type.RIGHT_DOWN;
            } else if (dx < 0 && dy < 0) {
                result = Type.LEFT_DOWN;
            } else if (dx < 0 && dy > 0) {
                result = Type.LEFT_UP;
            } else if (dx > 0 && dy > 0) {
                result = Type.RIGHT_UP;
            }

            return result;
        }

        private boolean isCircle(List<Point> points, Graphics2D g) {
            boolean result = false;
            Type[] shape = circleShape;
            bounds = new Point[shape.length];

            final int STEP = 5;
            int index = 0;
            int initial = 0;
            Point current = points.get(0);
            Type type = null;

            for (int i = STEP; i < points.size(); i += STEP) {
                final Point next = points.get(i);
                final int dx = next.x - current.x;
                final int dy = -(next.y - current.y);

                if (dx == 0 || dy == 0) {
                    continue;
                }

                final int marker = 8;
                if (null != g) {
                    g.setColor(Color.BLACK);
                    g.setStroke(new BasicStroke(2));
                    g.drawOval(current.x - marker/2, 
                               current.y - marker/2, 
                               marker, marker);
                }

                Type newType = getType(dx, dy);
                if (type == null || type != newType) {
                    if (newType != shape[index]) {
                        break;
                    }
                    bounds[index++] = current;
                }

                type = newType;
                current = next;
                initial = i;

                if (index >= shape.length) {
                    result = true;
                    break;
                }
            }
            return result;
        }

        @Override
        public void mousePressed(MouseEvent e) {
            cD = 0;
            points.clear();
            editing = true;
        }

        private int cX;
        private int cY;
        private int cD;

        @Override
        public void mouseReleased(MouseEvent e) {
            editing = false;
            if (points.size() > 0) {
                if (isCircle(points, null)) {
                    int r = Math.abs((bounds[2].y - bounds[0].y) / 2);
                    cX = bounds[0].x - r;
                    cY = bounds[0].y;
                    cD = 2 * r;
                } else {
                    cD = -1;
                }
                repaint();
            }
        }

        @Override
        public void mouseDragged(MouseEvent e) {
            Point newPoint = e.getPoint();
            if (editing && !last.equals(newPoint)) {
                points.add(newPoint);
                last = newPoint;
                repaint();
            }
        }

        @Override
        public void mouseMoved(MouseEvent e) {
        }

        @Override
        public void mouseEntered(MouseEvent e) {
        }

        @Override
        public void mouseExited(MouseEvent e) {
        }

        @Override
        public void mouseClicked(MouseEvent e) {
        }
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                CircleGestureDemo t = new CircleGestureDemo();
                t.setVisible(true);
            }
        });
    }

    final static Color HINT_COLOR = new Color(0x55888888, true);
}

Ответ 2

Классическая технология Computer Vision для обнаружения фигуры - это трансформация Hough. Одна из приятных вещей о преобразовании Хафа заключается в том, что он очень терпим к частичным данным, несовершенным данным и шуму. Использование Hough для круга: http://en.wikipedia.org/wiki/Hough_transform#Circle_detection_process

Учитывая, что ваш круг рисован, я думаю, что преобразование Хафа может быть хорошим для вас.

Здесь "упрощенное" объяснение, я прошу прощения за то, что это не так просто. Большая часть этого из школьного проекта, который я сделал много лет назад.

Трансформация Хафа - это схема голосования. Выделяется двухмерный массив целых чисел, и все элементы устанавливаются равными нулю. Каждый элемент соответствует одному пикселю в анализируемом изображении. Этот массив называется массивом аккумуляторов, так как каждый элемент будет накапливать информацию, голоса, указывая на возможность того, что пиксель может находиться в начале круга или дуги.

К изображению обрамляют детектор края градиентного оператора, а краевые пиксели или эглеры записываются. Эггель - это пиксель, который отличается своей интенсивностью или цветом относительно своих соседей. Степень разности называется величиной градиента. Для каждого эжекса достаточной величины применяется схема голосования, которая будет увеличивать элементы массива аккумуляторов. Элементы, увеличивающиеся (проголосовавшие за), соответствуют возможному происхождению кругов, проходящих через рассматриваемый эдж. Желаемый результат состоит в том, что если существует дуга, то истинное происхождение будет получать больше голосов, чем ложное происхождение.

Обратите внимание, что элементы массива аккумуляторов, которые посещаются для голосования, образуют круг вокруг рассматриваемого edgel. Вычисление координат x, y для голосования за тем же, что и вычисление координат x, y круга, который вы рисуете.

В вашем обратном изображении вы можете использовать набор (цветные) пиксели напрямую, а не вычислять эглеры.

Теперь с неадекватно расположенными пикселями вы не обязательно получите один элемент массива аккумулятора с наибольшим количеством голосов. Вы можете получить коллекцию соседних элементов массива с кучей голосов, кластера. Центр тяжести этого кластера может предложить хорошее приближение для начала координат.

Обратите внимание, что вам, возможно, придется выполнить преобразование Hough для разных значений радиуса R. Тот, который производит более плотный кластер голосов, "лучше" подходит.

Существуют различные методы, используемые для уменьшения голосов за ложное происхождение. Например, одним преимуществом использования эджелов является то, что они не только имеют величину, но и имеют направление. При голосовании нам нужно только голосовать за возможное происхождение в соответствующем направлении. Места, получающие голоса, образуют дугу, а не полный круг.

Вот пример. Начнем с круга радиуса 1 и инициализированного массива аккумуляторов. Поскольку каждый пиксель считается потенциальным источником, за него проголосовали. Истинное происхождение получает наибольшее количество голосов, которое в этом случае равно четырем.

.  empty pixel
X  drawn pixel
*  drawn pixel currently being considered

. . . . .   0 0 0 0 0
. . X . .   0 0 0 0 0
. X . X .   0 0 0 0 0
. . X . .   0 0 0 0 0
. . . . .   0 0 0 0 0

. . . . .   0 0 0 0 0
. . X . .   0 1 0 0 0
. * . X .   1 0 1 0 0
. . X . .   0 1 0 0 0
. . . . .   0 0 0 0 0

. . . . .   0 0 0 0 0
. . X . .   0 1 0 0 0
. X . X .   1 0 2 0 0
. . * . .   0 2 0 1 0
. . . . .   0 0 1 0 0

. . . . .   0 0 0 0 0
. . X . .   0 1 0 1 0
. X . * .   1 0 3 0 1
. . X . .   0 2 0 2 0
. . . . .   0 0 1 0 0

. . . . .   0 0 1 0 0
. . * . .   0 2 0 2 0
. X . X .   1 0 4 0 1
. . X . .   0 2 0 2 0
. . . . .   0 0 1 0 0

Ответ 3

Вот еще один способ. Используя UIView touchhesBegan, touchsMoved, touchEEded и добавление точек в массив. Вы разделяете массив на половинки и проверяете, будет ли каждая точка в одном массиве примерно одинакового диаметра от его аналога в другом массиве, как и все остальные пары.

    NSMutableArray * pointStack;

    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
    {
        // Detect touch anywhere
    UITouch *touch = [touches anyObject];


    pointStack = [[NSMutableArray alloc]init];

    CGPoint touchDownPoint = [touch locationInView:touch.view];


    [pointStack addObject:touchDownPoint];

    }


    /**
     * 
     */
    - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
    {

            UITouch* touch = [touches anyObject];
            CGPoint touchDownPoint = [touch locationInView:touch.view];

            [pointStack addObject:touchDownPoint];  

    }

    /**
     * So now you have an array of lots of points
     * All you have to do is find what should be the diameter
     * Then compare opposite points to see if the reach a similar diameter
     */
    - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
    {
            uint pointCount = [pointStack count];

    //assume the circle was drawn a constant rate and the half way point will serve to calculate or diameter
    CGPoint startPoint = [pointStack objectAtIndex:0];
    CGPoint halfWayPoint = [pointStack objectAtIndex:floor(pointCount/2)];

    float dx = startPoint.x - halfWayPoint.x;
    float dy = startPoint.y - halfWayPoint.y;


    float diameter = sqrt((dx*dx) + (dy*dy));

    bool isCircle = YES;// try to prove false!

    uint indexStep=10; // jump every 10 points, reduce to be more granular

    // okay now compare matches
    // e.g. compare indexes against their opposites and see if they have the same diameter
    //
      for (uint i=indexStep;i<floor(pointCount/2);i+=indexStep)
      {

      CGPoint testPointA = [pointStack objectAtIndex:i];
      CGPoint testPointB = [pointStack objectAtIndex:floor(pointCount/2)+i];

      dx = testPointA.x - testPointB.x;
      dy = testPointA.y - testPointB.y;


      float testDiameter = sqrt((dx*dx) + (dy*dy));

      if(testDiameter>=(diameter-10) && testDiameter<=(diameter+10)) // +/- 10 ( or whatever degree of variance you want )
      {
      //all good
      }
      else
      {
      isCircle=NO;
      }

    }//end for loop

    NSLog(@"iCircle=%i",isCircle);

}

Звучит хорошо?:)

Ответ 4

Я не эксперт по распознаванию образов, но вот как я мог бы подойти к проблеме.

Во-первых, при показе пользовательского пути как от руки, тайно накапливайте список выборок точки (x, y) вместе со временем. Вы можете получить оба факта из событий перетаскивания, объединить их в простой объект модели и скомпилировать их в изменяемом массиве.

Вероятно, вы хотите довольно часто брать образцы - скажем, каждые 0,1 секунды. Другой возможностью было бы начать очень часто, может быть, каждые 0,05 секунды, и смотреть, как долго пользователь тащит; если они тянут дольше, чем некоторое количество времени, затем уменьшайте частоту выборки (и удаляйте любые пробы, которые были пропущены) примерно на 0,2 секунды.

(И не принимайте мои цифры за Евангелие, потому что я просто вытащил их из шляпы. Экспериментируйте и находите лучшие значения.)

Во-вторых, проанализируйте образцы.

Вы хотите получить два факта. Во-первых, центр формы, который (IIRC) должен быть просто средним для всех точек. Во-вторых, средний радиус каждого образца из этого центра.

Если, как предположил @user1118321, вы хотите поддерживать многоугольники, тогда остальная часть анализа состоит в принятии такого решения: хочет ли пользователь нарисовать круг или многоугольник. Вы можете посмотреть образцы как многоугольник, чтобы начать это определение.

Существует несколько критериев, которые вы можете использовать:

  • Время: если пользователь навешивается дольше в некоторых точках, чем другие (которые, если образцы находятся на постоянном интервале, будут отображаться как совокупность последовательных выборок рядом друг с другом в пространстве), это могут быть углы. Вы должны сделать свой угловой порог небольшим, чтобы пользователь мог сделать это бессознательно, вместо того, чтобы сознательно останавливаться на каждом углу.
  • Угол. Круг будет иметь примерно одинаковый угол от одного образца до следующего. Многоугольник будет иметь несколько углов, соединенных отрезками прямой линии; углы - это углы. Для правильного многоугольника (круг к нерегулярному многоугольному эллипсу) угловые углы должны быть примерно одинаковыми; неправильный многоугольник будет иметь разные угловые углы.
  • Интервал: правильные углы многоугольника будут равными по размеру в пределах размера angular, а радиус будет постоянным. Неправильный многоугольник будет иметь нерегулярные интервалы angular и/или непостоянный радиус.

Третий и последний шаг - создать форму с центром в предварительно определенной центральной точке с ранее определенным радиусом.

Нет гарантий, что все, что я сказал выше, будет работать или быть эффективным, но я надеюсь, что он по крайней мере доставит вас на правильном пути - и, пожалуйста, если кто-то, кто знает больше о признании формы, чем я (что очень мало bar) видит это, не стесняйтесь оставлять комментарии или свой собственный ответ.

Ответ 5

Мне очень повезло с хорошо обученным распознавателем $1 (http://depts.washington.edu/aimgroup/proj/dollar/). Я использовал его для кругов, линий, треугольников и квадратов.

Это было давно, до UIGestureRecognizer, но я думаю, что должно быть легко создать соответствующие подклассы UIGestureRecognizer.

Ответ 6

Как только вы определите, что пользователь закончил рисовать свою форму там, где они начали, вы можете взять образец координат, который они нарисовали, и попытаться подгонять их к кругу.

Для решения этой проблемы существует решение MATLAB: http://www.mathworks.com.au/matlabcentral/fileexchange/15060-fitcircle-m

Основываясь на бумаге "Минимальная квадратура" кругов и эллипсов Уолтера Гандера, Джин Х. Голуб и Рольф Штребель: http://www.emis.de/journals/BBMS/Bulletin/sup962/gander.pdf

Д-р Йен Коуп из Университета Кентербери, Нью-Йорк, опубликовал статью с рефератом:

Задача определения круга наилучшего соответствия множеству точек в плоскости (или очевидное обобщение на n-размеры) легко сформулированная как нелинейная общая проблема наименьших квадратов, которая может быть решена с использованием алгоритма минимизации Гаусса-Ньютона. Эта Показано, что прямолинейный подход является неэффективным и чрезвычайно чувствительных к наличию выбросов. Альтернативная формулировка позволяет свести проблему к задаче линейных наименьших квадратов которая тривиально решена. Показано, что рекомендованный подход добавленное преимущество быть менее чувствительным к выбросам, чем нелинейный метод наименьших квадратов.

http://link.springer.com/article/10.1007%2FBF00939613

Файл MATLAB может вычислять как нелинейную TLS, так и линейную проблему LLS.

Ответ 7

Здесь довольно простой способ:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event

предполагая эту матричную сетку:

 A B C D E F G H
1      X X
2    X     X 
3  X         X
4  X         X
5    X     X
6      X X
7
8

Поместите некоторые UIViews в местоположения "X" и проверьте их, чтобы они попали (последовательно). Если они все попадут в последовательность, я думаю, что было бы справедливо позволить пользователю сказать: "Хорошо, вы нарисовали круг"

Звучит хорошо? (и просто)