IT

사용자의 터치로 완벽한 원 그리기

itgroup 2023. 7. 2. 19:14
반응형

사용자의 터치로 완벽한 원 그리기

저는 사용자가 손가락으로 터치할 때 화면에 그림을 그릴 수 있는 연습 프로젝트를 가지고 있습니다.아주 간단한 앱 나는 운동으로 했습니다.제 어린 사촌은 이 앱에서 제 아이패드로 손가락으로 사물을 그립니다(아이들이 그리는 그림: 원, 선 등, 그의 생각이 무엇이든).그리고 나서 그는 원을 그리기 시작했고, 그리고 나서 저에게 "좋은 원"으로 만들어 달라고 요청했습니다. (제가 알기로는, 우리가 화면에 손가락으로 얼마나 안정적으로 무언가를 그리려고 해도, 원은 결코 원만큼 둥글지 않습니다.)

여기서 제 질문은 사용자가 그린 선을 먼저 감지하여 원을 형성하고 화면에서 완벽하게 원형으로 만들어 대략 같은 크기의 원을 생성할 수 있는 코드가 있을까 하는 것입니다.직선이 아닌 직선을 만드는 것은 제가 어떻게 해야 하는지 알 수 있는 일이지만, 서클에 대해서는 쿼츠나 다른 방법으로 어떻게 해야 할지 잘 모르겠습니다.

제 추론은 사용자가 손가락을 들어 실제로 원을 그리려고 했다는 사실을 정당화하기 위해 선의 시작점과 끝점이 서로 닿거나 교차해야 한다는 것입니다.

때때로 바퀴를 재창조하는 데 시간을 보내는 것은 정말 유용합니다.많은 프레임워크가 있다는 것을 이미 알고 계시겠지만, 이러한 복잡성을 모두 도입하지 않고 단순하지만 유용한 솔루션을 구현하는 것은 그리 어려운 일이 아닙니다. (오해하지 마십시오. 어떤 심각한 목적이든 성숙하고 안정적인 프레임워크를 사용하는 것이 좋습니다.)

제가 먼저 결과를 발표하고 그 뒤에 숨겨진 간단하고 간단한 아이디어를 설명하겠습니다.

enter image description here

모든 점을 분석하고 복잡한 계산을 수행할 필요가 없습니다.이 아이디어는 가치 있는 메타 정보를 찾는 것입니다.탄젠트를 예로 들어 보겠습니다.

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;
        }

은 " 기값은다같다니습과음본다▁the▁of▁please"입니다.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는 상당히 높은 관심을 받았기 때문에 원활하게 작동하고 도면 힌트, 하이라이트 지원 포인트 등을 제공하기 위해 코드를 약간 업데이트했습니다.

enter image description here

코드는 다음과 같습니다.

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);
}

모양을 감지하는 고전적인 컴퓨터 비전 기술은 Hough Transform입니다.Hough Transform의 좋은 점 중 하나는 부분 데이터, 불완전한 데이터 및 노이즈에 매우 내성이 있다는 것입니다.원형에 Hough 사용하기: http://en.wikipedia.org/wiki/Hough_transform#Circle_detection_process

당신의 원이 손으로 그려졌다는 것을 고려할 때, 나는 호프 변환이 당신과 잘 어울릴 것이라고 생각합니다.

여기 "간단한" 설명이 있습니다. 그렇게 간단하지 않은 점 사과드립니다.대부분은 제가 수년 전에 했던 학교 프로젝트에서 나온 것입니다.

Hough Transform은 투표 계획입니다.2차원 정수 배열이 할당되고 모든 요소가 0으로 설정됩니다.각 요소는 분석 중인 영상의 단일 픽셀에 해당합니다.이 배열은 각 요소에 정보와 투표가 축적되어 픽셀이 원 또는 호의 원점에 있을 가능성을 나타내기 때문에 축열기 배열이라고 합니다.

그라데이션 연산자 에지 검출기가 영상에 적용되고 에지 픽셀 또는 에지가 기록됩니다.에지엘은 인접한 픽셀에 대해 강도나 색상이 다른 픽셀입니다.차이의 정도를 기울기 크기라고 합니다.충분한 크기의 각 에지에 대해 어큐뮬레이터 배열의 요소를 증가시키는 투표 방식이 적용됩니다.증분(투표)되는 요소는 고려 중인 모서리를 통과하는 원의 가능한 원점에 해당합니다.원하는 결과는 호가 존재할 경우 실제 원점이 거짓 원점보다 더 많은 표를 받게 됩니다.

