उपयोगकर्ता के स्पर्श से एक संपूर्ण वृत्त बनाएं


176

मेरे पास यह अभ्यास परियोजना है जो उपयोगकर्ता को स्क्रीन पर आकर्षित करने की अनुमति देता है क्योंकि वे अपनी उंगलियों से स्पर्श करते हैं। बहुत ही सरल ऐप मैंने एक्सरसाइज के तरीके से वापस किया। मेरे छोटे चचेरे भाई ने इस ऐप पर मेरे iPad के साथ अपनी उंगली से चीजों को खींचने की स्वतंत्रता ली (बच्चों के चित्र: सर्कल, लाइनें, आदि, जो भी उसके दिमाग में आया)। फिर उसने हलकों को आकर्षित करना शुरू कर दिया और फिर उसने मुझे इसे "अच्छा सर्कल" बनाने के लिए कहा (मेरी समझ से: खींचे गए सर्कल को पूरी तरह से गोल करें, क्योंकि हम जानते हैं कि कोई बात नहीं कि हम स्क्रीन पर अपनी उंगली से कुछ खींचने की कितनी स्थिर कोशिश करते हैं, सर्कल वास्तव में कभी भी गोल नहीं होता है जैसे सर्कल होना चाहिए)।

तो मेरा सवाल यह है कि, क्या कोई ऐसा तरीका है, जिसमें हम पहले उपयोगकर्ता द्वारा तैयार की गई एक रेखा का पता लगा सकते हैं जो एक वृत्त बनाती है और स्क्रीन पर पूरी तरह से गोल बनाकर लगभग एक ही आकार का चक्र बनाती है। इतनी सीधी रेखा नहीं बनाना एक ऐसी चीज है जिसे मैं जानता हूं कि मुझे कैसे करना है, लेकिन सर्कल के लिए, मुझे यह नहीं पता है कि इसे क्वार्ट्ज या अन्य तरीकों से कैसे करना है।

मेरा तर्क यह है कि, लाइन के प्रारंभ और अंत बिंदु को एक दूसरे को स्पर्श या पार करना होगा क्योंकि उपयोगकर्ता अपनी उंगली को इस तथ्य को सही ठहराने के लिए करता है कि वह वास्तव में एक वृत्त खींचने की कोशिश कर रहा था।


2
इस परिदृश्य में एक वृत्त और बहुभुज के बीच का अंतर बताना कठिन हो सकता है। एक "सर्कल टूल" होने के बारे में जहां उपयोगकर्ता केंद्र को परिभाषित करने के लिए क्लिक करता है, या एक बाउंड आयत के एक कोने, और त्रिज्या को बदलने या विपरीत कोने को सेट करने के लिए ड्रग्स करता है?
user1118321

2
@ user1118321: यह एक वृत्त को खींचने और एक पूर्ण वृत्त बनाने में सक्षम होने की अवधारणा को पराजित करता है। आदर्श रूप से, ऐप को उपयोगकर्ता के ड्राइंग से अकेले पहचानना चाहिए कि क्या उपयोगकर्ता ने एक सर्कल (अधिक या कम), एक दीर्घवृत्त, या बहुभुज को आकर्षित किया है। (इसके अलावा, पॉलीगॉन इस ऐप के दायरे में नहीं हो सकते हैं - यह सिर्फ सर्कल या लाइन्स हो सकते हैं।)
पीटर होसी

तो, आप किस उत्तर के लिए सोचते हैं कि मुझे इनाम देना चाहिए? मुझे कई अच्छे उम्मीदवार दिखे।
पीटर होसी

@Unheilig: मेरे पास विषय में कोई विशेषज्ञता नहीं है, जो ट्रिगर की नवजात समझ से परे है। उस ने कहा, मेरे लिए सबसे अधिक संभावना दिखाने वाले उत्तर stackoverflow.com/a/19071980/30461 , stackoverflow.com/a/19055873/30461 , stackoverflow.com/a/18995771-30461 , शायद stackoverflow.com/a/ हैं। 18992200/30461 , और मेरा अपना। वे वही हैं जो मैं पहले करने की कोशिश करूंगा। मैं आपके पास आदेश छोड़ता हूं।
पीटर होसे सेप

