C#+Halcon 垂直拼接/裁切避免频繁开辟内存

背景

在做一些线扫相机且进行连续拍摄的项目时,由于图像扫描的随机性,部分场景下需要对图像进行拼接和裁切,获取完整的一个图像。由于halcon中 crop相关的算子都是开辟新的内存方式,为了避免内存重复开辟,此处提供一个逻辑方式如下:

优点:

1)不需要每次都去新建内存,每次只需要做mem_copy的动作,减少耗时

2)在完整图像中查找特征,避免特征由于随机性拍照不完整而丢失

缺点:

1)如果直接复用指针,可能会导致拼接输出图像频率>检测频率,即缓存[0]的图片输出后,但检测未完成,此处队列循环又来到了缓存[0],最终导致原本的缓存[0]图像被覆盖,图像信息丢失。解决方式:可以修改为新建内存输出,会浪费一些cpu和内存

内存操作相关代码:

cs 复制代码
 public static class IntPtrHelper
    {
        /// <summary>
        /// 拷贝指针数据
        /// </summary>
        /// <param name="dest"></param>
        /// <param name="src"></param>
        /// <param name="count"></param>
        [DllImport("kernel32.dll", EntryPoint = "CopyMemory", SetLastError = false)]
        public static extern void CopyMemory(IntPtr dest, IntPtr src, uint count);


        /// <summary>
        /// 指针数据集重置
        /// </summary>
        /// <param name="dest"></param>
        /// <param name="c"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        [DllImport("msvcrt.dll", EntryPoint = "memset", CallingConvention = CallingConvention.Cdecl, SetLastError = false)]
        public static extern IntPtr MemSet(IntPtr dest, int c, int count);


        /// <summary>
        /// 从字节中拷贝数据至指针中
        /// </summary>
        /// <param name="destination"></param>
        /// <param name="sourcesBytes"></param>
        /// <param name="startIndex"></param>
        /// <param name="length"></param>
        public static void CopyFromBytes(this IntPtr destination, byte[] sourcesBytes,int startIndex,int length)
        {
            Marshal.Copy(sourcesBytes, startIndex, destination, length);
        }


        /// <summary>
        /// 获取指针内指定位置的内容的字节
        /// </summary>
        /// <param name="sourcePtr"></param>
        /// <param name="sourceOffset"></param>
        /// <param name="length"></param>
        /// <returns></returns>
        public static byte[] ToBytes(this IntPtr sourcePtr,int sourceOffset,int length)
        {
            byte[] rlt = new byte[length];
            Marshal.Copy(sourcePtr.Offset(sourceOffset), rlt, 0, length);
            return rlt;
        }


        /// <summary>
        /// 从一个指针的数据拷贝到另一个指针中
        /// </summary>
        /// <param name="source">数据源指针</param>
        /// <param name="destination">目标指针</param>
        /// <param name="sourceOffset">数据源指针偏移值</param>
        /// <param name="destinationOffset">目标指针偏移值</param>
        /// <param name="length">拷贝长度</param>
        public static void Copy(IntPtr source, IntPtr destination, int sourceOffset, int destinationOffset, int length)
        {
            IntPtr destPtr = destination.Offset(destinationOffset),
                srcPtr = source.Offset(sourceOffset);

            CopyMemory(destPtr, srcPtr, (uint)length);

        }


        /// <summary>
        /// 指针偏移指定长度
        /// </summary>
        /// <param name="sourcePrt"></param>
        /// <param name="offset"></param>
        /// <returns></returns>
        public static IntPtr Offset(this IntPtr sourcePrt, int offset)
        {
            IntPtr rltPtr;
            if (IntPtr.Size == sizeof(long))
            {
                rltPtr = new IntPtr(sourcePrt.ToInt64() + offset);
            }
            else
            {
                rltPtr = new IntPtr(sourcePrt.ToInt32() + offset);
            }
            return rltPtr;
        }


        /// <summary>
        /// 拷贝源指针指定位置的内容,返回新的指针.注意:此处的指针需要自己释放,不纳入内存管理
        /// </summary>
        /// <param name="source"></param>
        /// <param name="offset"></param>
        /// <param name="length"></param>
        /// <returns></returns>
        public static IntPtr Copy(IntPtr source, int offset, int length)
        {
            IntPtr ptr = Marshal.AllocHGlobal(length);
            Copy(source, ptr, offset, 0, length);
            return ptr;
        }
    }

