Kullanıcının dokunuşundan mükemmel bir daire çizin


176

Kullanıcının parmaklarıyla dokunurken ekrana çizmesine izin veren bu uygulama projem var. Çok basit App geri egzersiz yolu olarak yaptım. Benim küçük kuzenim bu App iPad ile parmağımla şeyler çizim özgürlüğü aldı (Çocuk çizimleri: daire, çizgiler, vb, aklına ne geldi). Sonra daireler çizmeye başladı ve sonra "iyi daire" yapmamı istedi (benim anlayışımdan: çizilen daireyi mükemmel bir şekilde yuvarlak yap, çünkü ekranda parmağımızla bir şey çizmeye ne kadar kararlı olduğumuzu biliyoruz, daire asla bir dairenin olması gerektiği gibi yuvarlak değildir).

Bu yüzden burada sorum şu: Kodda, kullanıcı tarafından çizilen bir daireyi ilk olarak bir daire oluşturan ve daireyi ekranda tamamen yuvarlak hale getirerek yaklaşık aynı boyutta bir çizgi tespit edebileceğimiz herhangi bir yol var. Düz bir çizgiyi düz yapmak, nasıl yapılacağını bildiğim bir şey, ama daire için, Kuvars veya diğer yöntemlerle nasıl yapacağımı bilmiyorum.

Benim akıl yürütmem, çizginin başlangıç ​​ve bitiş noktasının, kullanıcı aslında bir daire çizmeye çalıştığı gerçeğini haklı göstermek için parmağını kaldırdıktan sonra birbirlerine dokunması veya geçmesi gerektiğidir.


2
Bu senaryoda bir daire ile bir çokgen arasındaki farkı söylemek zor olabilir. Kullanıcının merkezini veya sınırlayıcı bir dikdörtgenin bir köşesini tanımlamak için tıkladığı ve yarıçapı değiştirmek veya karşı köşeyi ayarlamak için sürükleyen bir "Daire Aracı" na sahip olmaya ne dersiniz?
user1118321

2
@ user1118321: Bu, sadece bir daire çizebilmek ve mükemmel bir daireye sahip olmak kavramını yener. İdeal olarak, uygulama, kullanıcının bir daire (az ya da çok), elips veya çokgen çizip çizmediğini yalnızca kullanıcının çiziminden tanımalıdır. (Artı, çokgenler bu uygulama kapsamında olmayabilir - sadece daireler veya çizgiler olabilir.)
Peter Hosey

Peki, hangi cevaba ödül vermem gerektiğini düşünüyorsun? Çok iyi adaylar görüyorum.
Peter Hosey

@Unheilig: Yeni başlayan bir trig anlayışının ötesinde, bu konuda uzmanlığım yok. Bununla birlikte, benim için en fazla potansiyeli gösteren cevaplar stackoverflow.com/a/19071980/30461 , stackoverflow.com/a/19055873/30461 , stackoverflow.com/a/18995771/30461 , belki stackoverflow.com/a/ 18992200/30461 ve benim. Bunlar ilk denediğim şeyler. Siparişi size bırakıyorum.
Peter Hosey

1
@Gene: Bir cevapta belki ilgili bilgileri özetleyebilir ve daha fazla ayrıntıya bağlantı verebilirsiniz.
Peter Hosey

Yanıtlar:


381

Bazen tekerleği yeniden keşfetmek için biraz zaman harcamak gerçekten yararlıdır. Daha önce fark etmiş olabileceğiniz gibi, birçok çerçeve var, ancak tüm bu karmaşıklığı sunmadan basit, ancak kullanışlı bir çözüm uygulamak o kadar zor değil. (Lütfen beni yanlış anlamayın, ciddi bir amaç için olgun ve istikrarlı bir çerçeve olduğu kanıtlanmış daha iyidir).

Önce sonuçlarımı sunacağım ve sonra arkasındaki basit ve anlaşılır fikri anlatacağım.

resim açıklamasını buraya girin

Benim uygulamamda her noktayı analiz etmeye ve karmaşık hesaplamalar yapmaya gerek olmadığını göreceksiniz. Fikir, bazı değerli meta bilgileri tespit etmektir. Teğeti örnek olarak kullanacağım :

