如何在xamarin表单图像视图上放置地标?

时间:2019-06-12 10:11:28

标签: c# image xamarin.forms

我正在使用人脸检测服务,给定图片后,我会返回人脸标志位置。我的目标是将这些界标放置在屏幕上显示的图片上。

我的想法是将AbosluteLayout与Image视图一起使用,并将地标放置在图片上。重叠效果很好。问题在于界标的点是指原始图片尺寸坐标,并且渲染的图像具有完全不同的尺寸。我目前正在使用AspectFit,因此完整图片显示在AbsoluteLayout中,该比例通常使用信箱显示,以保持宽高比。

我尝试过的是考虑屏幕密度,并将其应用于这样的原始界标点:

rightEyePoint.X = Convert.ToInt32(rightEyePoint.X / displayDensity);

距离更近,但不是在所有图片上都可以(与风景/肖像不同)。

然后,我想考虑原始图像,可能想知道渲染图像的比例因子,因此我可以转换所有地标点,但是我不知道该怎么做。我试图创建一个自定义图像视图和渲染器以尝试从中获取比例计算,但是没有成功,因为我没有干涉本地图像组件的计算位置:

public class CustomImageView : Image
{
    protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
    {
        return base.OnMeasure(widthConstraint, heightConstraint);


    }

    protected override void OnSizeAllocated(double width, double height)
    {
        base.OnSizeAllocated(width, height);

    }

    protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        base.OnPropertyChanged(propertyName);
    }
}

public class CustomImageViewRenderer : ImageRenderer
{
    public CustomImageViewRenderer(Context context) : base(context) { }

    protected override void OnElementChanged(ElementChangedEventArgs<Image> e)
    {
        base.OnElementChanged(e);
        if(e.NewElement != null)
        {
            var image = Control as ImageView;

        }
    }
    protected override void OnSizeChanged(int w, int h, int oldw, int oldh)
    {
        base.OnSizeChanged(w, h, oldw, oldh);
    }

    protected override Task TryUpdateBitmap(Image previous = null)
    {
        return base.TryUpdateBitmap(previous);
    }
}

然后我想我可以自己计算当前渲染的图像大小,但似乎无法获取。因此,我只得到xamarin表单图像组件的大小,而不是其中呈现的图像。在这一点上,我认为我可以根据纵横比来计算渲染图像的大小,因为我正在使用AspectFit,但在过程中陷入了困境。

axml(示例):

<AbsoluteLayout BackgroundColor="Fuchsia" HorizontalOptions="Fill" VerticalOptions="Fill">
    <Controls:PinchToZoomContainer HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" CurrentPosition="{Binding CurrentPhotoPosition}" CurrentScale="{Binding CurrentPhotoScale}" AbsoluteLayout.LayoutBounds="0, 0, 1, 1" AbsoluteLayout.LayoutFlags="All" IsEnabled="{ Binding CurrentStep, Converter={StaticResource checkIntToBooleanValueConverter}, ConverterParameter=2}">
        <Image BackgroundColor="Blue" Source="{Binding CurrentPhotoPath}">
              <Image.Behaviors>
                  <behaviors:EventToCommandBehavior 
                                                EventName="SizeChanged" 
                                                Command="{Binding ImageSizeChangedCommand}"/>
                                        </Image.Behaviors>
                                    </Image>
                                    <!--<Controls:CustomImageView Source="{Binding CurrentPhotoPath}" BackgroundColor="Purple" SizeChanged=""/-->
                                </Controls:PinchToZoomContainer>

                                <!-- calculated landmarks -->
                                <Controls:LandmarkView 
                                Padding="2" 
                                DragMode="TouchAndRelease" 
                                LBounds="{Binding HairStartLandmarkBounds, Mode=TwoWay}" 
                                DragDirection="Vertical"
                                HorizontalOptions="FillAndExpand"
                                VerticalOptions="Start"
                                IsVisible="{ Binding CurrentStep, Converter={StaticResource checkIntToBooleanValueConverter}, ConverterParameter=3}"
                                x:Name="HairStartLandmark" 
                                ToggleDraggingCommand="{Binding HairStartLandmarkTouchedCommand}">
                                    <Controls:LandmarkView.Content>
                                        <AbsoluteLayout>
                                            <Image BackgroundColor="Red" 
                                                   WidthRequest="{Binding LandmarkLineWidth}" 
                                                   AbsoluteLayout.LayoutBounds= "{Binding LandmarkLineXPos}"
                                                   AbsoluteLayout.LayoutFlags="YProportional,WidthProportional"/>
                                            <ffimageloading:CachedImage 
                                                WidthRequest="{Binding HairStartLandmarkIconSize}" 
                                                HeightRequest="{Binding HairStartLandmarkIconSize}"
                                                Source="{Binding HairStartLandmarkIcon, Converter={StaticResource SvgImageSourceConverter}}">
                                            </ffimageloading:CachedImage>
                                        </AbsoluteLayout>
                                    </Controls:LandmarkView.Content>
                                </Controls:LandmarkView>