투표를 위해 방문하는 축전지 배열의 요소는 고려 중인 에지를 중심으로 원을 형성합니다.투표할 x,y 좌표를 계산하는 것은 그리는 원의 x,y 좌표를 계산하는 것과 같습니다.

손으로 그린 이미지에서는 가장자리를 계산하는 대신 세트(색상) 픽셀을 직접 사용할 수 있습니다.

위치가 불완전한 픽셀을 사용하면 가장 많은 표를 가진 단일 축열기 배열 요소를 얻을 수 없습니다.다수의 투표와 함께 인접한 배열 요소들의 집합, 즉 클러스터를 얻을 수 있습니다.이 군집의 무게중심은 원점에 대한 좋은 근사치를 제공할 수 있습니다.

다양한 반지름 R 값에 대해 Hough Transform을 실행해야 할 수도 있습니다.더 밀도가 높은 투표 집단을 만드는 것은 "더 나은" 적합성입니다.

잘못된 기원에 대한 표를 줄이기 위해 사용할 수 있는 다양한 기술이 있습니다.예를 들어 에지젤을 사용할 때의 한 가지 장점은 크기뿐만 아니라 방향도 있다는 것입니다.투표할 때 우리는 적절한 방향으로 가능한 기원에 대해서만 투표하면 됩니다.투표를 받는 장소는 완전한 원이 아니라 호를 형성할 것입니다.

여기 예가 있어요.먼저 반지름 1의 원과 초기화된 축전지 배열로 시작합니다.각 픽셀은 잠재적인 기원으로 간주되므로 투표됩니다.진원지는 4표로 가장 많은 표를 받습니다.

.  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

여기 다른 방법이 있습니다.UIView를 사용하여 시작, 이동, 종료를 누르고 배열에 점을 추가합니다.배열을 반으로 나누고 한 배열의 모든 점이 다른 배열의 모든 점과 거의 같은 지름인지 테스트합니다.

    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);

}

괜찮겠어요?:)

저는 형상 인식 전문가는 아니지만, 문제에 접근할 수 있는 방법은 이렇습니다.

첫째, 사용자의 경로를 자유롭게 표시하면서 시간과 함께 포인트(x, y) 샘플 목록을 몰래 축적합니다.드래그 이벤트에서 두 가지 사실을 가져와 단순한 모델 개체로 래핑하여 가변 배열로 쌓을 수 있습니다.

0.1초마다 표본을 상당히 자주 추출할 수 있습니다.또 다른 가능성은 0.05초마다 매우 자주 시작하여 사용자가 얼마나 오래 끌는지 관찰하는 것입니다. 시간이 더 오래 끌면 샘플 빈도를 0.2초 정도로 낮춥니다.

(그리고 제 번호를 복음으로 받아들이지 마세요. 제가 방금 모자에서 꺼냈기 때문입니다.실험을 통해 더 나은 값을 찾습니다.)

두 번째로, 표본을 분석합니다.

당신은 두 가지 사실을 도출하고 싶을 것입니다.첫째, 형상의 중심, 즉 IIRC는 모든 점의 평균이어야 합니다.둘째, 해당 중심에서 각 표본의 평균 반지름입니다.

@user1118321이 예상한 대로 다각형을 지원하려면 나머지 분석은 사용자가 원을 그릴지 다각형을 그릴지 결정하는 것으로 구성됩니다.이를 결정하기 위해 샘플을 시작할 다각형으로 볼 수 있습니다.

다음과 같은 몇 가지 기준을 사용할 수 있습니다.

  • 시간: 샘플이 일정한 간격을 유지하는 경우(샘플이 공간에서 서로 인접한 연속된 샘플 클러스터로 나타나는 경우) 사용자가 다른 지점보다 더 오래 호버링하면 해당 지점이 모서리일 수 있습니다.사용자가 의도적으로 각 코너에서 일시 중지할 필요 없이 무의식적으로 이 작업을 수행할 수 있도록 코너 임계값을 작게 만들어야 합니다.
  • 각도: 원은 한 표본에서 다음 표본까지 전체적으로 거의 동일한 각도를 갖습니다.다각형에는 여러 개의 각도가 직선 세그먼트로 연결됩니다. 각도는 모서리입니다.일반 폴리곤(비정규 폴리곤의 타원에 대한 원)의 경우 모서리 각도는 모두 거의 같아야 합니다. 비정규 폴리곤은 다른 모서리 각도를 가집니다.
  • 간격: 정다각형의 모서리는 각 차원 내에서 동일한 공간으로 떨어져 있으며 반지름은 일정합니다.불규칙한 다각형은 불규칙한 각도 간격 및/또는 일정하지 않은 반지름을 갖습니다.