一、循环队列

设置根据图片 宽度/高度/通道数/缓存个数 创建一个循环队列,每次在完整一张图片拼接截取后,自动移到下一张图片,假设缓存个数为3,则就是 [0,1,2,0,1,2,0....]这样无限循环取图片缓存进行内存拷贝覆盖,代码如下

cs 复制代码
  /// <summary>
        /// 拷贝图像
        /// </summary>
        /// <param name="source">原始图像</param>
        /// <param name="destination">需拷贝到的图像位置</param>
        /// <param name="sourceOffset"></param>
        /// <param name="destinationOffset"></param>
        /// <param name="length"></param>
        /// <param name="imageType"></param>
        /// <param name="imageChannel"></param>
        public static void Copy(HImage source, HImage destination, int sourceOffset, int destinationOffset, int length,emImageType imageType, emVerticalImageChannel imageChannel)
        {
            KeyValuePair<IntPtr, IntPtr>[] ptrParis = null;
            if(imageChannel == emVerticalImageChannel.one)
            {
                //单通道
                IntPtr ptrSrc = source.GetImagePointer1(out string _, out int width, out int height);
                IntPtr ptrDestination = destination.GetImagePointer1(out string _, out width, out height);
                ptrParis = new KeyValuePair<IntPtr, IntPtr>[1] {
                     new KeyValuePair<IntPtr, IntPtr>(ptrSrc,ptrDestination)
                };
            }
            else
            {
                //三通道
                source.GetImagePointer3(out IntPtr ptr1Src, out IntPtr ptr2Src, out IntPtr ptr3Src, out string _, out int width, out int height);
                destination.GetImagePointer3(out IntPtr ptr1Destination, out IntPtr ptr2Destination, out IntPtr ptr3Destination, out string _, out width, out height);

                ptrParis = new KeyValuePair<IntPtr, IntPtr>[3] {
                    new KeyValuePair<IntPtr, IntPtr>(ptr1Src,ptr1Destination),
                    new KeyValuePair<IntPtr, IntPtr>(ptr2Src,ptr2Destination),
                    new KeyValuePair<IntPtr, IntPtr>(ptr3Src,ptr3Destination),
                };
            }

            int pixelByteNumber = imageType.GetPixelByteNumber(); //获取图片类型一个像素对应的字节数

            foreach(KeyValuePair<IntPtr,IntPtr> kvp in ptrParis)
            {
                //循环拷贝
                IntPtrHelper.Copy(kvp.Key, kvp.Value, sourceOffset* pixelByteNumber, destinationOffset* pixelByteNumber, length* pixelByteNumber);
            }
        }

二、查找特征确定行起始和行终点

此处需要自己实现算法,最终目的就是找到你要的图片的起始行坐标和终止行坐标

三、输出图像

有了起始行坐标和终止行坐标后,我们只需要拿出对应的内存指针,调用GenImage1Extern或GenImage3Extern进行图片生成即可。需要注意的是要用HImage2来使用图像,因为假设原始那张缓存图片被释放掉,那么就会出现访问野指针的情况,毁灭性打击。

