【问题标题】:How to position Landmarks over xamarin forms Image view?如何将地标定位在 xamarin 表单图像视图上?
【发布时间】:2019-10-26 19:38:14
【问题描述】:

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

我的想法是使用带有图像视图的 AbosluteLayout 并将地标放置在图片上。重叠效果很好。问题是地标的点指的是原始图片大小坐标,而渲染图像的大小完全不同。我目前正在使用 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 Image 视图背景:

非常感谢

【问题讨论】:

  • 您是否尝试过获取图像边界? docs.microsoft.com/en-us/dotnet/api/…
  • 是的,这是很容易知道的信息,但正如我在问题中所说,您只能获得 XamarinForms 图像边界,而不是渲染图像框(可能有信箱)

标签: c# image xamarin.forms


【解决方案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) 位置。

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

【讨论】:

  • 感谢您的详细回复,看来这就是我要找的!实施后会更新。
  • 我认为您使用数字创建的示例是错误的,因为您使用的方程式与纵向图像相关,但您制作示例的图像是横向 (600 > 400)。对吗?
  • 你说得对,我把那部分搞砸了,对不起 :) 我应该编辑,还是你能弄清楚?
  • 我能弄明白,谢谢,但为了其他对此解决方案感兴趣的人,也许编辑会很好;)
  • 我错了。例子应该是对的,FRAME是风景。图片不是,是 4:5,所以是人像。
【解决方案2】:

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

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);

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

【讨论】:

    猜你喜欢
    • 2019-08-17
    • 2020-04-14
    • 2020-07-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多