Я работаю над приложением WPF, которое должно позволять пользователю определять любую четырехугольную форму, которая будет служить областью обрезки для кадра камеры. Это достигается путем наложения видеопотока четырьмя точками, соединенными линиями, чтобы визуализировать, какая область должна использоваться в выходном изображении.
В качестве второго шага, который вызывает у меня проблемы, я хотел бы показать результат преобразования выбранного четырехугольника в прямоугольник. Мне удалось сделать это с помощью opencv warpPerspective, но это оказалось быть медленным (тяжелым процессором).
Я считаю, что это также можно сделать с помощью WPF (ускорение на графическом процессоре), используя Viewport3D
и 3D-преобразования. Я нашел очень полезную статью, которая привела меня к следующему коду
<Viewport3D x:Name="canvasViewPort">
<Viewport3D.Camera>
<OrthographicCamera Position="0.5 0.5 1" LookDirection="0 0 -1" UpDirection="0 1 0" Width="1" />
</Viewport3D.Camera>
<ModelVisual3D>
<ModelVisual3D.Content>
<DirectionalLight Color="White" Direction="0,0,-1"/>
</ModelVisual3D.Content>
</ModelVisual3D>
<Viewport2DVisual3D>
<Viewport2DVisual3D.Transform>
<MatrixTransform3D x:Name="canvas3dTransform" />
</Viewport2DVisual3D.Transform>
<Viewport2DVisual3D.Geometry>
<MeshGeometry3D Positions="0 0 0, 0 1 0, 1 0 0, 1 1 0" TextureCoordinates="0 1, 0 0, 1 1, 1 0" TriangleIndices="0 2 1, 2 3 1"/>
</Viewport2DVisual3D.Geometry>
<Viewport2DVisual3D.Material>
<DiffuseMaterial Brush="White" Viewport2DVisual3D.IsVisualHostMaterial="True"/>
</Viewport2DVisual3D.Material>
<Canvas x:Name="mainCanvas" Margin="0" Width="{Binding ResolutionX}" Height="{Binding ResolutionY}">
<Canvas.Background>
<ImageBrush ImageSource="{Binding BackgroundImg}" />
</Canvas.Background>
</Canvas>
</Viewport2DVisual3D>
</Viewport3D>
И
protected void Transform()
{
double targetWidth = canvasViewPort.ActualWidth, xScale = targetWidth / ResolutionX,
targetHeight = canvasViewPort.ActualHeight, yScale = targetHeight / ResolutionY;
var points3d = new Point3D[4];
if (EditorMode || Corners == null || Corners.Count < 4)
{ //fit canvas in parent container without warping to allow Corners edition
points3d[0] = Point2dTo3d(canvasViewPort, new Point(0, 0));
points3d[1] = Point2dTo3d(canvasViewPort, new Point(0, targetHeight));
points3d[2] = Point2dTo3d(canvasViewPort, new Point(targetWidth, 0));
points3d[3] = Point2dTo3d(canvasViewPort, new Point(targetWidth, targetHeight));
}
else
{ //get warped points, Corners indices order is to reflect convention used in the linked blog post
points3d[0] = Point2dTo3d(canvasViewPort, new Point(Corners[0].X * xScale, Corners[0].Y * yScale));
points3d[1] = Point2dTo3d(canvasViewPort, new Point(Corners[3].X * xScale, Corners[3].Y * yScale));
points3d[2] = Point2dTo3d(canvasViewPort, new Point(Corners[1].X * xScale, Corners[1].Y * yScale));
points3d[3] = Point2dTo3d(canvasViewPort, new Point(Corners[2].X * xScale, Corners[2].Y * yScale));
}
var A = new Matrix3D();
A.M11 = points3d[2].X - points3d[0].X;
A.M12 = points3d[2].Y - points3d[0].Y;
A.M21 = points3d[1].X - points3d[0].X;
A.M22 = points3d[1].Y - points3d[0].Y;
A.OffsetX = points3d[0].X;
A.OffsetY = points3d[0].Y;
double den = A.M11 * A.M22 - A.M12 * A.M21;
double a = (A.M22 * points3d[3].X - A.M21 * points3d[3].Y +
A.M21 * A.OffsetY - A.M22 * A.OffsetX) / den;
double b = (A.M11 * points3d[3].Y - A.M12 * points3d[3].X +
A.M12 * A.OffsetX - A.M11 * A.OffsetY) / den;
var B = new Matrix3D();
B.M11 = a / (a + b - 1);
B.M22 = b / (a + b - 1);
B.M14 = B.M11 - 1;
B.M24 = B.M22 - 1;
canvas3dTransform.Matrix = B * A;
}
Point3D Point2dTo3d(Viewport3D vp, Point pt)
{
var cam = (OrthographicCamera)canvasViewPort.Camera;
double x = cam.Width / vp.ActualWidth * (pt.X - vp.ActualWidth / 2) + cam.Position.X;
double y = cam.Width / vp.ActualWidth * (pt.Y - vp.ActualHeight / 2) + cam.Position.Y;
return new Point3D(x, y, 0);
}
К сожалению, это делает противоположное тому, что мне нужно - он перемещает углы кадра в точки, определенные в коллекции Corners
, тогда как мне нужно разместить определенные точки в углах Canvas
'.
На изображении ниже область между прямоугольником и внутренним четырехугольником должна быть обрезана, а содержимое четырехугольника должно быть растянуто, чтобы соответствовать внешнему прямоугольнику.
Есть ли еще одна трансформация, которую я должен применить, чтобы добиться этого? Возможно, есть простое преобразование, которое я могу применить к координатам углов кадра, чтобы переместить туда определенные точки?