</AbsoluteLayout>

所以我的问题是,如何才能轻松获得渲染图像坐标系的原点和比例因子,以将其应用于界标点坐标并将其正确定位在渲染图像上?

更新:

我添加一张我所拥有的和我所需要的当前状态的图像。蓝色背景是Xamarin.Forms图像视图背景:

Landmarks positioning image

非常感谢您

2 个答案:

答案 0 :(得分:1)

一些数学传入。

Xamarin Forms使用与设备无关的像素,所以我的猜测是,您需要在帧内渲染图像的比例。假设其 A1:A2 其中一个单元长 x 。给出了帧的比例,例如 B1:B2 ,其中一个单位长 y 。我们的问题是x未知。因此,我们需要从等式中删除它(字面:D)

首先,您需要确定哪个更大,A1或A2。为什么?因为较长的一侧将成为完全适合框架的一侧。 (例如,如果图像的宽度大于高度,则图像的顶部和底部将具有信箱。如果图像的高度大于宽度,则图像的左侧和右侧将具有信箱。) A2较大(表示图片的高度大于宽度)。

在这种情况下, A2 * x 应该等于 B2 * y 。因此, x = B2 / A2 * y

您的图片将位于框架的中间,因此框架的另一侧将像:

[{z}宽蓝色背景]-[图像宽度]-[{z}宽蓝色背景]

现在,我们只需要获取 z 的长度。就是(B1 * y-(B2 / A2 * y)* A1)/ 2。


现在让数字更易理解:

背景框架的大小:600 x 400(宽x高)-可以从代码中检索-表示B1 = 3,B2 = 2和y = 200。

图像比例:4:5-可以从原始图像尺寸中获取-表示A1 = 4,A2 = 5,我们不知道x是多少。 (因为这是您需要的一部分。)

这将导致x = 2/5 * 200 = 80。

这最终意味着z为(3 * 200-80 * 4)/ 2 =(600-320)/ 2 = 140。

这意味着,在这种情况下,图像的第一个倾斜是在背景框架内的(140,0)位置。

我希望这会有所帮助,并让您创建自己的计算方法。

答案 1 :(得分:0)

最后,我使用了一个更简单的系统。这是我创建的用于获取渲染图像边界的方法的代码。

private void CalculateRendredImageBounds()
    {
        Point origin = new Point();

        _renderedImageRatio = CurrentPhotoSize.Width / CurrentPhotoSize.Height;

        if (CurrentPhotoSize.Height > CurrentPhotoSize.Width)
        {
            //case height greater than width (portrait)
            _renderedPhotoWidth = (CurrentPhotoSize.Width * RenderedImageContainerSize.Height) / CurrentPhotoSize.Height;
            origin.X = Convert.ToInt32((RenderedImageContainerSize.Width - _renderedPhotoWidth) / 2);
            origin.Y = 0;

            _renderedImageBounds = new Rectangle(origin.X, origin.Y, _renderedPhotoWidth, RenderedImageContainerSize.Height);
        }
        else
        {
            //case width greater than height (landscape)
            _renderedPhotoHeight = (CurrentPhotoSize.Height * RenderedImageContainerSize.Width) / CurrentPhotoSize.Width;
            origin.X = 0;
            origin.Y = Convert.ToInt32((RenderedImageContainerSize.Height - _renderedPhotoHeight) / 2);

            _renderedImageBounds = new Rectangle(origin.X, origin.Y, RenderedImageContainerSize.Width, _renderedPhotoHeight);
        }
    }

然后,当您想在容器坐标系中找到一个点时,可以使用以下内容

X景观:

Convert.ToInt32((originalXPos * RenderedImageContainerSize.Width) / CurrentPhotoSize.Width);

Y风景:

Convert.ToInt32((originalYPos * _renderedPhotoHeight) / CurrentPhotoSize.Height + (RenderedImageContainerSize.Height - _renderedPhotoHeight) / 2);

X肖像:

Convert.ToInt32((originalXPos * _renderedPhotoWidth) / CurrentPhotoSize.Width + (RenderedImageContainerSize.Width - _renderedPhotoWidth) / 2);

Y人像:

Convert.ToInt32((originalYPos * RenderedImageContainerSize.Height) / CurrentPhotoSize.Height);

注意:我没有显示私有属性声明,但是我认为代码足够可读。