1
@Gene: शायद आप प्रासंगिक जानकारी को संक्षेप में प्रस्तुत कर सकते हैं, और अधिक जानकारी के लिए, एक उत्तर में लिंक कर सकते हैं।
पीटर होसे

जवाबों:


381

कभी-कभी पहिया को सुदृढ़ करने के लिए कुछ समय बिताना वास्तव में उपयोगी होता है। जैसा कि आपने पहले ही देखा होगा कि बहुत सारी रूपरेखाएँ हैं, लेकिन यह सब एक जटिलता को लागू किए बिना एक सरल, लेकिन अभी तक उपयोगी समाधान को लागू करना उतना कठिन नहीं है। (कृपया मुझे गलत न समझें, किसी भी गंभीर उद्देश्य के लिए कुछ परिपक्व और स्थिर रूपरेखा साबित करना बेहतर है)।

मैं अपने परिणाम पहले प्रस्तुत करूंगा और फिर उनके पीछे के सरल और सीधे विचार की व्याख्या करूंगा।

यहां छवि विवरण दर्ज करें

आप मेरे कार्यान्वयन में देखेंगे कि हर एक बिंदु का विश्लेषण करने और जटिल संगणना करने की आवश्यकता नहीं है। विचार कुछ मूल्यवान मेटा जानकारी को स्पॉट करने के लिए है। मैं उदाहरण के रूप में स्पर्शरेखा का उपयोग करूंगा :

यहां छवि विवरण दर्ज करें

आइए एक सरल और सरल पैटर्न की पहचान करें, जो चयनित आकार के लिए विशिष्ट है:

यहां छवि विवरण दर्ज करें

इसलिए उस विचार के आधार पर एक सर्किल डिटेक्शन मैकेनिज्म को लागू करना कठिन नहीं है। नीचे काम कर रहे डेमो देखें (क्षमा करें, मैं जावा को इस तेज़ और थोड़े गंदे उदाहरण के लिए सबसे तेज़ तरीके के रूप में उपयोग कर रहा हूं):

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

आईओएस पर समान व्यवहार को लागू करने के लिए यह एक समस्या नहीं होनी चाहिए, क्योंकि आपको बस कई घटनाओं और निर्देशांक की आवश्यकता होती है। निम्नलिखित की तरह कुछ ( उदाहरण देखें ):

- (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) अन्य इशारों का समर्थन करने के लिए शून्य के बराबर है:

यहां छवि विवरण दर्ज करें

अपडेट करें

इस छोटे 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);
}

76
शानदार जवाब Renat। दृष्टिकोण का स्पष्ट विवरण, छवियां जो प्रक्रिया का दस्तावेजीकरण करती हैं, एनिमेशन भी। सबसे सामान्यीकृत, मजबूत समाधान भी लगता है। स्पर्शरेखा एक बहुत ही चतुर विचार की तरह लगती है - प्रारंभिक (वर्तमान?) लिखावट मान्यता तकनीकों की तरह। इस उत्तर के लिए प्रश्न को बुकमार्क किया गया। :)
enhzflep

27
आम तौर पर: एक संक्षिप्त, समझने योग्य स्पष्टीकरण और चित्र और एक एनिमेटेड डेमो और कोड और विविधताएं? यह एक आदर्श स्टैक ओवरफ्लो उत्तर है।
पीटर होसे

11
यह इतना अच्छा जवाब है, मैं लगभग माफ कर सकता हूं वह जावा में कंप्यूटर ग्राफिक्स कर रहा है! ;)
निकोलस मिआरी

4
क्या इस क्रिसमस, सांता रेनाट के लिए अब आश्चर्यजनक अपडेट (यानी, अधिक आकृतियाँ आदि) होंगे? :-)
Unheilig

1
वाह। टूअर डे फ़ोर्स।
वॉग्सलैंड

14

किसी आकृति का पता लगाने के लिए एक क्लासिक कंप्यूटर विज़न तकनीक Hough Transform है। Hough Transform के बारे में एक अच्छी बात यह है कि यह आंशिक डेटा, अपूर्ण डेटा और शोर के प्रति बहुत सहनशील है। किसी मंडली के लिए Hough का उपयोग करना: http://en.wikipedia.org/wiki/Hough_transform#Circle_detection_process

यह देखते हुए कि आपके सर्कल को हाथ से खींचा गया है, मुझे लगता है कि होफ ट्रांसफॉर्म आपके लिए एक अच्छा मैच हो सकता है।