세 번째이자 마지막 단계는 이전에 결정된 중심점을 중심으로 이전에 결정된 반지름을 사용하여 형상을 만드는 것입니다.

제가 위에서 말한 것이 효과적이거나 효율적일 것이라는 보장은 없지만, 적어도 여러분이 올바른 방향으로 나아가기를 바랍니다. 그리고 저보다 모양 인식에 대해 더 잘 아는 사람(매우 낮은 막대)이 이를 본다면, 언제든지 댓글이나 여러분 자신의 답변을 올리세요.

사용자가 도형 그리기를 시작한 위치에서 완료했다고 판단하면 그들이 그린 좌표의 샘플을 가져와 원에 맞춰 볼 수 있습니다.

문제에 대한 MATLAB 솔루션은 다음과 같습니다. http://www.mathworks.com.au/matlabcentral/fileexchange/15060-fitcircle-m

이것은 월터 갠더, 진 H. 골럽, 롤프 스트렐의 원과 타원의 최소 제곱 피팅 논문을 기반으로 합니다. http://www.emis.de/journals/BBMS/Bulletin/sup962/gander.pdf

뉴질랜드 캔터베리 대학의 이안 쿠페 박사는 다음과 같은 추상적인 논문을 발표했습니다.

평면의 점 집합(또는 n차원에 대한 명백한 일반화)에 가장 적합한 원을 결정하는 문제는 가우스-뉴턴 최소화 알고리듬을 사용하여 해결할 수 있는 비선형 총 최소 제곱 문제로 쉽게 공식화됩니다.이러한 직접적인 접근 방식은 비효율적이며 특이치의 존재에 매우 민감한 것으로 나타났습니다.대안적인 공식을 사용하면 문제를 사소한 문제로 해결할 수 있는 선형 최소 제곱 문제로 줄일 수 있습니다.권장 접근법은 비선형 최소 제곱 접근법보다 특이치에 훨씬 덜 민감하다는 추가적인 이점이 있는 것으로 나타났습니다.

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

MATLAB 파일은 비선형 TLS와 선형 LLS 문제를 모두 계산할 수 있습니다.

