WPF Image Pan, Zoom y desplazamiento con capas en un lienzo

Espero que alguien pueda ayudarme aquí. Estoy creando una aplicación de imágenes WPF que toma imágenes en vivo de una cámara, lo que permite a los usuarios ver la imagen y luego resaltar las regiones de interés (ROI) en esa imagen. La información sobre las ROI (ancho, altura, ubicación relativa a un punto de la imagen, etc.) se envía de vuelta a la cámara, indicando / entrenando al firmware de la cámara dónde buscar códigos de barras, texto, niveles de líquidos, giros en un tornillo, etc. en la imagen). Una característica deseada es la capacidad de desplazar y acercar la imagen y sus ROI, así como desplazarse cuando la imagen se amplía más que el área de visualización. StrokeThickness y FontSize de la ROI necesitan mantener la escala original, pero la anchura y la altura de las formas dentro de una ROI necesitan escalar con la imagen (esto es fundamental para capturar ubicaciones exactas de píxeles para transmitir a la cámara). Tengo la mayor parte de esto resuelto con la excepción del desplazamiento y algunos otros problemas. Mis dos áreas de preocupación son:

Cuando introduzco un ScrollViewer no obtengo ningún comportamiento de desplazamiento. Como lo entiendo, necesito introducir un LayoutTransform para obtener el comportamiento correcto de ScrollViewer. Sin embargo, cuando hago eso, otras áreas comienzan a descomponerse (por ejemplo, las ROI no mantienen su posición correcta sobre la imagen, o el puntero del mouse comienza a deslizarse fuera del punto seleccionado en la imagen cuando se desplaza, o en la esquina izquierda de mi imagen rebota a la posición actual del mouse en MouseDown.)

No puedo obtener la escala de mi ROI de la forma en que los necesito. Tengo este trabajo, pero no es lo ideal. Lo que tengo no conserva el grosor exacto del trazo, y no he investigado ignorar la escala en los bloques de texto. Espero que veas lo que estoy haciendo en los ejemplos de código.

Estoy seguro de que mi problema tiene que ver con mi falta de comprensión de las Transformaciones y su relación con el sistema de diseño de WPF. Con suerte, una versión del código que muestre lo que he logrado hasta ahora ayudará (ver más abajo).

Para su información, si los Adorners son la sugerencia, es posible que no funcione en mi escenario porque podría terminar con más adornos de los que se admiten (se dice que los adornos de 144 son cuando las cosas comienzan a descomponerse).

En primer lugar, a continuación se muestra una captura de pantalla que muestra una imagen con ROI (texto y una forma). El rectángulo, la elipse y el texto deben seguir el área de la imagen en escala y rotación, pero no deben escalar en grosor o tamaño de fuente.

Aquí está el XAML que muestra la imagen anterior, junto con un control deslizante para el zoom (la rueda de la rueda del mouse aparecerá más adelante)

<Window x:Class="PanZoomStackOverflow.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    mc:Ignorable="d"
    Title="MainWindow" Height="768" Width="1024">

<DockPanel>
  <Slider x:Name="_ImageZoomSlider" DockPanel.Dock="Bottom"
          Value="2"
          HorizontalAlignment="Center" Margin="6,0,0,0" 
          Width="143" Minimum=".5" Maximum="20" SmallChange=".1" 
          LargeChange=".2" TickFrequency="2" 
          TickPlacement="BottomRight" Padding="0" Height="23"/>

  <!-- This resides in a user control in my solution -->
  <Grid x:Name="LayoutRoot">
    <ScrollViewer Name="border" HorizontalScrollBarVisibility="Auto" 
                  VerticalScrollBarVisibility="Auto">
      <Grid x:Name="_ImageDisplayGrid">
        <Image x:Name="_DisplayImage" Margin="2" Stretch="None"
               Source="Untitled.bmp"
               RenderTransformOrigin ="0.5,0.5"
               RenderOptions.BitmapScalingMode="NearestNeighbor"
               MouseLeftButtonDown="ImageScrollArea_MouseLeftButtonDown"
               MouseLeftButtonUp="ImageScrollArea_MouseLeftButtonUp"
               MouseMove="ImageScrollArea_MouseMove">                            
           <Image.LayoutTransform>
             <TransformGroup>
               <ScaleTransform />
               <TranslateTransform />
             </TransformGroup>
           </Image.LayoutTransform>
         </Image>
         <AdornerDecorator> <!-- Using this Adorner Decorator for Move, Resize and Rotation and feedback adornernments -->
           <Canvas x:Name="_ROICollectionCanvas"
                   Width="{Binding ElementName=_DisplayImage, Path=ActualWidth, Mode=OneWay}"
                   Height="{Binding ElementName=_DisplayImage, Path=ActualHeight, Mode=OneWay}"
                   Margin="{Binding ElementName=_DisplayImage, Path=Margin, Mode=OneWay}">

             <!-- This is a user control in my solution -->
             <Grid IsHitTestVisible="False" Canvas.Left="138" Canvas.Top="58" Height="25" Width="186">
               <TextBlock Text="Rectangle ROI" HorizontalAlignment="Center" VerticalAlignment="Top" 
                          Foreground="Orange" FontWeight="Bold" Margin="0,-15,0,0"/>
                 <Rectangle StrokeThickness="2" Stroke="Orange"/>
             </Grid>

             <!-- This is a user control in my solution -->
             <Grid IsHitTestVisible="False" Canvas.Left="176" Canvas.Top="154" Height="65" Width="69">
               <TextBlock Text="Ellipse ROI" HorizontalAlignment="Center" VerticalAlignment="Top" 
                          Foreground="Orange" FontWeight="Bold" Margin="0,-15,0,0"/>
               <Ellipse StrokeThickness="2" Stroke="Orange"/>
             </Grid>
           </Canvas>
         </AdornerDecorator>
       </Grid>
     </ScrollViewer>
  </Grid>