यहाँ एक "सरलीकृत" स्पष्टीकरण है, मैं इसके लिए माफी माँगता हूँ कि यह वास्तव में सरल नहीं है। इसका ज्यादातर हिस्सा एक स्कूल प्रोजेक्ट से है जो मैंने कई साल पहले किया था।

Hough Transform एक वोटिंग स्कीम है। पूर्णांकों की एक दो आयामी सरणी आवंटित की जाती है और सभी तत्व शून्य पर सेट होते हैं। प्रत्येक तत्व का विश्लेषण किया जा रहा छवि में एक एकल पिक्सेल से मेल खाती है। इस सरणी को संचायक सरणी के रूप में संदर्भित किया जाता है क्योंकि प्रत्येक तत्व जानकारी, वोटों को जमा करेगा, यह संभावना दर्शाता है कि एक पिक्सेल एक सर्कल या आर्क के मूल में हो सकता है।

एक ढाल ऑपरेटर एज डिटेक्टर छवि और एज पिक्सल पर लागू होता है, या एडगेल, रिकॉर्ड किया जाता है। एडगेल एक ऐसा पिक्सेल है जिसमें पड़ोसी के संबंध में एक अलग तीव्रता या रंग होता है। अंतर की डिग्री को ढाल परिमाण कहा जाता है। पर्याप्त परिमाण के प्रत्येक एडगेल के लिए एक मतदान योजना लागू की जाती है जो संचायक सरणी के तत्वों को बढ़ाएगा। जिन तत्वों में वृद्धि (के लिए मतदान) की जाती है, वे उन मूल उत्पत्ति के अनुरूप हैं, जो विचाराधीन एडगेल से गुजरते हैं। वांछित परिणाम यह है कि यदि एक चाप मौजूद है, तो असली मूल को झूठे मूल की तुलना में अधिक वोट प्राप्त होंगे।

ध्यान दें कि वोटिंग के लिए एकत्रित किए जा रहे संचायक सरणी के तत्व विचाराधीन एडगेल के चारों ओर एक चक्र बनाते हैं। X की गणना करना, y वोट करने के लिए निर्देशांक को x की गणना करने के समान है, एक वृत्त के y निर्देशांक को जो आप ड्राइंग कर रहे हैं।

आपके हाथ से खींची गई छवि में आप कैलगल्स की गणना करने के बजाय सीधे सेट (रंगीन) पिक्सल का उपयोग करने में सक्षम हो सकते हैं।

अब अपूर्ण रूप से स्थित पिक्सेल के साथ आपको आवश्यक रूप से सबसे बड़ी संख्या में वोटों के साथ एक एकल संचायक सरणी तत्व नहीं मिलेगा। आपको वोटों के एक समूह, एक क्लस्टर के साथ पड़ोसी सरणी तत्वों का एक संग्रह मिल सकता है। इस क्लस्टर के गुरुत्वाकर्षण का केंद्र उत्पत्ति के लिए एक अच्छा अनुमान लगा सकता है।

ध्यान दें कि आपको त्रिज्या R के विभिन्न मानों के लिए Hough Transform चलाना पड़ सकता है। वोटों के सघन क्लस्टर का निर्माण करने वाला "बेहतर" फिट है।

झूठी उत्पत्ति के लिए वोट कम करने के लिए उपयोग करने के लिए विभिन्न तकनीकें हैं। उदाहरण के लिए एडगेल का उपयोग करने का एक फायदा यह है कि उनके पास न केवल एक परिमाण है, बल्कि उनके पास एक दिशा भी है। जब मतदान की आवश्यकता होती है, तो हमें उचित दिशा में संभावित मूल वोटों की आवश्यकता होती है। वोट प्राप्त करने वाले स्थान एक पूर्ण चक्र के बजाय एक चाप का निर्माण करेंगे।

यहाँ एक उदाहरण है। हम त्रिज्या एक और एक प्रारंभिक संचायक सरणी के एक चक्र के साथ शुरू करते हैं। जैसा कि प्रत्येक पिक्सेल को माना जाता है कि संभावित उत्पत्ति के लिए मतदान किया जाता है। असली मूल को सबसे अधिक वोट मिलते हैं जो इस मामले में चार हैं।