저는 제대로 훈련된 1달러 인식기(http://depts.washington.edu/aimgroup/proj/dollar/) 에서 꽤 운이 좋았습니다.원, 선, 삼각형, 사각형에 사용했습니다.

UIGesture Recognizer 이전에는 오래되었지만 적절한 UIGesture Recognizer 하위 클래스를 만드는 것은 쉬울 것이라고 생각합니다.

다음은 매우 간단한 사용 방법입니다.

- (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

"X" 위치에 몇 개의 UIView를 배치하고 (순서대로) 적중 여부를 테스트합니다. 모두 연속으로 맞으면 사용자가 "원을 그리셨습니다"라고 말하는 것이 공평할 것 같습니다.

괜찮은 것 같습니까? (그리고 단순합니다)

사용자가 터치하는 픽셀은 x-y 좌표의 모음입니다.Ian Coope는 여기서 반복적이지 않은 원을 맞추기 위한 선형 최소 제곱 알고리즘을 제안했습니다. https://ir.canterbury.ac.nz/handle/10092/11104 .이 방법은 단순 변수 변경을 사용하여 적합치를 선형화하는 것입니다.

는 여기에 설명된 간단한 파이썬 구현을 만들었습니다: https://scikit-guess.readthedocs.io/en/latest/generated/skg.nsphere_fit.html .

GitHub: https://github.com/madphysicist/scikit-guess/blob/master/src/skg/nsphere.py 에서 소스를 찾을 수 있습니다.함수의 길이가 약 20줄에 불과하기 때문에 행렬을 반전시킬 수 있는 라이브러리에 액세스할 수 있는 한 원하는 언어로 변환하는 데 문제가 없습니다.실제로 여기서 설명하는 문제는 산술 연산으로 수동으로 수행할 수 있는 3x3 행렬만 반전하면 됩니다.

다음은 2D 사례에 특화된 간단한 Java 구현입니다.프로덕션 애플리케이션이나 픽셀 수가 많은 경우에는 스케일링을 포함하지 않았지만, 이는 매우 쉬운 사전 및 사후 처리 단계로 독자에게 연습으로 남겨집니다.

// This is just a container for the result for the example.
// Make it proper with getters and setters if you like.
public class Circle
{
    public final double radius;
    public final double x;
    public final double y;

    public Circle(double radius, double x, double y)
    {
        this.radius = radius;
        this.x = x;
        this.y = y;
    }

    public static fit(int[] x, int[] y)
    {
        // exercise for the reader: check that x.length == y.length

        // To solve b * x = d in terms of least-squares projection
        //   1. bT * b * x = bT * y
        //   2. x = inv(bT * b) * bT * d
        // Matrix b[i] = [x[i], y[i], 1]
        // Vector d[i] = [x[i]*x[i] + y[i]*y[i]]
        long[][] bTb = new long[3][3] = {{0L, 0L, 0L},
                                         {0L, 0L, 0L},
                                         {0L, 0L, 0L}};
        long[] bTd = new long[3] {0L, 0L, 0L};

        for(int i = 0; i < x.length; i++) {
            long x2 = x[i] * x[i];
            long y2 = y[i] * y[i];
            long xy = x[i] * y[i];
            bTb[0][0] += x2;
            bTb[0][1] += xy;
            bTb[1][0] += xy;
            bTb[1][1] += y2;
            bTb[0][2] += x[i];
            bTb[2][0] += x[i];
            bTb[1][2] += y[i];
            bTb[2][1] += y[i];
            bTb[2][2] += 1L;
            long d = x2 + y2;
            bTd[0] += x[i] * d;
            bTd[1] += y[i] * d;
            bTd[2] += d;
        }

        // invert the matrix, e.g.: https://www.wikihow.com/Find-the-Inverse-of-a-3x3-Matrix
        double det_bTb =
            bTb[0][0] * (bTb[1][1] * bTb[2][2] - bTb[2][1] * bTb[1][2]) -
            bTb[0][1] * (bTb[1][0] * bTb[2][2] - bTb[2][0] * bTb[1][2]) +
            bTb[0][2] * (bTb[1][0] * bTb[2][1] - bTb[2][0] * bTb[1][1]);
        // exercise for reader: check if determinant is zero
        double[][] inv_bTb = new double[3][3];
        inv_bTb[0][0] = (double)(bTb[1][1] * bTb[2][2] - bTb[1][2] * bTb[2][1]) / det_bTb;
        inv_bTb[0][1] = (double)(bTb[0][2] * bTb[2][1] - bTb[0][1] * bTb[2][2]) / det_bTb;
        inv_bTb[0][2] = (double)(bTb[0][1] * bTb[1][2] - bTb[0][2] * bTb[1][1]) / det_bTb;
        inv_bTb[1][0] = (double)(bTb[2][0] * bTb[1][2] - bTb[1][0] * bTb[2][2]) / det_bTb;
        inv_bTb[1][1] = (double)(bTb[0][0] * bTb[2][2] - bTb[2][0] * bTb[0][2]) / det_bTb;
        inv_bTb[1][2] = (double)(bTb[1][0] * bTb[0][2] - bTb[0][0] * bTb[1][2]) / det_bTb;
        inv_bTb[2][0] = (double)(bTb[1][0] * bTb[2][1] - bTb[2][0] * bTb[1][1]) / det_bTb;
        inv_bTb[2][1] = (double)(bTb[2][0] * bTb[0][1] - bTb[0][0] * bTb[2][1]) / det_bTb;
        inv_bTb[2][2] = (double)(bTb[0][0] * bTb[1][1] - bTb[0][1] * bTb[1][0]) / det_bTb;

        double[] result = new double[3] {
            bTd[0] * inv_bTb[0][0] + bTd[1] * inv_bTb[0][1] + bTd[2] * inv_bTb[0][2],
            bTd[0] * inv_bTb[1][0] + bTd[1] * inv_bTb[1][1] + bTd[2] * inv_bTb[1][2],
            bTd[0] * inv_bTb[2][0] + bTd[1] * inv_bTb[2][1] + bTd[2] * inv_bTb[2][2]
        };


        return new Circle(Math.sqrt(result[2] +
                               0.25 * result[0] * result[0] +
                               0.25 * result[1] * result[1]),
                          0.5 * result[0], 0.5 * result[1]);
    }
}

다음은 제가 만든 손으로 그린 원의 샘플이며, 모든 검은색 픽셀의 좌표가 전달될 때 이 솔루션의 적합도입니다.

enter image description here

언급URL : https://stackoverflow.com/questions/18934805/draw-a-perfect-circle-from-users-touch

반응형