resim açıklamasını buraya girin

Seçilen şekil için tipik olan basit ve anlaşılır bir deseni tanımlayalım:

resim açıklamasını buraya girin

Dolayısıyla, bu fikre dayanan bir daire algılama mekanizması uygulamak o kadar da zor değil. Aşağıdaki çalışma demosuna bakın (Üzgünüm, Java'yı bu hızlı ve biraz kirli örneği sağlamanın en hızlı yolu olarak kullanıyorum):

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

Sadece birkaç etkinliğe ve koordinatlara ihtiyacınız olduğu için iOS'ta benzer davranışları uygulamak sorun olmamalı. Aşağıdaki gibi bir şey ( örneğe bakın ):

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

Birkaç geliştirme mümkündür.

Herhangi bir noktadan başlayın

Mevcut gereksinim, aşağıdaki basitleştirme nedeniyle üst orta noktadan bir daire çizmeye başlamaktır:

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

Lütfen varsayılan değerinin indexkullanıldığına dikkat edin. Şeklin kullanılabilir "parçaları" ndaki basit bir arama bu sınırlamayı kaldıracaktır. Tam bir şekli algılamak için dairesel bir tampon kullanmanız gerektiğini lütfen unutmayın:

resim açıklamasını buraya girin

Saat yönünde ve saat yönünün tersine

Her iki modu da desteklemek için önceki geliştirmedeki dairesel arabelleği kullanmanız ve her iki yönde de arama yapmanız gerekir:

resim açıklamasını buraya girin

Elips çiz

boundsDizide zaten ihtiyacınız olan her şeye sahipsiniz .

resim açıklamasını buraya girin

Sadece bu verileri kullanın:

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

Diğer hareketler (isteğe bağlı)

Son olarak, diğer hareketleri desteklemek için dx(veya dy) sıfıra eşit olduğunda bir durumu düzgün bir şekilde ele almanız gerekir :

resim açıklamasını buraya girin

Güncelleme

Bu küçük PoC oldukça yüksek bir ilgi gördü, bu yüzden düzgün çalışması ve bazı çizim ipuçları sağlamak, destek noktalarını vurgulamak vb.

resim açıklamasını buraya girin

İşte kod:

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

76
Muhteşem cevap Renat. Yaklaşımın net açıklaması, süreci belgeleyen görüntüler, animasyonlar da. Ayrıca en genelleştirilmiş, en sağlam çözüm gibi görünüyor. Teğetler gerçekten zekice bir fikir gibi geliyor - ilk (şimdiki?) El yazısı tanıma teknikleri gibi. Bu cevap uğruna soru işaretlendi. :)
enhzflep

27
Daha genel olarak: Kısa, anlaşılır bir açıklama VE diyagramları VE bir animasyon demosu VE kodu VE varyasyonları? Bu ideal bir Stack Overflow cevabıdır.
Peter Hosey

11
Bu çok iyi bir cevap, Java'da bilgisayar grafikleri yaptığını neredeyse affedebilirim! ;)
Nicolas Miari

4
Bu Noel, Santa Renat için şaşırtıcı güncellemeler (yani daha fazla şekil vb.) Olacak mı? :-)
Unheilig

1
Vay. Tur gücü.
wogsland

14

Bir şekli tespit etmek için klasik bir Bilgisayar Görme tekniği Hough Dönüşümüdür. Hough Transform hakkındaki güzel şeylerden biri, kısmi verilere, kusurlu verilere ve gürültüye karşı çok toleranslı olmasıdır. Hough'u bir çevre için kullanma: http://en.wikipedia.org/wiki/Hough_transform#Circle_detection_process

Çemberinizin elle çizildiği göz önüne alındığında, Hough dönüşümünün sizin için iyi bir eşleşme olabileceğini düşünüyorum.

İşte "basitleştirilmiş" bir açıklama, gerçekten bu kadar basit olmadığı için özür dilerim. Büyük bir kısmı yıllar önce yaptığım bir okul projesinden.