.  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

यहाँ एक और तरीका है। 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);

}

वह आवाज ठीक है? :)


3

मैं कोई आकार पहचान विशेषज्ञ नहीं हूं, लेकिन यहां बताया गया है कि मैं कैसे समस्या का सामना कर सकता हूं।

सबसे पहले, उपयोगकर्ता के पथ को फ्रीहैंड के रूप में प्रदर्शित करते समय, चुपके से समय के साथ बिंदु (x, y) के नमूनों की एक सूची जमा करते हैं। आप अपने ड्रैग इवेंट्स से दोनों तथ्यों को प्राप्त कर सकते हैं, उन्हें एक साधारण मॉडल ऑब्जेक्ट में लपेट सकते हैं, और उन लोगों को एक परस्पर सरणी में ढेर कर सकते हैं।

आप शायद नमूने को अक्सर बार-बार कहना चाहते हैं, हर 0.1 सेकंड में। एक और संभावना वास्तव में अक्सर शुरू करने की होगी , शायद हर 0.05 सेकंड में, और देखें कि उपयोगकर्ता कितनी देर तक ड्रग करता है; यदि वे कुछ समय से अधिक समय तक खींचते हैं, तो नमूना आवृत्ति कम करें (और किसी भी नमूने को छोड़ दें जो 0.2 सेकंड की तरह कुछ छूट गया है)।

(और सुसमाचार के लिए मेरे नंबर मत लो, क्योंकि मैंने अभी उन्हें अपनी टोपी से निकाला है। प्रयोग और बेहतर मूल्य खोजें।)

दूसरा, नमूनों का विश्लेषण।

आप दो तथ्यों को प्राप्त करना चाहते हैं। सबसे पहले, आकृति का केंद्र, जो (IIRC) केवल सभी बिंदुओं का औसत होना चाहिए। दूसरा, उस केंद्र से प्रत्येक नमूने की औसत त्रिज्या।

यदि, @ user1118321 ने अनुमान लगाया है, तो आप बहुभुज का समर्थन करना चाहते हैं, तो बाकी विश्लेषण में यह निर्णय करना है: क्या उपयोगकर्ता एक वृत्त या बहुभुज खींचना चाहता है। आप उस निर्धारण को शुरू करने के लिए बहुभुज के रूप में नमूनों को देख सकते हैं।

कई मापदंड हैं जिनका आप उपयोग कर सकते हैं:

  • समय: यदि उपयोगकर्ता दूसरों की तुलना में कुछ बिंदुओं पर अधिक समय तक मंडराता है (जो, यदि नमूने एक निरंतर अंतराल पर हैं, तो अंतरिक्ष में एक-दूसरे के पास लगातार नमूनों के समूह के रूप में दिखाई देंगे), वे कोने हो सकते हैं। आपको अपने कोने को छोटा बनाना चाहिए ताकि उपयोगकर्ता प्रत्येक कोने पर जानबूझकर रुकने के बजाय यह अनजाने में कर सके।
  • कोण: एक सर्कल में लगभग एक ही कोण से एक नमूना होगा जो अगले चारों ओर होगा। बहुभुज में कई कोण होंगे जो सीधी रेखा के खंडों से जुड़ते हैं; कोण कोने हैं। एक नियमित बहुभुज (एक अनियमित बहुभुज के घेरा के लिए चक्र) के लिए, कोने के कोण सभी को लगभग समान होना चाहिए; एक अनियमित बहुभुज के अलग-अलग कोने कोण होंगे।
  • अंतराल: एक नियमित बहुभुज के कोनों को कोणीय आयाम के भीतर बराबर जगह मिलेगी, और त्रिज्या स्थिर होगी। एक अनियमित बहुभुज में अनियमित कोणीय अंतराल और / या एक गैर-स्थिर त्रिज्या होगा।

तीसरा और अंतिम चरण पहले से निर्धारित त्रिज्या के साथ, पहले से निर्धारित केंद्र बिंदु पर केंद्रित आकार बनाना है।

इस बात की कोई गारंटी नहीं है कि मैंने जो कुछ भी ऊपर कहा है वह काम करेगा या कुशल होगा, लेकिन मुझे उम्मीद है कि यह कम से कम आपको सही रास्ते पर ले जाता है - और कृपया, अगर कोई है जो मेरे बारे में आकृति मान्यता के बारे में अधिक जानता है (जो बहुत कम बार है) देखता है यह, एक टिप्पणी या अपने खुद के जवाब पोस्ट करने के लिए स्वतंत्र महसूस हो रहा है।


