将矩形的四个点重新排序为正确的顺序

时间:2014-03-27 18:47:55

标签: java sorting

纵横比=高度/宽度始终> 1(大多数情况下甚至> 2),因此我应该清楚/准确地知道如何旋转。


我在OpenCV / Java中有一个RotatedRect对象 我可以得到一个包含4个Point of Point和Point对象的数组来定义x / y值。

现在我想对这4个点进行排序,使得左上角的Point是数组的第一个元素,然后是顺时针,所以上下点是第四个元素。

我假设矩形没有旋转太多(只是一些小角度),例如

我在示例中指出了我指的是左上角(TL)。

怎么做?

你不需要专门告诉我OpenCV等,只是假设你有两个阵列

int[] x = new int[4];
int[] y = new int[4];

n - 点的坐标为(x[n-1], y[n-1])。然后我可以专门为OpenCV做这件事。

5 个答案:

答案 0 :(得分:4)

答案

如果您知道

,这是一个非常简单的解决方案
  1. -45 < roundedRect.angle < 45
  2. roundedRect.size.height > roundedRect.size.width
  3. 如果确实如此,则按顺时针顺序的点将始终按此顺序排列:

    pts[0], pts[3], pts[2], pts[1]
    

    顺便说一句,如果它不会对你的程序造成太大伤害,那么这些点会以逆时针顺序发送,从左上角开始......那么你就不必进行任何重新排序/排序

    其他情况:

    • height > width && 135 < roundedRect.angle < 225
      • 从左上角开始的顺时针顺序为2,3,0,1
      • 左上角的逆时针顺序是2,1,0,3
    • width > height && -135 < roundedRect.angle < -45
      • 从左上角开始的顺时针顺序为3,2,1,0
      • 左上角的逆时针顺序为3,0,1,2
    • width > height && 45 < roundedRect.angle < 135
      • 从左上角开始的顺时针顺序为1,0,3,2
      • 左上角的逆时针顺序为1,2,3,0

    剩下的情况都意味着矩形从左到右比从上到下更大,这在您的场景中不会发生。此外,如果角度超出这些范围,您可以连续添加或减去360以获得其中一个范围内的角度。


    解释

    (TL; DR)

    我们从OpenCV如何计算这些点的值来了解这一点。你可以通过一些实验来解决这个问题。这是我写的一个小程序,用来演示它:

    import java.awt.BorderLayout;
    import java.awt.Dimension;
    import java.awt.EventQueue;
    import java.awt.Graphics;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    
    import javax.swing.JComponent;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.Timer;
    
    import org.opencv.core.Point;
    import org.opencv.core.RotatedRect;
    import org.opencv.core.Size;
    
    public class TestFrame extends JFrame {
        public static void main(String... args) {
            final TestFrame frame = new TestFrame();
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    frame.setVisible(true);
                }
            });
        }
    
        private RectComponent rect;
    
        public TestFrame() {
            JPanel containerPane = new JPanel(new BorderLayout());
            setDefaultCloseOperation(EXIT_ON_CLOSE);
            rect = new RectComponent();
            containerPane.add(rect);
            setContentPane(containerPane);
            setSize(400,400);
            new Timer(100, rect).start();
        }
    
        public class RectComponent extends JComponent implements ActionListener {
            private RotatedRect rect = new RotatedRect(new Point(0,0), new Size(1, 3), 0);
    
            private final Point[] pts = new Point[4];
    
            @Override
            protected void paintComponent(Graphics g) {
                rect.points(pts);
                printPoints();
                Dimension size = getSize();
                drawRectLine(g, pts[0], pts[1], size);
                drawRectLine(g, pts[1], pts[2], size);
                drawRectLine(g, pts[2], pts[3], size);
                drawRectLine(g, pts[0], pts[3], size);
            }
    
            private void printPoints() {
                System.out.format("A: %d, TL: %s, TR: %s, BR: %s, BL%s%n",
                        (int) (rect.angle + (rect.angle < 0 ? -1e-6 : 1e-6)), // Stupid doubles, stupid rounding error
                        pointToString(pts[0]),
                        pointToString(pts[3]),
                        pointToString(pts[2]),
                        pointToString(pts[1]));
            }
    
            private String pointToString(Point p) {
                return String.format("{%.2f,%.2f}",p.x, p.y);
            }
    
            private void drawRectLine(Graphics g, Point left, Point right, Dimension size) {
                g.drawLine(scale(left.x, size.width), scale(left.y, size.height),
                        scale(right.x, size.width), scale(right.y, size.height));
            }
    
    
            private int scale(double value, int coord) {
                return (int) (value * coord) / 4 + coord / 2;
            }
    
    
            @Override
            public void actionPerformed(ActionEvent e) {
                rect.angle += 1;
                if(rect.angle > 44) rect.angle = -44;
                repaint();
            }
        }
    }
    