cs 复制代码
        public class HImage2:HImage
        {
            /// <summary>
            /// 引用的图像
            /// </summary>
            public HImage ReferenceData { get; set; }

            public HImage2(HImage referenceImage, HObject hObject):base(hObject)
            {
                //智能指针,不需要拷贝内存新建的问题,此处避免由于缓存中图像释放导致此处访问野指针的问题
                ReferenceData = new HImage(referenceImage);
            }

            public override void Dispose()
            {
                base.Dispose();

                ReferenceData?.Dispose();
                ReferenceData = null;
            }
        }

        /// <summary>
        /// 根据垂直方向的位置的长度,进行图像裁切,且图像引用原来的指针
        /// </summary>
        /// <param name="image"></param>
        /// <param name="top">起始行坐标</param>
        /// <param name="height">高度</param>
        /// <returns></returns>
        public static HImage2 CropVerticalReference(this HImage image,int top,int height)
        {
            int channel = image.CountChannels().I;
            image.GetImageSize(out int widthSrc, out int heightSrc);
            if(top+height>heightSrc)
            {
                throw new Exception($"裁切终止行为{top + height},超出图像高度{heightSrc}");
            }
            if(channel == 1)
            {
                IntPtr ptr = image.GetImagePointer1(out string type, out  widthSrc, out  heightSrc);
                emImageType typeEm = (emImageType)Enum.Parse(typeof(emImageType), type, true);
                int scale = typeEm.GetPixelByteNumber();

                IntPtr offsetPtr = ptr.Offset(scale * top * widthSrc);

                HOperatorSet.GenImage1Extern(out HObject temp, type, widthSrc, height, offsetPtr, 0);

                HImage2 rlt = new HImage2(image, temp);
                temp.Dispose();
                return rlt;

            }
            else if(channel == 3)
            {
                image.GetImagePointer3(out IntPtr pRed, out IntPtr pGreen, out IntPtr pBlue, out string type, out widthSrc, out heightSrc);
                emImageType typeEm = (emImageType)Enum.Parse(typeof(emImageType), type, true);
                int scale = typeEm.GetPixelByteNumber();

                IntPtr offsetPtrRed = pRed.Offset(scale * top * widthSrc);
                IntPtr offsetPtrGreen = pGreen.Offset(scale * top * widthSrc);
                IntPtr offsetPtrBlue = pBlue.Offset(scale * top * widthSrc);

                HOperatorSet.GenImage3Extern(out HObject temp, type, widthSrc, height, offsetPtrRed, offsetPtrGreen, offsetPtrBlue, 0);
                HImage2 rlt = new HImage2(image, temp);
                temp.Dispose();
                return rlt;
            }
            else
            {
                throw new Exception($"不支持通道数为{channel}的图像裁剪.");
            }
        }

总结

该方式适用于大图像的垂直方向拼接并截取检测,减少内存消化,同时避免野指针的问题。

相关推荐
美狐美颜sdk1 小时前
美颜SDK架构揭秘:人脸美型API的底层实现与优化策略
图像处理·人工智能·深度学习·架构·视频美颜sdk·美颜api
初级代码游戏2 小时前
MAUI(C#)安卓开发起步
android·开发语言·c#·hyper-v·maui·haxm·aehd
Nita.5 小时前
设计模式|策略模式 Strategy Pattern 详解
设计模式·c#·策略模式
WangMing_X7 小时前
C#实现语音合成播报器——基于System.Speech的语音交互方案,在windows上实现语音播报指定文本
c#·语音识别·语音播报
“抚琴”的人8 小时前
C#—csv文件格式操作实例【在winform表格中操作csv】
开发语言·c#
MF_AI8 小时前
颈椎X光数据集(cervical spine X-ray dataset)
图像处理·人工智能·深度学习·yolo·计算机视觉·spine
幻想趾于现实8 小时前
VisionPro、VisionMaster 多模板匹配分类(大球刀、小球刀、尖刀、三角刀)
开发语言·图像处理·机器学习·visionmaster·visionpro
我不是程序猿儿9 小时前
【C#】检查已有窗口,防止重复打开
开发语言·c#
shepherd枸杞泡茶9 小时前
.NET 多线程 C# 多线程 持续更新、完善、进化
c#·.net