+1 हाय, इनपुट के लिए धन्यवाद। बहुत सूचनाप्रद। इसी तरह, मेरी इच्छा है कि आईओएस / "शेप रिकग्निशन" सुपरमैन किसी तरह इस पोस्ट को देखेगा और हमें आगे बताएगा।
उन्हेलिग

1
@ यूनीलिग: अच्छा विचार है। किया हुआ।
पीटर होसी

1
आपका एल्गोरिथ्म अच्छा लगता है। मैं इस बात की जाँच करूंगा कि उपयोगकर्ता का मार्ग एक पूर्ण वृत्त / बहुभुज से कितनी दूर है। (उदाहरण के लिए, प्रतिशत का मतलब वर्ग विचलन है।) यदि यह बहुत बड़ा है, तो उपयोगकर्ता आदर्श आकार नहीं चाहेगा। एक कुशल डूडलर के लिए, कटऑफ एक मैला कामचोर के लिए की तुलना में छोटा होगा। यह होने से कार्यक्रम कलाकारों को कलात्मक स्वतंत्रता देने में मदद करेगा, लेकिन शुरुआती लोगों को बहुत मदद मिलेगी।
dmm

@ user2654818: आप इसे कैसे मापेंगे?
पीटर होसी

1
@PeterHosey: मंडलियों के लिए स्पष्टीकरण: एक बार जब आपके पास आदर्श सर्कल होता है, तो आपको केंद्र और त्रिज्या मिल जाता है। इसलिए आप हर खींचे गए बिंदु को लें, और केंद्र से इसकी वर्ग दूरी की गणना करें, जो ((x-x0) ^ 2 + (y-y0) ^ 2) है। त्रिज्या वर्ग से घटाएँ। (मैं अभिकलन को बचाने के लिए बहुत सारे वर्गमूल से बच रहा हूं।) कॉल करें कि एक खींचे गए बिंदु के लिए चुकता त्रुटि। सभी खींचे गए बिंदुओं के लिए चुकता त्रुटि को औसत करें, फिर इसे वर्गमूल करें, फिर इसे त्रिज्या से विभाजित करें। यह आपका औसत प्रतिशत विचलन है। (गणित / आँकड़े शायद असाध्य-योग्य हैं, लेकिन यह व्यवहार में काम करेगा।)
dmm

2