Hough Dönüşümü bir oylama programıdır. İki boyutlu bir tamsayı dizisi ayrılır ve tüm öğeler sıfıra ayarlanır. Her öğe analiz edilen görüntüdeki tek bir piksele karşılık gelir. Bu dizi akümülatör dizisi olarak adlandırılır, çünkü her öğe bir pikselin daire veya yayın kökeninde olma olasılığını belirten bilgi, oy biriktirir.

Görüntüye bir gradyan operatör kenar detektörü uygulanır ve kenar pikselleri veya edgeller kaydedilir. Edgel, komşularına göre farklı bir yoğunluğa veya renge sahip bir pikseldir. Farkın derecesine gradyan büyüklüğü denir. Yeterli büyüklükteki her kenar için, akümülatör dizisinin elemanlarını artıracak bir oylama şeması uygulanır. Artırılan (oylanan) öğeler, söz konusu edgelden geçen dairelerin olası kökenlerine karşılık gelir. Arzu edilen sonuç, eğer bir yay varsa, gerçek kökeninin yanlış kökenlerden daha fazla oy alacağıdır.

Oylama için ziyaret edilen akümülatör dizisinin öğelerinin, söz konusu edgelin etrafında bir daire oluşturduğunu unutmayın. Oylanacak x, y koordinatlarını hesaplamak, çizmekte olduğunuz bir dairenin x, y koordinatlarını hesaplamakla aynıdır.

Elle çizilmiş görüntünüzde set (renkli) pikselleri edgelleri hesaplamak yerine doğrudan kullanabilirsiniz.

Şimdi hatalı yerleştirilmiş piksellerle, en fazla oyu alan tek bir akümülatör dizi öğesi elde etmeniz gerekmez. Bir grup oy, bir küme ile komşu dizi öğeleri koleksiyonu alabilirsiniz. Bu kümenin ağırlık merkezi, başlangıç ​​noktası için iyi bir yaklaşım sağlayabilir.

Farklı yarıçap R değerleri için Hough Transform'ı çalıştırmanız gerekebileceğini unutmayın. Daha yoğun oy kümesini üreten kişi "daha iyi" uyumdur.

Yanlış kökenlere yönelik oyları azaltmak için kullanılacak çeşitli teknikler vardır. Örneğin, edgel kullanmanın bir avantajı, sadece büyüklükleri değil, aynı zamanda bir yönleri de olmasıdır. Oy verirken, sadece olası yönler için uygun yönde oy kullanmamız gerekir. Oy alan yerler, tam bir daire yerine bir yay oluşturacaktır.

İşte bir örnek. Yarıçap bir daire ve başlatılmış bir akümülatör dizisi ile başlıyoruz. Her piksel dikkate alındığı için potansiyel kökenleri oylanır. Bu durumda dört olan en fazla oyu asıl alır.

.  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

5

İşte başka bir yol. UIView dokunma özelliğini kullanma Diziyi yarıya bölüyorsunuz ve bir dizideki her noktanın diğer dizideki karşılıklarından diğer tüm çiftlerle kabaca aynı çapta olup olmadığını test ediyorsunuz.

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

}

Kulağa hoş geliyor mu? :)


3

Ben şekil tanıma uzmanı değilim, ama soruna nasıl yaklaşabilirim.

İlk olarak, kullanıcının yolunu serbest olarak görüntülerken, zamanla birlikte nokta (x, y) örneklerinin bir listesini gizlice biriktirir. Her iki olguyu sürükleme etkinliklerinizden alabilir, basit bir model nesnesine sarabilir ve bunları değiştirilebilir bir dizide toplayabilirsiniz.

Muhtemelen örnekleri oldukça sık almak istersiniz - örneğin, her 0.1 saniyede bir. Başka bir olasılık, gerçekten her 0.05 saniyede bir sık ​​sık başlamak ve kullanıcının ne kadar sürüklendiğini izlemek olacaktır; belirli bir süreden daha uzun süre sürüklerse, örnek frekansını düşürün (ve kaçırılan tüm örnekleri 0,2 saniye).

(Ve sayılarımı müjde için almayın, çünkü onları şapkamdan çıkardım. Deney yapın ve daha iyi değerler bulun.)

İkincisi, örnekleri analiz edin.

İki gerçek türetmek isteyeceksiniz. İlk olarak, şeklin merkezi (IIRC) sadece tüm noktaların ortalaması olmalıdır. İkincisi, o merkezden her örneğin ortalama yarıçapı.