</DockPanel>

Aquí está el C # que maneja la panorámica y el zoom.

public partial class MainWindow : Window
{
private Point origin;
private Point start;
private Slider _slider;

public MainWindow()
{
    this.InitializeComponent();

    //Setup a transform group that we'll use to manage panning of the image area
    TransformGroup group = new TransformGroup();
    ScaleTransform st = new ScaleTransform();
    group.Children.Add(st);
    TranslateTransform tt = new TranslateTransform();
    group.Children.Add(tt);
    //Wire up the slider to the image for zooming
    _slider = _ImageZoomSlider;
    _slider.ValueChanged += _ImageZoomSlider_ValueChanged;
    st.ScaleX = _slider.Value;
    st.ScaleY = _slider.Value;
    //_ImageScrollArea.RenderTransformOrigin = new Point(0.5, 0.5);
    //_ImageScrollArea.LayoutTransform = group;
    _DisplayImage.RenderTransformOrigin = new Point(0.5, 0.5);
    _DisplayImage.RenderTransform = group;
    _ROICollectionCanvas.RenderTransformOrigin = new Point(0.5, 0.5);
    _ROICollectionCanvas.RenderTransform = group;
}

//Captures the mouse to prepare for panning the scrollable image area
private void ImageScrollArea_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
    _DisplayImage.ReleaseMouseCapture();
}

//Moves/Pans the scrollable image area  assuming mouse is captured.
private void ImageScrollArea_MouseMove(object sender, MouseEventArgs e)
{
    if (!_DisplayImage.IsMouseCaptured) return;

    var tt = (TranslateTransform)((TransformGroup)_DisplayImage.RenderTransform).Children.First(tr => tr is TranslateTransform);

    Vector v = start - e.GetPosition(border);
    tt.X = origin.X - v.X;
    tt.Y = origin.Y - v.Y;
}

//Cleanup for Move/Pan when mouse is released
private void ImageScrollArea_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    _DisplayImage.CaptureMouse();
    var tt = (TranslateTransform)((TransformGroup)_DisplayImage.RenderTransform).Children.First(tr => tr is TranslateTransform);
    start = e.GetPosition(border);
    origin = new Point(tt.X, tt.Y);
}

//Zoom according to the slider changes
private void _ImageZoomSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
    //Panel panel = _ImageScrollArea;
    Image panel = _DisplayImage;

    //Set the scale coordinates on the ScaleTransform from the slider
    ScaleTransform transform = (ScaleTransform)((TransformGroup)panel.RenderTransform).Children.First(tr => tr is ScaleTransform);
    transform.ScaleX = _slider.Value;
    transform.ScaleY = _slider.Value;


    //Set the zoom (this will affect rotate too) origin to the center of the panel
    panel.RenderTransformOrigin = new Point(0.5, 0.5);

    foreach (UIElement child in _ROICollectionCanvas.Children)
    {
        //Assume all shapes are contained in a panel
        Panel childPanel = child as Panel;

        var x = childPanel.Children;

        //Shape width and heigh should scale, but not StrokeThickness
        foreach (var shape in childPanel.Children.OfType<Shape>())
        {
            if (shape.Tag == null)
            {
                //Hack: This is be a property on a usercontrol in my solution
                shape.Tag = shape.StrokeThickness;
            }
            double orignalStrokeThickness = (double)shape.Tag;

            //Attempt to keep the underlying shape border/stroke from thickening as well
            double newThickness = shape.StrokeThickness - (orignalStrokeThickness / transform.ScaleX);

            shape.StrokeThickness -= newThickness;
        }
    }
}
}

El código debería funcionar en un proyecto y solución .NET 4.0 o 4.5, suponiendo que no haya errores de cortar / pegar.

¿Alguna idea? Las sugerencias son bienvenidas.