c# - 我需要一个弯曲的矩形的透视变换
问题描述
有没有办法以某种方式旋转图像?我将向您解释:我不希望旋转PictureBox1.Image.RotateFlip(RotateFlipType.Rotate90FlipNone)
,或如图所示 ,而是将弯曲的矩形定向到相机(与相机齐平)。
我为此画了一张图。这是从上面的视图。想象一下,您是站在点 (0, 0) 并正在为身体拍照的人(“Rechteck”)。我想将该图像旋转某个角度,例如 35°,因为您与身体呈 35°(透视)。
我已经准备了一些源代码并为此进行了数学计算。代码的工作方式如下:用户必须输入从自己到矩形中心的距离,以及角度。在附图中,这是红线。您还需要知道矩形的宽度。程序计算 x 和 y 坐标 (x_v
和y_v
)。程序计算从矩形左边缘到右边缘的所有距离和角度。
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace schiefes_Rechteck
{
public partial class Form_Main : Form
{
/// <summary>
/// entered angle in degrees
/// </summary>
private Int16 eingegebener_Winkel_in_Grad;
private UInt16 Radius_in_mm;
/// <summary>
/// Length of the rectangle in mm
/// </summary>
private UInt16 Laenge_des_Rechtecks_in_mm;
/// <summary>
/// all distances [mm]
/// </summary>
private List<double> alle_Entfernungen = new List<double>();
/// <summary>
/// all angles [°]
/// </summary>
private List<double> alle_Winkel = new List<double>();
public Form_Main()
{
InitializeComponent();
}
private void Form_Main_Load(object sender, EventArgs e)
{
this.BackColor = Color.FromArgb(148, 148, 109);
this.Location = new Point(0, 0);
Button_Start.BackColor = Color.FromArgb(194, 194, 165);
TextBox_Entfernung.Text = "1300";
TextBox_Winkel.Text = "35";
TextBox_Rechtecklaenge.Text = "503";
if (System.IO.File.Exists(Application.StartupPath + "\\schiefes_Rechteck_Grafik.PNG"))
{
PictureBox1.Image = Image.FromFile(Application.StartupPath + "\\schiefes_Rechteck_Grafik.PNG");
}
}
private void Form_Main_FormClosing(object sender, FormClosingEventArgs e)
{
if (PictureBox1.Image != null)
{
PictureBox1.Image.Dispose();
}
if (PictureBox2.Image != null)
{
PictureBox2.Image.Dispose();
}
}
private void TextBox_Entfernung_TextChanged(object sender, EventArgs e)
{
if (!string.IsNullOrEmpty(TextBox_Entfernung.Text))
{
bool erfolgreich = UInt16.TryParse(TextBox_Entfernung.Text, out Radius_in_mm);
if (erfolgreich)
{
TextBox_Entfernung.ForeColor = Color.FromArgb(0, 163, 0);
}
else
{
TextBox_Entfernung.ForeColor = Color.FromArgb(163, 0, 0);
}
}
}
private void TextBox_Winkel_TextChanged(object sender, EventArgs e)
{
if (!string.IsNullOrEmpty(TextBox_Winkel.Text))
{
bool erfolgreich = Int16.TryParse(TextBox_Winkel.Text, out eingegebener_Winkel_in_Grad);
if (erfolgreich)
{
TextBox_Winkel.ForeColor = Color.FromArgb(0, 163, 0);
}
else
{
TextBox_Winkel.ForeColor = Color.FromArgb(163, 0, 0);
}
}
}
private void TextBox_Rechtecklaenge_TextChanged(object sender, EventArgs e)
{
if (!string.IsNullOrEmpty(TextBox_Rechtecklaenge.Text))
{
bool erfolgreich = UInt16.TryParse(TextBox_Rechtecklaenge.Text, out Laenge_des_Rechtecks_in_mm);
if (erfolgreich)
{
TextBox_Rechtecklaenge.ForeColor = Color.FromArgb(0, 163, 0);
}
else
{
TextBox_Rechtecklaenge.ForeColor = Color.FromArgb(163, 0, 0);
}
}
}
private async void Button_Start_Click(object sender, EventArgs e)
{
ListBox1.Items.Clear();
await Task.Run(() => Berechnung_aller_Werte());
}
private void Berechnung_aller_Werte()
{
alle_Entfernungen.Clear();
alle_Winkel.Clear();
double x_v, y_v; // Mitte des Rechtecks, davon die x-Koordinate und die y-Koordinate.
x_v = Radius_in_mm * Math.Cos((90.0 - (double)eingegebener_Winkel_in_Grad) * Math.PI / 180.0); //richtig
y_v = Radius_in_mm * Math.Sin((90.0 - (double)eingegebener_Winkel_in_Grad) * Math.PI / 180.0); //richtig
double alpha_in_Grad = 0.0;
double Entfernung = 0.0;
double halbe_Rechteckbreite = Laenge_des_Rechtecks_in_mm / 2.0;
double Position_linker_Rand = x_v - halbe_Rechteckbreite; //richtig
double Zaehler = 0.0;
while (Zaehler < Laenge_des_Rechtecks_in_mm)
{
alpha_in_Grad = Math.Atan((Position_linker_Rand + Zaehler) / y_v) * 180.0 / Math.PI;
alle_Winkel.Add(alpha_in_Grad);
Entfernung = Math.Sqrt(Math.Pow(Position_linker_Rand + Zaehler, 2) + Math.Pow(y_v, 2));
alle_Entfernungen.Add(Entfernung);
Zaehler += 1.0;
}
this.BeginInvoke((Action)(() => { ListBox1.Items.Add(Math.Round(alle_Entfernungen.Last(), 0).ToString() + " mm"); ListBox1.Items.Add(Math.Round(alle_Winkel.Last(), 0).ToString() + " °"); }));
}
}//Form
}
解决方案
如果您想要一个不弯曲的图像,即所有线条都保持垂直或水平,那么图像所发生的一切就是从侧面看它的宽度会显得更小,而它的高度将保持不变。
通过将您看到图像的视角度(即图像中黄绿色线和粉红色线之间的角度)除以您站在正前方时的相应角度来获取 x 轴的比例因子的图像。这个因素的一个很好的近似值是简单cos(36° * PI / 180)
的,其中角度是与图像中红线的角度。
xScale = Math.Cos(angleToMidLineInDegrees * Math.PI / 180);
yScale = 1;
或者干脆
xScale = Math.Cos(angleToMidLineInRadians);
yScale = 1;
在哪里
angleToMidLineInRadians = Math.ATan(x_redLine / y_v);
或一步
xScale = y_v / Math.Sqrt(x_redLine * x_redLine + y_v * y_v);
参见: WolframAlpha 上的cos(arctan(x/y))。
但是,在变换图像时,您必须进行逆变换(如视频末尾所述),因为您要确定变换后图像的像素。即,您将在伪代码中执行(其中t
表示已转换且没有t
原始坐标):
for (yt = 0 to height_t - 1; yt++) {
for (xt = 0 to width_t - 1; xt++) {
(x, y) = inverse_transformation(xt, yt);
color_t = get_color(picture, x, y);
draw(picture_t, xt, yt, color_t);
}
}
推荐阅读
- javascript - React Context 总是会重新渲染
- batch-file - 7zip 使用批处理文件创建空档案
- sql - 连接两个表并满足条件时如何删除重复行
- html - css网格中的嵌套div在父div之外?
- javascript - 在 JS 中生成均匀分布的随机数
- ios - 当基本字符串包含双引号时,仅针对 Swift 中 UILabel 的特定范围的点击手势不起作用
- vue.js - 挂载钩子中的错误:“TypeError:无法读取 null 的属性‘类型’”
- powershell - 将派生属性添加到 powershell 命令管道
- node.js - 尝试在 node-mysql 中获取 MAX 查询的值时出错
- jenkins - 声明式管道 Jenkins groovy.lang.MissingMethodException