이 연습 프로젝트를 통해 사용자가 손가락으로 터치 할 때 화면에 그릴 수 있습니다. 내가 운동 방법으로 다시했던 매우 간단한 응용 프로그램. 내 작은 사촌 이이 응용 프로그램에서 내 iPad로 손가락으로 물건을 그리는 자유를 얻었습니다 (어린이 그림 : 원, 선 등 그의 마음에 들었던 것). 그런 다음 그는 원을 그리기 시작한 후 “좋은 원”으로 만들 것을 요청했습니다. 원은 절대 원처럼 반올림되지 않습니다.
그래서 제 질문은 코드에서 원을 형성하는 사용자가 그린 선을 먼저 감지하고 화면에서 완전히 둥글게하여 원과 거의 같은 크기의 원을 생성 할 수있는 방법이 있습니까? 그렇게 직선이 아닌 직선을 만드는 것은 내가해야 할 일이지만 원은 석영이나 다른 방법으로 어떻게 해야하는지 잘 모릅니다.
내 추론은 사용자가 실제로 원을 그리려고한다는 사실을 정당화하기 위해 사용자가 손가락을 든 후에 선의 시작과 끝이 서로 닿거나 교차해야한다는 것입니다.
답변
때로는 바퀴를 재발 명하는 데 시간을 보내는 것이 정말 유용합니다. 이미 알 수 있듯이 많은 프레임 워크가 있지만 복잡성을 도입하지 않고 간단하지만 유용한 솔루션을 구현하는 것은 어렵지 않습니다. (나를 잘못하지 마십시오. 진지한 목적을 위해 성숙하고 안정적인 프레임 워크로 입증 된 일부를 사용하는 것이 좋습니다).
먼저 결과를 제시하고 그 뒤에 간단하고 간단한 아이디어를 설명하겠습니다.
구현에서 모든 단일 지점을 분석하고 복잡한 계산을 수행 할 필요가 없음을 알 수 있습니다. 중요한 메타 정보를 찾아내는 것이 좋습니다. 예를 들어 탄젠트 를 사용하겠습니다 .
선택한 모양에 전형적인 단순하고 간단한 패턴을 식별 해 보겠습니다.
따라서 그 아이디어를 기반으로 원 감지 메커니즘을 구현하는 것은 어렵지 않습니다. 아래의 데모 데모를 참조하십시오 (죄송합니다.이 빠르고 약간 더러운 예제를 제공하는 가장 빠른 방법으로 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
이 사용됩니다. 사용 가능한 모양의 “부분”을 간단하게 검색하면 해당 제한이 제거됩니다. 전체 모양을 감지하려면 원형 버퍼를 사용해야합니다.
시계 방향 및 시계 반대 방향
두 모드를 모두 지원하려면 이전 개선 사항의 순환 버퍼를 사용하고 양방향으로 검색해야합니다.
타원 그리기
bounds
배열에 필요한 모든 것이 있습니다 .
해당 데이터를 사용하십시오.
cWidth = bounds[2].y - bounds[0].y;
cHeight = bounds[3].y - bounds[1].y;
다른 제스처 (선택 사항)
마지막으로 다른 제스처를 지원하기 위해 dx
(또는 dy
)가 0 인 상황을 올바르게 처리하면됩니다 .
최신 정보
이 작은 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);
}
답변
모양을 감지하는 고전적인 컴퓨터 비전 기술은 Hough Transform입니다. Hough Transform의 장점 중 하나는 부분 데이터, 불완전한 데이터 및 노이즈에 매우 관대하다는 것입니다. 서클에 Hough 사용 :
http://en.wikipedia.org/wiki/Hough_transform#Circle_detection_process
귀하의 원이 손으로 그려 졌으므로 Hough 변환이 당신에게 잘 어울릴 것입니다.
여기에 “간단한”설명이 있습니다. 실제로 그렇게 단순하지 않은 점에 대해 사과드립니다. 그것의 대부분은 몇 년 전에 내가 한 학교 프로젝트에서 나온 것입니다.
허프 변환은 투표 방식입니다. 2 차원 정수 배열이 할당되고 모든 요소가 0으로 설정됩니다. 각 요소는 분석중인 이미지의 단일 픽셀에 해당합니다. 이 배열은 각 요소가 정보, 투표권을 축적하므로 픽셀이 원 또는 호의 원점에있을 가능성을 나타 내기 때문에 누산기 배열이라고합니다.
그라디언트 연산자 엣지 검출기가 이미지에 적용되고 엣지 픽셀 또는 엣지가 기록됩니다. Edgel은 주변에 대해 다른 강도 또는 색상을 갖는 픽셀입니다. 차이의 정도를 그래디언트 크기라고합니다. 충분한 크기의 각 edge1에 대해 어큐뮬레이터 어레이의 요소를 증가시키는 투표 방식이 적용됩니다. 증가 (투표 용)되는 요소는 고려중인 edgel을 통과하는 가능한 원의 원점에 해당합니다. 원하는 결과는 호가 존재하면 실제 원점이 잘못된 원점보다 더 많은 투표를 받게됩니다.
투표를 위해 방문되는 누산기 배열의 요소는 고려중인 가장자리 주위에 원을 형성합니다. 투표 할 x, y 좌표를 계산하는 것은 그리는 원의 x, y 좌표를 계산하는 것과 같습니다.
손으로 그린 이미지에서 edgels를 계산하는 대신 세트 (컬러) 픽셀을 직접 사용할 수 있습니다.
이제 불완전하게 위치한 픽셀로 인해 가장 많은 표를 가진 단일 누산기 배열 요소를 얻을 필요는 없습니다. 여러 개의 투표권과 클러스터가있는 이웃 배열 요소 모음을 얻을 수 있습니다. 이 군집의 무게 중심은 원점에 대한 근사치를 제공 할 수 있습니다.
반경 R의 다른 값에 대해 Hough Transform을 실행해야 할 수도 있습니다. 더 밀집된 투표 클러스터를 생성하는 것은 “더 나은”적합입니다.
허위 기원에 대한 투표를 줄이기 위해 사용할 수있는 다양한 기술이 있습니다. 예를 들어, edgels를 사용하는 것의 장점은 크기뿐만 아니라 방향도 가지고 있다는 것입니다. 투표 할 때 적절한 방향으로 가능한 출처에 대해서만 투표하면됩니다. 투표를받는 위치는 완전한 원이 아닌 호를 형성합니다.
다음은 예입니다. 우리는 반지름 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과 같이 다각형을 지원하려는 경우 나머지 분석은 사용자가 원을 그리거나 다각형을 그릴 지 여부를 결정하는 것으로 구성됩니다. 먼저 샘플을 다각형으로보고 결정을 내릴 수 있습니다.
사용할 수있는 몇 가지 기준이 있습니다.
- 시간 : 사용자가 다른 지점보다 특정 지점에서 더 오래 이동하면 (샘플이 일정한 간격으로 있으면 공간에서 서로 인접한 연속 샘플 클러스터로 표시됨) 모퉁이 일 수 있습니다. 사용자가 각 코너에서 의도적으로 일시 중지하지 않고 무의식적으로이를 수행 할 수 있도록 코너 임계 값을 작게 만들어야합니다.
- 각도 : 원은 한 샘플에서 다음 샘플까지 거의 같은 각도를 갖습니다. 다각형에는 직선 세그먼트로 연결된 여러 각도가 있습니다. 각도는 모서리입니다. 일반 다각형 (원의 다각형에서 불규칙한 다각형의 타원)의 경우 모서리 각도는 거의 같아야합니다. 불규칙한 다각형의 모서리 각도는 다릅니다.
- 간격 : 일반 다각형의 모서리는 각도 치수 내에서 동일한 간격으로 떨어져 있으며 반지름은 일정합니다. 불규칙 다각형은 불규칙한 각도 간격 및 / 또는 일정하지 않은 반경을 갖습니다.
세 번째이자 마지막 단계는 이전에 결정된 반경을 사용하여 이전에 결정된 중심점을 중심으로 모양을 만드는 것입니다.
위에서 말한 내용이 효과가 있거나 효율적일 것이라고 보장 할 수는 없지만, 적어도 나에게 모양 인식에 대해 더 많이 아는 사람이 있다면 의견이나 자신의 답변을 자유롭게 게시하십시오.
답변
제대로 훈련 된 $ 1 인식기 ( http://depts.washington.edu/aimgroup/proj/dollar/ ) 와 함께 행운을 빕니다 . 나는 원, 선, 삼각형 및 사각형에 사용했습니다.
UIGestureRecognizer 이전에는 오래되었지만 적절한 UIGestureRecognizer 서브 클래스를 작성하는 것이 쉬워야한다고 생각합니다.
답변
사용자가 시작된 위치에서 도형 그리기를 완료 한 후에는 좌표를 샘플링하여 원에 맞추려고 시도 할 수 있습니다.
이 문제에 대한 MATLAB 솔루션이 있습니다 :
http://www.mathworks.com.au/matlabcentral/fileexchange/15060-fitcircle-m
이 논문 은 Walter Gander, Gene H. Golub 및 Rolf Strebel가 작성한 최소 제곱 원의 원과 타원 피팅을 기반으로합니다 .
.
뉴질랜드 캔터베리 대학교 (University of Canterbury)의 Ian Coope 박사는 초록과 함께 논문을 발표했습니다.
평면의 점 집합에 가장 잘 맞는 원을 결정하는 문제 (또는 n 차원에 대한 명백한 일반화)는 Gauss-Newton 최소화 알고리즘을 사용하여 해결할 수있는 비선형 총 최소 제곱 문제로 쉽게 공식화됩니다. 이 간단한 접근 방식은 비효율적이며 특이 치의 존재에 매우 민감합니다. 대안적인 제형은 문제가 사소한 해결되는 선형 최소 제곱 문제로 감소 될 수있게한다. 권장 접근법은 비선형 최소 제곱 접근법보다 특이 치에 훨씬 덜 민감하다는 추가 이점을 갖는 것으로 나타났습니다.
http://link.springer.com/article/10.1007%2FBF00939613
MATLAB 파일은 비선형 TLS 및 선형 LLS 문제를 모두 계산할 수 있습니다.
답변
다음을 사용하는 매우 간단한 방법이 있습니다.
- (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를 배치하고 적중 (순서대로)되는지 테스트하십시오. 모두 순서대로 맞으면 사용자가 “원을 그리셨습니까?”라고 말하게하는 것이 공정하다고 생각합니다.
괜찮아? (간단한)