zoukankan      html  css  js  c++  java
  • Halcon对象HImage三通道图像转换为C#中的Bitmap的终极解决方案

    时隔一年多,终于把这个问题解决掉了。现在回头看,这个问题其实并不复杂,当初处理不了,完全是因为缺乏经验,多看多写,可能就会在处理其他问题时看到解决困扰很久的问题的解决方法。
    原问题参阅:C# Halcon联合编程问题(二)
    原方案参考Halcon对象Hobject转换为.net对象Bitmap

    于一天后(3.20)更新:
    写完这篇第二天,突然想明白了之前没有搞明白的问题。
    原来的方案有问题的地方终于知道在哪里了。
    经测试发现,GetImagePointer3如果参数传递的是HTuple类型,可以看到返回的HTuple的type是Long,这种情况下,如果想要取HTuple指向的指针地址,显然是不应该用.I来取的,而应该用.L,这样一来,问题就解决了。包括如果用IntPtr,如果能接受unsafe的话,其实也不用把指向的地址中的值全都拷贝出来,直接用(byte*)IntPtr就是个地址,是和Scan0那个地方是一样的。当初别人用.I可能和平台有关系,别人可能是32位的,而我的是64位的。

    本文提供两种类似的解决方案,思路其实和原方案都是一致的,两种方案的区别在于前者经测试同样的图片,耗时约是后者的20倍-30倍,后者要用到unsafe,我是不太喜欢在C#中用unsafe,这个单词给人的感觉就不太安全。
    本文提供的解决方案与原来找到的解决方案最主要的不同,在于拿到图像灰度值的指针之后的操作,原方法转换为了byte*,而我的方法将指针所指向的数值复制到了byte数组中。
    上代码:

    // 读取图像
    HImage image = new HImage(@"0.png");
    // 获取存放r,g,b值的指针
    image.GetImagePointer3(out IntPtr r, out IntPtr g, out IntPtr b, out string type, out int w, out int h);
    byte[] red = new byte[w * h];
    byte[] green = new byte[w * h];
    byte[] blue = new byte[w * h];
    // 将指针指向地址的值取出来放到byte数组中
    Marshal.Copy(r, red, 0, w * h);
    Marshal.Copy(g, green, 0, w * h);
    Marshal.Copy(b, blue, 0, w * h);
    

    以下是两种方案的区别
    方案1:

    Bitmap bitmap = new Bitmap(w, h, PixelFormat.Format32bppRgb);
    Rectangle rect = new Rectangle(0, 0, w, h);
    BitmapData bitmapData = bitmap.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format32bppRgb);
    IntPtr bptr = bitmapData.Scan0;
    for (int i = 0; i < red.Length; i++)
    {
        Marshal.Copy(blue, i, bptr + i * 4, 1);
        Marshal.Copy(green, i, bptr + i * 4 + 1, 1);
        Marshal.Copy(red, i, bptr + i * 4 + 2, 1);
        Marshal.Copy(new byte[] { 255 }, 0, bptr + i * 4 + 3, 1);
    }
    bitmap.UnlockBits(bitmapData);
    

    方案2:

    Bitmap bitmap2 = new Bitmap(w, h, PixelFormat.Format32bppRgb);
    Rectangle rect2 = new Rectangle(0, 0, w, h);
    BitmapData bitmapData2 = bitmap2.LockBits(rect2, ImageLockMode.ReadWrite, PixelFormat.Format32bppRgb);
    unsafe
    {
        byte* bptr2 = (byte*)bitmapData2.Scan0;
        for(int i = 0; i < w * h;i ++)
        {
            bptr2[i * 4] = blue[i];
            bptr2[i * 4 + 1] = green[i];
            bptr2[i * 4 + 2] = red[i];
            bptr2[i * 4 + 3] = 255;
        }
    }
    bitmap2.UnlockBits(bitmapData2);
    

    两种方案都经过测试,可以获取到准确的图像数据。
    测试使用的图像大小为3072*2048,使用方案1耗时约250ms,使用方案2耗时约10ms。
    其实如果对图像格式没有特别的要求,Bitmap在new的时候完全可以使用Format24bppRgb而不是Format32bppRgb,这样遍历过程的耗时也会缩短很多,实测方案1可以缩短到不到200ms,方案2在10ms以内。
    果然还是直接指针操作要快很多啊,但是如果可以的话,我是真不想用unsafe。
    推测原来的方案是直接将HTuple.I转换为指针的时候出了毛病,但是因为对C/C++不太熟悉,暂时找不到不出错的写法。

  • 相关阅读:
    程序员式的幽默(灌水)
    你应该知道的
    WPF控件应用[0]
    WPF控件应用[2]
    C#调用Win32 的API函数User32.dll
    C#获取当前行号
    C#导入excel重写
    [转]wpf相关好资源
    使用C#和Excel进行报表开发-生成统计图Chart
    [转]用 delphi 创建一个服务程序
  • 原文地址:https://www.cnblogs.com/yutou2016/p/14558148.html
Copyright © 2011-2022 走看看