растянуть четырехугольник до размеров прямоугольника

Я работаю над приложением 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 '.

На изображении ниже область между прямоугольником и внутренним четырехугольником должна быть обрезана, а содержимое четырехугольника должно быть растянуто, чтобы соответствовать внешнему прямоугольнику. визуализация моих намерений

Есть ли еще одна трансформация, которую я должен применить, чтобы добиться этого? Возможно, есть простое преобразование, которое я могу применить к координатам углов кадра, чтобы переместить туда определенные точки?


person slawekwin    schedule 22.07.2016    source источник
comment
есть ли прогресс на данный момент?   -  person Nissim    schedule 14.08.2017


Ответы (1)


Используя 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!
person Nissim    schedule 16.08.2017
comment
Хотя это кажется хорошим решением, я не могу легко его проверить сейчас, потому что я больше не работаю над этой частью проекта. Спасибо. - person slawekwin; 16.08.2017