@ User1118321'in tahmin ettiği gibi, çokgenleri desteklemek istiyorsanız, analizin geri kalanı bu kararı vermekten oluşur: kullanıcının bir daire veya bir çokgen çizmek isteyip istemediği. Bu saptamayı yapmak için örneklere başlamak için çokgen olarak bakabilirsiniz.

Kullanabileceğiniz birkaç kriter vardır:

  • Süre: Kullanıcı bazı noktalarda diğerlerinden daha uzun süre havada kalırsa (numuneler sabit bir aralıktaysa, uzayda birbirine yakın ardışık örnekler kümesi olarak görünür), bunlar köşeler olabilir. Köşe eşiğinizi küçültmelisiniz, böylece kullanıcı bunu her köşede kasıtlı olarak duraklatmak yerine bilinçsizce yapabilir.
  • Açı: Bir daire, bir numuneden diğerine kabaca aynı açıya sahip olacaktır. Bir çokgenin düz çizgi parçalarıyla birleştirilen birkaç açısı olacaktır; açılar köşelerdir. Düzenli bir çokgen için (düzensiz bir çokgenin elipsine daire), köşe açıları kabaca aynı olmalıdır; düzensiz bir çokgenin farklı köşe açıları olacaktır.
  • Aralık: Düzenli bir çokgenin köşeleri açısal boyut içinde eşit boşluk olacak ve yarıçap sabit olacaktır. Düzensiz bir çokgen düzensiz açısal aralıklara ve / veya sabit olmayan bir yarıçapa sahip olacaktır.

Üçüncü ve son adım, önceden belirlenmiş merkez nokta üzerinde ortalanmış, önceden belirlenmiş yarıçap ile şekil oluşturmaktır.

Yukarıda söylediğim herhangi bir şeyin işe yarayacağını veya verimli olacağını garanti etmiyoruz, ancak umarım en azından sizi doğru yola götürür - ve lütfen, şekil tanıma hakkında benden daha fazla bilen biri (çok düşük bir çubuk) görürse Bu, bir yorum ya da kendi cevap göndermek için çekinmeyin.


+1 Merhaba, giriş için teşekkürler. Çok bilgilendirici. Benzer şekilde, iOS / "şekil tanıma" süpermanının bir şekilde bu yayını görmesini ve bizi daha da aydınlatmasını diliyorum.
Unheilig

1
@Unheilig: İyi fikir. Bitti.
Peter Hosey

1
Algoritmanız kulağa hoş geliyor. Kullanıcının yolunun mükemmel bir daireden / çokgenden ne kadar uzaklaştığına bir kontrol ekleyeceğim. (Örneğin, ortalama kare sapması yüzdesi.) Çok büyükse, kullanıcı ideal şekli istemeyebilir. Kalifiye bir doodler için, kesme özensiz bir doodler'dan daha küçük olacaktır. Buna sahip olmak, programın sanatçılara sanatsal özgürlük vermesine izin verir, ancak yeni başlayanlara çok yardım eder.
dmm

@ user2654818: Bunu nasıl ölçersiniz?
Peter Hosey

1
@PeterHosey: Çevreler için açıklama: İdeal daireye sahip olduğunuzda, merkeze ve yarıçapa sahipsiniz. Böylece, çizilen her noktayı alırsınız ve merkezden olan kare mesafesini hesaplarsınız, ((x-x0) ^ 2 + (y-y0) ^ 2). Bunu kare yarıçapından çıkarın. (Hesaplamayı kaydetmek için çok sayıda kare kökten kaçınıyorum.) Çizilmiş bir nokta için kare hatası olduğunu söyleyin. Tüm çizilmiş noktalar için kare hatasını ortalayın, ardından kare kök yapın, sonra yarıçapa bölün. Bu sizin ortalama yüzde diverjansınız. (Matematik / istatistik muhtemelen saçma değerdir, ancak pratikte işe yarayacaktır.)
dmm

2

