纵横比=高度/宽度始终> 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做这件事。
答案 0 :(得分:4)
如果您知道:
,这是一个非常简单的解决方案-45 < roundedRect.angle < 45
roundedRect.size.height > roundedRect.size.width
如果确实如此,则按顺时针顺序的点将始终按此顺序排列:
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)。距离最小的点将是左上角。然后你可以继续使用我在下面给出的步骤。
如果你不能想到这一点,一旦你确定了矩形的左上角,就会有另一种更优雅的方式(因此前三个步骤保持不变)。确定左上角后:
顶点的位置可以很容易地找到(进入 顺时针顺序):
shortest distance = top right point
longest distance = bottom right point
middle distance = bottom left point
您不需要使用条件。
注意:只要高度始终大于宽度的条件,此操作就会成立。
答案 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所说的那样,更容易获得基于毕达哥拉斯公式的订单。
根据变量a
和b
所具有的符号,订单将有所不同。这些符号取决于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) ) );
}