मेरे पास अच्छी तरह से प्रशिक्षित $ 1 पहचानकर्ता ( http://depts.washington.edu/aimgroup/proj/dollar/ ) के साथ बहुत अच्छी किस्मत है । मैंने इसका उपयोग मंडलियों, रेखाओं, त्रिकोण और वर्गों के लिए किया।

यह UIGestureRecognizer से पहले एक लंबे समय से पहले था, लेकिन मुझे लगता है कि उचित UIGestureRecognizer उपवर्गों का निर्माण करना आसान होना चाहिए।


2

एक बार जब आप निर्धारित कर लेते हैं कि उपयोगकर्ता ने अपना आकार बनाना शुरू कर दिया है जहां वे शुरू हुए थे, तो आप उन निर्देशांक का एक नमूना ले सकते हैं जो उन्होंने आकर्षित किया और उन्हें एक सर्कल में फिट करने का प्रयास करें।

इस समस्या का MATLAB समाधान यहाँ है: http://www.mathworks.com.au/matlabcentral/fileexchange/15060-fitcircle-m

जो वाल्टर गैंडर, जीन एच। गोलूब और रॉल्फ स्ट्रेबेल द्वारा सर्किलों और एलिप्स के पेपर लिस्ट-स्क्वेयर फिटिंग्स पर आधारित है: http://www.emis.de/journals/BBMS/Bulletin-sup962/gander.pdf

कैंटरबरी विश्वविद्यालय से डॉ। इयान कूपर, NZ ने अमूर्त के साथ एक पेपर प्रकाशित किया:

विमान में बिंदुओं के एक सेट (या एन-आयामों के लिए स्पष्ट सामान्यीकरण) के लिए सबसे अच्छा फिट के चक्र को निर्धारित करने की समस्या को आसानी से एक गैर-कुल कुल न्यूनतम वर्गों की समस्या के रूप में तैयार किया जाता है जो कि गॉस-न्यूटन अनुकूलन एल्गोरिथ्म का उपयोग करके हल किया जा सकता है। इस सीधे-आगे के दृष्टिकोण को अयोग्य और बाहरी लोगों की उपस्थिति के प्रति बेहद संवेदनशील दिखाया गया है। एक वैकल्पिक सूत्रीकरण समस्या को एक रैखिक कम से कम वर्गों की समस्या को कम करने की अनुमति देता है जो तुच्छ रूप से हल हो जाती है। अनुशंसित दृष्टिकोण को गैर-कम से कम वर्गों के दृष्टिकोण से आउटलेर्स के प्रति बहुत कम संवेदनशील होने का अतिरिक्त लाभ दिखाया गया है।

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

MATLAB फ़ाइल nonlinear TLS और रैखिक LLS समस्या दोनों की गणना कर सकती है।


0

यहाँ एक बहुत ही सरल तरीका है:

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

कुछ XIVIV को "X" स्थानों पर रखें और हिट (अनुक्रम में) बनने के लिए उनका परीक्षण करें। यदि वे सभी अनुक्रम में आते हैं तो मुझे लगता है कि उपयोगकर्ता को यह कहना उचित होगा कि "अच्छा किया आपने एक वृत्त आकर्षित किया"

ठीक लगता है? (और सरल)


हाय, नींबू। अच्छा तर्क, लेकिन ऊपर के परिदृश्य में, इसका मतलब है कि हमें छुआछूत का पता लगाने के लिए 64 यूआईवीयूवी की आवश्यकता होगी, है ना? और यदि आप एक उदाहरण के लिए एक iPad के आकार का कैनवास है, तो आप एक एकल UIView के लिए आकार कैसे परिभाषित करेंगे? ऐसा लगता है कि यदि सर्कल छोटा है और यदि एक एकल यूआईईवीवाई का आकार बड़ा है, तो इस मामले में हम अनुक्रम की जांच नहीं कर सकते हैं क्योंकि सभी खींचे गए बिंदु एक एकल यूआईवीवाई के भीतर स्थित होंगे।
अनहिलिग

हां - यह एक शायद केवल तभी काम करता है जब आप कैनवास को 300x300 जैसी किसी चीज़ के लिए ठीक करते हैं और फिर उसके पास एक "उदाहरण" कैनवास होता है, जिस सर्कल के आकार के साथ आप उपयोगकर्ता को आकर्षित करने के लिए देख रहे हैं। यदि ऐसा है तो मैं 50x50 वर्ग * 6 के साथ जाऊंगा, आपको केवल उन दृश्यों को प्रस्तुत करने की आवश्यकता है जिन्हें आप सही स्थानों पर हिट करने में रुचि रखते हैं, न कि सभी 6 * 6 (36) या 8 * 8 (64) में
dijipiji

@ यूनीलीग: यही समाधान करता है। किसी भी चीज को देखने के सही अनुक्रम से गुजरने के लिए पर्याप्त गोलाकार (और आप अतिरिक्त स्लोप के लिए कुछ अधिकतम संख्या में डिटोर्स की अनुमति दे सकते हैं) एक सर्कल के रूप में मेल खाएंगे। फिर आप इसे उन सभी विचारों के केंद्र में स्थित एक पूर्ण सर्कल में स्नैप करते हैं, जिनकी त्रिज्या उन सभी (या कम से कम अधिकांश) तक पहुंचती है।
पीटर होसी

@PeterHosey ठीक है, मुझे इसके चारों ओर अपना सिर लाने की कोशिश करें। मुझे खुशी होगी अगर आप में से कोई भी इस रोल को पाने के लिए कुछ कोड प्रदान कर सकता है। इस बीच, मैं अपने सिर को इस ओर घुमाने की भी कोशिश करूंगा और बाद में कोडिंग वाले हिस्से के साथ भी ऐसा ही करूंगा। धन्यवाद।
उन्हेलिग

बस आपके लिए एक और तरीका प्रस्तुत किया गया है जो मुझे लगता है कि बेहतर हो सकता है
दिजीपेजी
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.