答案 1 :(得分:3)

编辑:如果您可以自由地假设矩形没有旋转太多,您可以直接使用公式通过计算与原点的距离来找到左上角点长度=((y1-y2)^ 2 +     上面的(x1-x2)^ 2)^(0.5 ),其中原点为(0,0)。距离最小的点将是左上角。然后你可以继续使用我在下面给出的步骤。

如果你不能想到这一点,一旦你确定了矩形的左上角,就会有另一种更优雅的方式(因此前三个步骤保持不变)。确定左上角后:

  1. 找出从左上角到其他三个点的距离 使用毕达哥拉斯公式的点,长度=((y1-y2)^ 2 + (X1-X2)^ 2)^(0.5)
  2. 您现在有三个长度对应于每个长度 从左上角开始的顶点。
  3. 顶点的位置可以很容易地找到(进入 顺时针顺序):​​

    shortest distance = top right point 
    longest distance = bottom right point 
    middle distance = bottom left point
    
  4. 您不需要使用条件。

    注意:只要高度始终大于宽度的条件,此操作就会成立。

答案 2 :(得分:2)

搜索具有最高y值的2个点,其中一个始终是定义中的TL(宽度&lt;高度和小角度(不高于45°!)。

按y值的降序对数组进行排序,得到第二个最高y值的元素。

如果此点具有最低x值,则定义右图(1)。否则,具有最高值的点是您的TL并定义您的左图(2)。

现在你得到TL是你的第一个元素的顺时针顺序。

在案例(1)中:更改已排序数组的最后2个元素的位置 如果是(2):改变前两个元素的位置。

这是正确的,因为你的定义,但我无法以适当的数学方式解释它。

答案 3 :(得分:1)

我刚尝试了一种方法。我不确定它是“更简单”还是以其他方式比你的“更好”,但我会在这里发布它作为一个建议:

您可以计算矩形的中心。然后你可以移动矩形,使其中心位于原点。然后,您可以使用Math.atan2方法计算每个点与x轴的角度。这里的好处是:它返回-PI ... + PI范围内的角度,它与你想要的顺序完全匹配:左上角点将是“最负”角度,左下角点将具有“最积极的“。

此说明仅用于说明。其中一些步骤(特别是“移动”矩形)不必明确地完成。

但是:在此示例中,您可以通过鼠标单击设置角点。如上所述,每个点将用其索引和角度标记。设置第四个点后,将相应地重新排序点,并显示结果索引/角度。

据我所知,结果似乎是你想要计算的结果。

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;


public class RectanglePointReorderingTest
{
    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            @Override
            public void run()
            {
                createAndShowGUI();
            }
        });
    }

    private static void createAndShowGUI()
    {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.getContentPane().add(new RectanglePointReorderingPanel());
        f.setSize(800, 800);
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    static Point2D computeCenter(List<Point2D> points)
    {
        double x = 0;
        double y = 0;
        for (Point2D p : points)
        {
            x += p.getX();
            y += p.getY();
        }
        x /= points.size();
        y /= points.size();
        return new Point2D.Double(x, y);
    }

    static double computeAngle(Point2D center, Point2D point)
    {
        double dx = point.getX() - center.getX();
        double dy = point.getY() - center.getY();
        double angleRad = Math.atan2(dy, dx);
        return angleRad;
    }

    static Comparator<Point2D> createComparator(Point2D center)
    {
        final Point2D finalCenter = 
            new Point2D.Double(center.getX(), center.getY());
        return new Comparator<Point2D>()
        {
            @Override
            public int compare(Point2D p0, Point2D p1)
            {
                double angle0 = computeAngle(finalCenter, p0);
                double angle1 = computeAngle(finalCenter, p1);
                return Double.compare(angle0, angle1);
            }
        };
    }    

    static void sortPoints(List<Point2D> points)
    {
        Collections.sort(points, createComparator(computeCenter(points)));
    }

}


