私は、アフィン変換前後の3点(P0、P1、P2)の位置(XとY)を知っています。私は、この変換を一致AffineTransformationオブジェクトを構築したいです。言い換えれば、私は自分の知ら先に知られている点P0、P1、P2を移動するアフィン変換を見つけたいです。
ここで私はこれまで何をやったかであります:
package image_transformation;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import math.Vector2d;
public class ImageTransformation {
public static void main(String[] args) throws IOException {
// the position of the points before the transformation
Vector2d[] src = new Vector2d[] {
new Vector2d(486, 191),
new Vector2d(456, 565),
new Vector2d(149, 353)
};
// the position of the points after the transformation
Vector2d[] dest = new Vector2d[] {
new Vector2d(0, 0),
new Vector2d(0, 600),
new Vector2d(600, 600)
};
// the transformation that we are building
AffineTransform at = new AffineTransform();
// the translation to move the p0 to its destination
Vector2d translationVec = dest[0].sub(src[0]);
at.translate(translationVec.x, translationVec.y);
// the rotation around p0 (it will not move) to align p0, p1 and p1's destination
Vector2d vec0 = src[1].sub(src[0]);
Vector2d vec1 = dest[1].sub(dest[0]);
double angle = orientedAngle(vec0, vec1);
at.rotate(angle, src[0].x, src[0].y);
// the scaling to adjust the distance between p0 and p1
// problem: it will induce a translation
Vector2d origin = src[1].sub(src[0]);
Vector2d target = origin.normalize().mult(dest[1].sub(dest[0]).length());
Vector2d scale = new Vector2d(target.x / origin.x, target.y / origin.y);
if (Double.isNaN(scale.x)) scale.x = 1D;
if (Double.isNaN(scale.y)) scale.y = 1D;
at.scale(scale.x, scale.y);
// TODO compute the induced translation and apply its inverse to move p0 and p1 to their destination
// TODO terminate the transformation to move p2 to its destination
// apply the transformation to an image to check if it works
BufferedImage inImg = ImageIO.read(new File("input.png"));
BufferedImage outImg = new BufferedImage(inImg.getWidth(), inImg.getHeight(), BufferedImage.TYPE_INT_ARGB);
new AffineTransformOp(at, AffineTransformOp.TYPE_BICUBIC).filter(inImg, outImg);
File outFile = new File("output.png");
outFile.createNewFile();
ImageIO.write(outImg, "png", outFile);
}
private static double orientedAngle(Vector2d vec0, Vector2d vec1) {
return Math.atan2(vec0.x * vec1.y - vec0.y * vec1.x, vec0.x * vec1.x + vec0.y * vec1.y);
}
}
Vector2dクラスはベクトルに関するいくつかの基本的な数学を行い、そのメソッドのそれぞれは、自己説明(... [stract]、MULT [iply]、長さ、ノーマライズなどサブ)自分の名前です。
私は、このアルゴリズムを終了する方法は考えています。この方法は、すでにこのすべてを行うことが存在する場合にも、私は非常にそれを使用することを嬉しくなります。
これは、少なくとも密接に関係している、4点を変形テクスチャ、私はそれが重複したとみなすことができると言うことではないと思います。
あなたはそこにかなりの数学のものをしました。しかし、おそらくそれは必要はありません。右のアプローチでは、問題自体はかなり簡単です。2Dでのアフィン変換の意味を考えてみましょう:それは別のものに1スペースを変換します。ここで重要なポイントは次のとおりです。
行列の列は単位ベクトルに行列を適用した結果であります
さて、あなたは3点を持っているとき、あなたは彼らからベクトルを計算することができます:
double dx1 = p1.getX() - p0.getX();
double dy1 = p1.getY() - p0.getY();
double dx2 = p2.getX() - p0.getX();
double dy2 = p2.getY() - p0.getY();
そして、あなたは、単にの最初の列にこれらの値をプラグインすることができますAffineTransform
。最後の列は、AffineTransform
で与えられる訳が含まれていp0
。結果はAffineTransform
点に点(0,0)、(1,0)及び(0,1)変換そのp0
、p1
およびp2
それぞれ。あなたの場合は反転これは、変換、それがポイントに変換しp0
、p1
、とp2
の点(0,0)、(1,0)と(0,1)へ。
あなたがしなければならないすべてはあるので、
- その変換ソース点が単位ベクトルに変換を作成します
- 宛先ポイントにその変換を単位ベクトルに変換作成
- 連結2
擬似コードは(!)本当に簡単です
AffineTransform unitToSrc = computeTransform(src[0], src[1], src[2]);
AffineTransform unitToDst = computeTransform(dst[0], dst[1], dst[2]);
AffineTransform at = new AffineTransform();
at.concatenate(unitToDst);
at.concatenate(unitToSrc.inverted());
全体のものはMCVEとして、ここでは実装されています。赤い点は、「ソース」点で、緑の点は、「先」点です。あなたは、マウスでそれらを周りにドラッグすることができます。
青い円はソースポイントに変換を適用した結果を示し、あなたがすることができます参照して、彼らが所望の目的地の位置で終わること。
実際の計算はして行われるcomputeTransform
方法。これはに基づいて実装されていることを注意java.awt.geom.Point2D
クラス(とないVector2d
あなたは、省略することをクラス)が、これは変更に簡単にする必要があります:ポイント・ツーまたはベクトルクラスから使用されている唯一のことは、x / y座標です。それを超えて、存在しないすべてに関与なし(カスタム)数学の実装に。唯一の数学は、アフィン変換を反転されていますが、そこに内蔵されていますそのための機能。
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.util.Arrays;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class AffineTransformFromPoints
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(() -> createAndShowGUI());
}
private static void createAndShowGUI()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
AffineTransformFromPointsPanel panel =
new AffineTransformFromPointsPanel();
f.getContentPane().setLayout(new BorderLayout());
f.getContentPane().add(panel, BorderLayout.CENTER);
f.setSize(1200,900);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class AffineTransformFromPointsPanel extends JPanel
implements MouseListener, MouseMotionListener
{
private Point2D draggedPoint;
// the position of the points before the transformation
Point2D[] src = new Point2D[] {
new Point2D.Double(486, 191),
new Point2D.Double(456, 565),
new Point2D.Double(149, 353)
};
// the position of the points after the transformation
Point2D[] dst = new Point2D[] {
new Point2D.Double(0, 0),
new Point2D.Double(0, 600),
new Point2D.Double(600, 600)
};
public AffineTransformFromPointsPanel()
{
addMouseListener(this);
addMouseMotionListener(this);
}
@Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D)gr;
g.setColor(Color.WHITE);
g.fillRect(0, 0, getWidth(), getHeight());
g.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(Color.RED);
for (Point2D v : src)
{
paint(g, v);
}
g.setColor(Color.GREEN);
for (Point2D v : dst)
{
paint(g, v);
}
g.setColor(Color.BLUE);
AffineTransform at = computeTransform(src, dst);
for (Point2D v : src)
{
draw(g, v, at);
}
}
private static AffineTransform computeTransform(
Point2D src[], Point2D dst[])
{
AffineTransform unitToSrc = computeTransform(src[0], src[1], src[2]);
AffineTransform unitToDst = computeTransform(dst[0], dst[1], dst[2]);
AffineTransform srcToUnit = null;
try
{
srcToUnit = unitToSrc.createInverse();
}
catch (NoninvertibleTransformException e)
{
System.out.println(e.getMessage());
return new AffineTransform();
}
AffineTransform at = new AffineTransform();
at.concatenate(unitToDst);
at.concatenate(srcToUnit);
return at;
}
private static AffineTransform computeTransform(
Point2D p0, Point2D p1, Point2D p2)
{
AffineTransform at = new AffineTransform();
double dx1 = p1.getX() - p0.getX();
double dy1 = p1.getY() - p0.getY();
double dx2 = p2.getX() - p0.getX();
double dy2 = p2.getY() - p0.getY();
at.setTransform(dx1, dy1, dx2, dy2, p0.getX(), p0.getY());
return at;
}
private static void paint(Graphics2D g, Point2D p)
{
double r = 6;
g.fill(new Ellipse2D.Double(
p.getX() - r, p.getY() - r, r + r, r + r));
}
private static void draw(Graphics2D g, Point2D v, AffineTransform at)
{
double r = 8;
Point2D p = new Point2D.Double(v.getX(), v.getY());
at.transform(p, p);
g.draw(new Ellipse2D.Double(
p.getX() - r, p.getY() - r, r + r, r + r));
}
@Override
public void mouseDragged(MouseEvent e)
{
if (draggedPoint != null)
{
draggedPoint.setLocation(e.getPoint());
repaint();
}
}
@Override
public void mousePressed(MouseEvent e)
{
draggedPoint = closest(e.getPoint(), Arrays.asList(src));
if (draggedPoint == null)
{
draggedPoint = closest(e.getPoint(), Arrays.asList(dst));
}
}
private static Point2D closest(
Point2D p, Iterable<? extends Point2D> points)
{
final double threshold = 10;
Point2D closestPoint = null;
double minDistance = Double.MAX_VALUE;
for (Point2D point : points)
{
double dd = point.distance(p);
if (dd < threshold && dd < minDistance)
{
minDistance = dd;
closestPoint = point;
}
}
return closestPoint;
}
@Override
public void mouseReleased(MouseEvent e)
{
draggedPoint = null;
}
@Override
public void mouseMoved(MouseEvent e)
{
// Nothing to do here
}
@Override
public void mouseClicked(MouseEvent e)
{
// Nothing to do here
}
@Override
public void mouseEntered(MouseEvent e)
{
// Nothing to do here
}
@Override
public void mouseExited(MouseEvent e)
{
// Nothing to do here
}
}