Düzgün eğitilmiş 1 $ tanıyıcı ( http://depts.washington.edu/aimgroup/proj/dollar/ ) ile oldukça iyi şanslar yaşadım . Daireler, çizgiler, üçgenler ve kareler için kullandım.

UIGestureRecognizer'dan çok uzun zaman önceydi, ancak uygun UIGestureRecognizer alt sınıflarını oluşturmak kolay olmalı diye düşünüyorum.


2

Kullanıcının başladığı yerdeki şeklini çizmeyi bitirdikten sonra, çizdiği koordinatların bir örneğini alıp bir daireye yerleştirmeyi deneyebilirsiniz.

Burada bu probleme bir MATLAB çözümü var: http://www.mathworks.com.au/matlabcentral/fileexchange/15060-fitcircle-m

Kağıda dayalı olduğu daire ve elips içinde takılması en küçük kareler Walter Gander, Gen H. Golub ve Rolf STREBEL tarafından: http://www.emis.de/journals/BBMS/Bulletin/sup962/gander.pdf

NZ Canterbury Üniversitesi'nden Dr Ian Coope, şu özeti içeren bir makale yayınladı:

Düzlemdeki bir dizi noktaya (veya n-boyutlara bariz genelleme) en uygun daireyi belirleme problemi, bir Gauss-Newton minimizasyon algoritması kullanılarak çözülebilen doğrusal olmayan toplam en küçük kareler problemi olarak kolayca formüle edilebilir. Bu basit yaklaşımın, aykırı değerlere karşı verimsiz ve son derece hassas olduğu gösterilmiştir. Alternatif bir formülasyon problemin önemsiz bir şekilde çözülen doğrusal en küçük kareler problemine indirgenmesini sağlar. Önerilen yaklaşımın, aykırı değerlere doğrusal olmayan en küçük kareler yaklaşımından çok daha az duyarlı olma avantajı olduğu gösterilmiştir.

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

MATLAB dosyası hem doğrusal olmayan TLS hem de doğrusal LLS problemini hesaplayabilir.


0

İşte oldukça basit bir yol:

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

bu matris ızgarasını varsayarak:

 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

Bazı UIView'leri "X" konumlarına yerleştirin ve isabet için test edin (sırayla). Eğer hepsi sırayla vurulursa, kullanıcının "Aferin bir daire çizdin" demesinin adil olabileceğini düşünüyorum.

Kulağa hoş geliyor mu? (ve basit)


Merhaba, limon. İyi bir akıl yürütme, ancak yukarıdaki senaryoda, dokunuşları tespit etmek için 64 UIViews'e ihtiyacımız var demektir, değil mi? Tuval örneğin bir iPad'in boyutu ise, tek bir UIView için boyutu nasıl tanımlarsınız? Daire küçükse ve tek bir UIView boyutu daha büyükse, bu durumda diziyi kontrol edemedik çünkü tüm çizilmiş noktalar tek bir UIView içinde olacaktır.
Unheilig

Evet - bu sadece tuvali 300x300 gibi bir şeye sabitlerseniz ve daha sonra kullanıcının çizmek için aradığınız daire boyutuyla yanında bir "örnek" tuval varsa çalışır. Eğer öyleyse 50x50 kareler * 6 ile gidersem, sadece vurmakla ilgilendiğiniz Görünümleri sadece 6 * 6 (36) veya 8 * 8 (64) değil, doğru yerlerde göstermeniz gerekir
dijipiji

@Unheilig: Bu çözüm böyle yapıyor. Doğru görünüm dizisinden geçecek kadar dairesel olan her şey (ve potansiyel olarak ekstra eğim için maksimum sayıda yola izin verebilirsiniz) daire olarak eşleşecektir. Daha sonra yarıçapı hepsine (veya en azından çoğuna) ulaşan tüm bu görünümlerin merkezinde bulunan mükemmel bir daireye yaslarsınız.
Peter Hosey

@PeterHosey Tamam, başımı bu konuya getirmeye çalışayım. Herhangi biriniz bu haddeleme almak için bazı kod sağlayabilir eğer takdir ediyorum. Bu arada, kafamı bu yönde döndürmeye çalışacağım ve daha sonra kodlama kısmı ile aynı şeyi yapacağım. Teşekkürler.
Unheilig

Sadece senin için daha iyi olabileceğini düşündüğüm başka bir yol gönderdi
dijipiji
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.