class RectanglePointReorderingPanel extends JPanel implements MouseListener
{
    private List<Point2D> points = new ArrayList<Point2D>();

    public RectanglePointReorderingPanel()
    {
        addMouseListener(this);
    }

    @Override
    protected void paintComponent(Graphics gr)
    {
        super.paintComponent(gr);
        Graphics2D g = (Graphics2D)gr;

        g.setColor(Color.BLACK);
        if (points.size() < 4)
        {
            g.drawString("Click to create points", 20, 20);
        }
        else
        {
            g.drawString("Sorted points. Click again to clear.", 20, 20);
        }
        for (int i=0; i<points.size(); i++)
        {
            Point2D point = points.get(i);
            double x = point.getX();
            double y = point.getY();
            g.setColor(Color.RED);
            g.fill(new Ellipse2D.Double(x-5,y-5,10,10));

            g.setColor(Color.BLACK);

            double angleRad = 
                RectanglePointReorderingTest.computeAngle(
                    RectanglePointReorderingTest.computeCenter(points), point);
            String angleString = String.valueOf((int)Math.toDegrees(angleRad));
            g.drawString(String.valueOf(i)+" ("+angleString+")", (int)x+5, (int)y+5);


        }
    }

    @Override
    public void mouseClicked(MouseEvent e)
    {
        if (points.size() == 4)
        {
            points.clear();
            repaint();
        }
        else
        {
            points.add(e.getPoint());
            if (points.size() == 4)
            {
                RectanglePointReorderingTest.sortPoints(points);
            }
            repaint();
        }
    }


    @Override
    public void mouseEntered(MouseEvent e) {}

    @Override
    public void mouseExited(MouseEvent e) { }

    @Override
    public void mousePressed(MouseEvent e) { }

    @Override
    public void mouseReleased(MouseEvent e) { }


}

答案 4 :(得分:1)

因为您特别要求RotatedRect数据结构中的点的顺序,所以该顺序是可预测的并且永远不会更改(除非您更新库和开发人员以某种方式需要更改该代码 - &gt;非常不太可能)。

我得到的订单很奇怪,如下:

point[0] - bottom left
point[1] - top left
point[2] - top right
point[3] - bottom right

您可以在OpenCV source code中看到RotatedRect中的点列表是如何根据其中心和角度创建的:

public void points(Point pt[])
    {
        double _angle = angle * Math.PI / 180.0;
        double b = (double) Math.cos(_angle) * 0.5f;
        double a = (double) Math.sin(_angle) * 0.5f;

        pt[0] = new Point(
                center.x - a * size.height - b * size.width,
                center.y + b * size.height - a * size.width);

        pt[1] = new Point(
                center.x + a * size.height - b * size.width,
                center.y - b * size.height - a * size.width);

        pt[2] = new Point(
                2 * center.x - pt[0].x,
                2 * center.y - pt[0].y);

        pt[3] = new Point(
                2 * center.x - pt[1].x,
                2 * center.y - pt[1].y);
    }

编辑(评论后):

如果您意识到角落顺序取决于角度。如同之前Sunil所说的那样,更容易获得基于毕达哥拉斯公式的订单。

根据变量ab所具有的符号,订单将有所不同。这些符号取决于cos()sin()结果,而这些结果又取决于角度。所以你有4个符号组合(+a, +b), (-a ,-b), (+a, -b), (-a, +b)。如果我的理论成立,这些将给出4个不同的点顺序。

您可以通过将所有点距离(0,0)来获得左上角。您将获得一个最小距离或两个(相等)最小距离。如果您得到2个最小值,则选择一个作为左上角的矩形角:具有较小x坐标的那个在我看来更有意义并且根据您的绘图。相同的过程可用于其他矩形角。

// Distance (x1, y1) to (x2, y2) = abs( sqrt( (x2-x1)^2 + (y2-y1)^2 ) )
// Note:This is a quite literal translation of the formula, there are more efficient ways.
public static final double pointsDist(Point pt1, Point pt2){        
   return  Math.abs( Math.sqrt( Math.pow((double) (pt2.x - pt1.x), 2) + Math.pow((double) (pt2.y - pt1.y), 2) ) );          
}