2

我正在开发一个 WPF 应用程序,它必须允许用户定义任何四边形以用作相机框架的裁剪区域。这是通过用线连接的四个点覆盖视频流来实现的,以可视化应该在输出图像中使用哪个区域。

作为给我带来麻烦的第二步,我想展示将选定的四边形转换为矩形的结果。我已经设法使用 opencv 的warpPerspective函数来做到这一点,但事实证明这很慢(处理器很重)。

我相信它也可以通过 WPF(GPU 加速)使用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' 角中。

在下面的图像中,矩形和内部四边形之间的区域将被剪裁,而四边形的内容应该被拉伸以适合外部矩形。 我的意图的可视化

我必须应用另一种转换来实现这一目标吗?也许有一个简单的转换可以应用于框架角坐标以将定义的点移动到那里?

4

1 回答 1

2

使用 Accord.NET(作为 nuget 包提供),您可以使用以下内容:

Bitmap origin = // this is your quadrilateral bitmap
var corners = new List<IntPoint>(new IntPoint[]
{
    new IntPoint(topLeftPoint), new IntPoint(topRightPoint), new IntPoint(bottomRightPoint), new IntPoint(bottomLeftPoint)
});
var filter = new QuadrilateralTransformation(corners, rect.Width, rect.Height); // rect = your target rectangle size
Bitmap result = filter.Apply(origin); // voila!
于 2017-08-16T13:03:27.843 回答