Lib Bridge | Cvb Image to Emgu Mat

I would be most grateful if anyone could provide some C# emgu sample code regarding accessing cvb snap image in the form of an EMGU Cv.Mat.

Is there not some lib agnostic way of doing this? For example, if cvb could stream a snap into a memory buffer encoded as a PNG or bitmap, other libs could pic that up in an agnostic way, for example imdecode.
In this way we could save to memory in much the same way you can save to disk from one lib and load to disk from another lib and all the problems are taken care of.

I haven’t been having much success with trying to model the C# library bridge on the C++ lib bridge, or by adapting ideas found on the forum for Cvb pixel access.
I haven’t used C# much since 2011, which doesn’t help. The legacy application I am trying to modify is also based on an older version of Cvb, before the number of dlls was consolidated down.

  1. I first tried to see if I could get the basic types right, following the core essentials of the C++ style no_copy bridge without any checking (as I know it is 1 channel greyscale to greyscale):
        static unsafe Emgu.CV.Mat bridge(Cvb.Image.IMG source, Emgu.CV.Mat mat)
        {
            int width = Cvb.Image.ImageWidth(source);
            int height = Cvb.Image.ImageHeight(source);
           
            Size size;
            size.Width = width;
            size.Height = height;
 
            IntPtr sourceBase;
            int sourceYInc;
            int sourceXInc;

            MIplImage retval =  CvInvoke.cvCreateImageHeader(size, Emgu.CV.CvEnum.IplDepth.IplDepth_8U, 1);
           
            Cvb.Utilities.GetLinearAccess(source, 0, out sourceBase, out sourceXInc, out sourceYInc);

            CvInvoke.cvSetData(ref retval, ref sourceBase, ref sourceYInc); // << - tried a variety of approaches here.
  1. I also tried accessing the individual pixels of the cvb image, and using that data to reconstruct an image in the Emgu library.
    It seems to work for the first few thousand pixels but ultimately crashed my debugger application itself. Very slow up to that point.
        //   static unsafe void CopyPixelsWithValue(Cvb.Image.IMG source,  Emgu.CV.Mat  mat)
        static unsafe Emgu.CV.Mat CopyPixelsWithValue(Cvb.Image.IMG source, Emgu.CV.Mat mat)
        {
      
            int width = Cvb.Image.ImageWidth(source);
            int height = Cvb.Image.ImageHeight(source);
            IntPtr sourceBase;
            int sourceYInc;
            int sourceXInc;

            Cvb.Utilities.GetLinearAccess(source, 0, out sourceBase, out sourceXInc, out sourceYInc);

            for (int y = 0; y < height; y++)
            {
                byte* pSrcLine = (byte*)sourceBase + y * sourceYInc;

                for (int x = 0; x < width; x++)
                {
                    var srcVal = *(pSrcLine + x * sourceXInc);

                    Matrix<byte> mask = new Matrix<byte>(2056, 2464);
                    mask.Data[y, x] = 1;
                    mat.SetTo(new MCvScalar(srcVal), mask);
                }

            }
            return mat;
        }

Any help much appreciated!

Hi @vizion,

sorry for the late reply. First, I would recommend the object oriented wrappers of :cvb: 13.02.000 and higher. With these it gets much simpler. If you have a :cvb: acquisition loop you can convert them to Cv.Mats as follows (helper class below):

var stream = device.Stream;
stream.Start();
try
{
  while (acquiring)
  {
    using (var acqImage = stream.Wait())
    {
      var acqMat = acqImage.ToMat();

      // do your OpenCV processing here
    }
  }
}
finally
{
  stream.Abort();
}

See here for a quick tutorial on the new API:

Getting Started with CVB.Net

Programming Questions.Net API

csharp tutorial

Here is what you need for the .ToMat() extension method:

public enum SwapChannels
{
  No,
  Yes
}

public static class CvbEmguExtensions
{
  public static Mat ToMat(this Image image, SwapChannels swap = SwapChannels.Yes)
  {
    if (image == null)
      throw new ArgumentNullException(nameof(image));

    if (swap == SwapChannels.Yes)
    {
      using (var swapped = Image.FromPlanes(MappingOption.LinkPixels, image.Planes.Reverse().ToArray()))
      {
        return swapped.ToMat(SwapChannels.No);
      }
    }

    var mat = new Mat(image.Height, image.Width, image.GetDepthType(), image.Planes.Count);
    try
    {
      image.CopyTo(mat);
      return mat;
    }
    catch
    {
      mat.Dispose();
      throw;
    }
  }

  public static DepthType GetDepthType(this Image image)
  {
    if (image == null)
      throw new ArgumentNullException(nameof(image));
    if (!image.Planes.DataTypesIdentical)
      throw new ArgumentException("Differing data types per plane not supported");

    var dataType = image.Planes[0].DataType;
    var bitsPerChannel = dataType.BitsPerPixel;

    if (dataType.IsUnsignedInteger)
    {
      if (bitsPerChannel <= 8)
        return DepthType.Cv8U;
      else if (bitsPerChannel <= 16)
        return DepthType.Cv16U;
    }
    else if (dataType.IsSignedInteger)
    {
      if (bitsPerChannel <= 8)
        return DepthType.Cv8S;
      else if (bitsPerChannel <= 16)
        return DepthType.Cv16S;
      else if (bitsPerChannel <= 32)
        return DepthType.Cv32S;
    }
    else if (dataType.IsFloat)
    {
      if (bitsPerChannel == 32)
        return DepthType.Cv32F;
      else if (bitsPerChannel == 64)
        return DepthType.Cv64F;
    }

    throw new NotSupportedException($"{dataType} not supported by Cv.Mat");
  }

  public static void CopyTo(this Image image, Mat mat)
  {
    if (image == null)
      throw new ArgumentNullException(nameof(image));
    if (mat == null)
      throw new ArgumentNullException(nameof(mat));

    using (var target = mat.AsWrappedCvbImage())
    {
      image.CopyTo(target);
    }
  }

  public static WrappedImage AsWrappedCvbImage(this Mat mat)
  {
    var planeStride = mat.GetBitDepth() / 8;
    var pixelStride = planeStride * mat.NumberOfChannels;

    return new WrappedImage
    (
      mat.DataPointer, 0, mat.Width, mat.Height,
      mat.GetPixelDataType(), mat.GetBitDepth(),
      pixelStride, mat.Step, planeStride, Enumerable.Range(0, mat.NumberOfChannels).ToArray()
    );
  }

  public static PixelDataType GetPixelDataType(this Mat mat)
  {
    if (mat == null)
      throw new ArgumentNullException(nameof(mat));

    switch (mat.Depth)
    {
      default:
      case DepthType.Default:
        throw new InvalidOperationException("Invalid bit depth");
      case DepthType.Cv8U:
      case DepthType.Cv16U:
        return PixelDataType.UInt;
      case DepthType.Cv8S:
      case DepthType.Cv16S:
      case DepthType.Cv32S:
        return PixelDataType.Int;
      case DepthType.Cv32F:
      case DepthType.Cv64F:
        return PixelDataType.Float;
    }
  }

  public static int GetBitDepth(this Mat mat)
  {
    if (mat == null)
      throw new ArgumentNullException(nameof(mat));

    switch (mat.Depth)
    {
      default:
      case DepthType.Default:
        throw new InvalidOperationException("Invalid bit depth");
      case DepthType.Cv8U:
      case DepthType.Cv8S:
        return 8;
      case DepthType.Cv16U:
      case DepthType.Cv16S:
        return 16;
      case DepthType.Cv32S:
      case DepthType.Cv32F:
        return 32;
      case DepthType.Cv64F:
        return 64;
    }
  }
}

The swap is necessary as :cvb: always treats plane 0 as red. OpenCV loads RGB data as BGR (thus channel 0 is blue). Hope this helps.

Merry Christmas :santa:

P.S.: the implementation here always copies. You can also sometimes create a Cv.Mat view on another buffer. If you are interested in it, I will also write it :wink:.

2 Likes

4 posts were split to a new topic: Lib Bridge | CVB to OpenCV