【10】大恒相机SDK C++开发 ——对相机采集的原图像数据IFrameData裁剪ROI 实时显示在pictureBox中,3种方法实现(效率不同)

发布于:2025-08-02 ⋅ 阅读:(13) ⋅ 点赞:(0)


当我们只要显示ROI区域的画面(显示局部细节),而不是相机整个画面时,就需要对图像数据进行截取。

对于工业相机原图像数据IFrameData 裁剪ROI,并不像 OpenCV中截取ROI那么简单, 下面提供3种方法实现(效率不同),并将ROI画面实时显示在pictureBox中,

1 在回调函数中实现

public IntPtr pBuffer = IntPtr.Zero;

 private void __OnFrameCallbackFun(object objUserParam, IFrameData objIFrameData)
{
    int channel = 0;
    try
    {
        if (null != objIFrameData)
        {
            //************************************************************
            //获取图像信息,为图像处理等功能做准备
            //*************************************************************
            int imgHeight = (int)objIFrameData.GetHeight();
            int imgWidth = (int)objIFrameData.GetWidth();


            //************************************************************
            // 对图像进行裁剪并显示在 PictureBox 中
            //*************************************************************
            Rectangle ROI = new Rectangle(1323, 212, 2200, 1500);

            //获取图像buffer
            GX_VALID_BIT_LIST emValidBits = GX_VALID_BIT_LIST.GX_BIT_0_7;
            emValidBits = __GetBestValudBit(objIFrameData.GetPixelFormat());

            // 判断图像是否成功接收
            if (GX_FRAME_STATUS_LIST.GX_FRAME_STATUS_SUCCESS == objIFrameData.GetStatus())
            {
                // 在关键部分的代码前加锁
                lock (this)
                {
                    if (m_bIsColor)
                    {
                        // 彩色图像
                        pBuffer = objIFrameData.ConvertToRGB24(emValidBits, GX_BAYER_CONVERT_TYPE_LIST.GX_RAW2RGB_NEIGHBOUR, false);//最后一个参数是否垂直翻转图像
                        channel = 3;
                    }
                    else
                    {
                        // 黑白图像
                        if (__IsPixelFormat8(objIFrameData.GetPixelFormat()))
                        {
                            pBuffer = objIFrameData.GetBuffer();
                        }
                        else
                        {
                            pBuffer = objIFrameData.ConvertToRaw8(emValidBits);
                        }

                        channel = 1;
                    }

                    PixelFormat format = new PixelFormat();
                    format = channel == 3 ? PixelFormat.Format24bppRgb : PixelFormat.Format8bppIndexed;//若3通道彩色图像,否则黑白图像

                    //创建ROI 空图像
                    Bitmap bitmap = new Bitmap(ROI.Width, ROI.Height, format);
                    BitmapData bmpData = bitmap.LockBits(new Rectangle(0, 0, ROI.Width, ROI.Height), ImageLockMode.WriteOnly, bitmap.PixelFormat);

                    int srcStride = imgWidth * channel;// 原图每行数据长度
                    int destStride = bmpData.Stride;// 新图每行数据长度
                    int srcStartX = (ROI.X - 1) * channel; // ROI 起始 X 坐标在原图数据中的位置(二维视角)
                    int srcStartY = ROI.Y - 1; // ROI 起始 Y 坐标在原图数据中的位置(二维视角)
                    int srcOffset = srcStartY * srcStride + srcStartX; // ROI 起始位置在原图数据中的偏移量(原图数据在内存中是一行,一维数组)
                    
                    unsafe
                    {
                        byte* srcPtr = (byte*)pBuffer + srcOffset;
                        byte* destPtr = (byte*)bmpData.Scan0;

                        for (int y = 0; y < ROI.Height; y++)
                        {
                            for (int x = 0; x < ROI.Width * channel; x++)
                            {
                                destPtr[x] = srcPtr[x];
                            }

                            srcPtr += srcStride;
                            destPtr += destStride;
                        }
                    }

                    bitmap.UnlockBits(bmpData);


                    // 在这里处理 bitmap 图像,下面是显示举例
                    m_bitmap = bitmap;//将其赋值给全局的m_bitmap,从而可以在外部调用
                    ShowProcessedImage(m_nOperateID, bitmap);//传给函数,进行处理显示,
                    //pictureBox1.Image = bitmap;//传给pictureBox1直接显示

                }
            }
        }
           m_objCFps.IncreaseFrameNum();
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

2 独立封装调用

2.1 获取图像宽、高、pBuffer、channel

        private void getImgInfo(IFrameData objIFrameData, ref IntPtr pBuffer)
        {
            try
            {
                if (null != objIFrameData)
                {

                    //获取图像宽高
                    SrcImgHeight = (int)objIFrameData.GetHeight();
                    SrcImgWidth = (int)objIFrameData.GetWidth();


                    //获取图像buffer
                    GX_VALID_BIT_LIST emValidBits = GX_VALID_BIT_LIST.GX_BIT_0_7;
                    emValidBits = __GetBestValudBit(objIFrameData.GetPixelFormat());

                    // 判断图像是否成功接收
                    if (GX_FRAME_STATUS_LIST.GX_FRAME_STATUS_SUCCESS == objIFrameData.GetStatus())
                    {
                        // 在关键部分的代码前加锁
                        lock (this)
                        {
                            if (colorFlag)
                            {
                                // 彩色图像
                                pBuffer = objIFrameData.ConvertToRGB24(emValidBits, GX_BAYER_CONVERT_TYPE_LIST.GX_RAW2RGB_NEIGHBOUR, false);//最后一个参数是否垂直翻转图像,true则翻转
                                channel = 3;
                            }
                            else
                            {
                                // 黑白图像
                                if (__IsPixelFormat8(objIFrameData.GetPixelFormat()))
                                {
                                    pBuffer = objIFrameData.GetBuffer();
                                }
                                else
                                {
                                    pBuffer = objIFrameData.ConvertToRaw8(emValidBits);
                                }

                                channel = 1;
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

2.2 内存图像数据截取ROI并显示

          private void getRoiBmpData(Rectangle ROI, IntPtr pBuffer, ref Bitmap ROI_bitmap)
        {
            try
            {
                if (null != pBuffer)
                { 
                    //判断像素格式
                    PixelFormat format = new PixelFormat();
                    // format = channels == 3 ? PixelFormat.Format24bppRgb : PixelFormat.Format8bppIndexed;//若3通道彩色图像,否则黑白图像
                    switch (channel)
                    {
                        case 1:
                            format = PixelFormat.Format8bppIndexed;
                            break;
                        case 3:
                            format = PixelFormat.Format24bppRgb;
                            break;
                        case 4:
                            format = PixelFormat.Format32bppArgb;
                            break;
                    }

                    // 在关键部分的代码前加锁
                    lock (this)
                    {
                        //创建ROI 空图像
                        Bitmap bitmap = new Bitmap(ROI.Width, ROI.Height, format);
                        BitmapData bmpData = bitmap.LockBits(new Rectangle(0, 0, ROI.Width, ROI.Height), ImageLockMode.WriteOnly, bitmap.PixelFormat);
        

                        int srcStride = SrcImgWidth * channel;// 原图每行数据长度
                        int destStride = bmpData.Stride;// 新图每行数据长度
                        int srcStartX = (ROI.X - 1) * channel; // ROI 起始 X 坐标在原图数据中的位置(二维视角)
                        int srcStartY = ROI.Y - 1; // ROI 起始 Y 坐标在原图数据中的位置(二维视角)
                        int srcOffset = srcStartY * srcStride + srcStartX; // ROI 起始位置在原图数据中的偏移量(原图数据在内存中是一行,一维数组)

                        unsafe
                        {
                            byte* srcPtr = (byte*)pBuffer + srcOffset;
                            byte* destPtr = (byte*)bmpData.Scan0;

                            for (int y = 0; y < ROI.Height; y++)
                            {
                                for (int x = 0; x < ROI.Width * channel; x++)
                                {
                                    destPtr[x] = srcPtr[x];
                                }

                                srcPtr += srcStride;
                                destPtr += destStride;
                            }
                        }

                        bitmap.UnlockBits(bmpData);

                        // 在这里处理 bitmap 图像
                        //ShowProcessedImage(m_nOperateID, bitmap);
                        ROI_bitmap = bitmap;
                    }
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

2.3 回调函数调用


       public static int SrcImgHeight, SrcImgWidth;
       int channel = 0;

 
       bool isShowSrcImg = true;
        bool camImg_isProcess = false; //是否图像处理,不执行图像处理的时候,默认开启预览模式。
        bool isPreviewRoiImg = false; //是否图像处理,不执行图像处理的时候,默认开启预览模式。
        Rectangle ROI = new Rectangle(1323, 212, 2200, 1500);
        Bitmap bitmapB6;

代码中未出现的变量为 全局变量,或者库中的变量

        private void __OnFrameCallbackFun_1(object objUserParam, IFrameData objIFrameData)
        {
            try
            {
                if (isShowSrcImg) //当开始处理图像时原视频要暂停,否则buffer中的数据会变化,图像上下颠倒交替出现(因Show(objIFrameData)中显示实现对图像数据做了垂直翻转)
                {
                    //************************************************************
                    //显示相机获取的原图
                    //************************************************************                 
                    m_objGxBitmap1.Show(objIFrameData);
                }

                //获取图像宽、高、pBuffer、channel等信息
                getImgInfo2(objIFrameData, ref pBuffer1);


                //************************************************************
                // 对图像进行裁剪并显示在 PictureBox 中
                //*************************************************************
                if (isPreviewRoiImg)// 开启预览模式。
                {
                    getRoiBmpData2(ROI, pBuffer1, ref bitmapB6);

                    //ImageShow1.Image = bitmapB6;
                }

                //统计帧率
                m_objCFps1.IncreaseFrameNum();
            }
            catch (Exception ex)
            {
                MessageBox.Show("回调函数2" + ex.Message);
            }
        }

3 for循环嵌套 方法2

        private void __OnFrameCallbackFun(object objUserParam, IFrameData objIFrameData)
        {
            try
            {
                if (null != objIFrameData)
                {
                    // 获取图像信息
                    int srcImgWidth = (int)objIFrameData.GetWidth();
                    int srcImgHeight = (int)objIFrameData.GetHeight();

                    GX_VALID_BIT_LIST emValidBits = GX_VALID_BIT_LIST.GX_BIT_0_7;
                    emValidBits = __GetBestValudBit(objIFrameData.GetPixelFormat());

                    // 判断图像是否成功接收
                    if (GX_FRAME_STATUS_LIST.GX_FRAME_STATUS_SUCCESS == objIFrameData.GetStatus())
                    {
                        // 在关键部分的代码前加锁
                        lock (this)
                        {
                            IntPtr pBuffer = IntPtr.Zero;
                            if (m_bIsColor)
                            {
                                // 彩色图像
                                pBuffer = objIFrameData.ConvertToRGB24(emValidBits, GX_BAYER_CONVERT_TYPE_LIST.GX_RAW2RGB_NEIGHBOUR, false);
                            }
                            else
                            {
                                // 黑白图像
                                if (__IsPixelFormat8(objIFrameData.GetPixelFormat()))
                                {
                                    pBuffer = objIFrameData.GetBuffer();
                                }
                                else
                                {
                                    pBuffer = objIFrameData.ConvertToRaw8(emValidBits);
                                }
                            }

                            // 创建ROI Bitmap
                            Bitmap bitmap = new Bitmap(ROI.Width, ROI.Height, PixelFormat.Format24bppRgb);
                            BitmapData bmpData = bitmap.LockBits(new Rectangle(0, 0, ROI.Width, ROI.Height), ImageLockMode.WriteOnly, bitmap.PixelFormat);

                            // 计算每行字节数
                            int stride = bmpData.Stride;
                            int bytes = Math.Abs(stride) * bitmap.Height;

                            // 创建一个 byte 数组来存储图像数据
                            byte[] imageData = new byte[bytes];

                            // 计算ROI在原始图像中的偏移位置
                            int roiOffsetX = ROI.X;
                            int roiOffsetY = ROI.Y;

                            // 将 pBuffer 中的图像数据复制到数组中,考虑ROI在原始图像中的偏移位置
                            for (int y = 0; y < ROI.Height; y++)
                            {
                                int srcY = roiOffsetY + y;
                                int srcIndex = srcY * srcImgWidth * 3; // 假设是24位RGB图像

                                for (int x = 0; x < ROI.Width; x++)
                                {
                                    int srcX = roiOffsetX + x;
                                    int dstIndex = (y * ROI.Width + x) * 3;

                                    imageData[dstIndex] = Marshal.ReadByte(pBuffer, srcIndex + srcX * 3);
                                    imageData[dstIndex + 1] = Marshal.ReadByte(pBuffer, srcIndex + srcX * 3 + 1);
                                    imageData[dstIndex + 2] = Marshal.ReadByte(pBuffer, srcIndex + srcX * 3 + 2);
                                }
                            }

                            // 复制图像数据到 Bitmap 中
                            Marshal.Copy(imageData, 0, bmpData.Scan0, bytes);

                            bitmap.UnlockBits(bmpData);

                            // 显示图像
                            ShowProcessedImage(m_nOperateID, bitmap);
                        }
                    }
                }

                m_objCFps.IncreaseFrameNum();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

4 for循环嵌套 方法3

        private void __OnFrameCallbackFun(object objUserParam, IFrameData objIFrameData)
        {
            try
            {
                if (null != objIFrameData)
                {
                    // 获取图像信息
                    SrcImgWidth = (int)objIFrameData.GetWidth();
                    SrcImgHeight = (int)objIFrameData.GetHeight();

                    GX_VALID_BIT_LIST emValidBits = GX_VALID_BIT_LIST.GX_BIT_0_7;
                    emValidBits = __GetBestValudBit(objIFrameData.GetPixelFormat());

                    PixelFormat format;

                    // 判断图像是否成功接收
                    if (GX_FRAME_STATUS_LIST.GX_FRAME_STATUS_SUCCESS == objIFrameData.GetStatus())
                    {
                        // 在关键部分的代码前加锁
                        lock (this)
                        {
                            IntPtr pBuffer = IntPtr.Zero;
                            int channel;

                            if (m_bIsColor)
                            {
                                // 彩色图像
                                pBuffer = objIFrameData.ConvertToRGB24(emValidBits, GX_BAYER_CONVERT_TYPE_LIST.GX_RAW2RGB_NEIGHBOUR, false); // 最后一个参数是否垂直翻转图像,true则翻转
                                channel = 3;
                            }
                            else
                            {
                                // 黑白图像
                                if (__IsPixelFormat8(objIFrameData.GetPixelFormat()))
                                {
                                    pBuffer = objIFrameData.GetBuffer();
                                }
                                else
                                {
                                    pBuffer = objIFrameData.ConvertToRaw8(emValidBits);
                                }

                                channel = 1;
                            }

                            // 判断像素格式
                            format = channel == 3 ? PixelFormat.Format24bppRgb : PixelFormat.Format8bppIndexed;

                            // 创建ROI 空图像
                            m_bitmap = new Bitmap(ROI.Width, ROI.Height, format);
                            BitmapData bmpData = m_bitmap.LockBits(new Rectangle(0, 0, ROI.Width, ROI.Height), ImageLockMode.WriteOnly, m_bitmap.PixelFormat);

                            int srcStride = SrcImgWidth * channel; // 原图每行数据长度
                            int destStride = bmpData.Stride;       // 新图每行数据长度
                            int srcStartX = (ROI.X - 1) * channel; // ROI 起始 X 坐标在原图数据中的位置(二维视角)
                            int srcStartY = ROI.Y - 1;             // ROI 起始 Y 坐标在原图数据中的位置(二维视角)
                            int srcOffset = srcStartY * srcStride + srcStartX; // ROI 起始位置在原图数据中的偏移量(原图数据在内存中是一行,一维数组)

                            // 将 pBuffer 中的图像数据复制到数组中,考虑ROI在原始图像中的偏移位置
                            for (int y = 0; y < ROI.Height; y++)
                            {
                                int srcY = srcStartY + y;
                                int srcIndex = srcOffset + srcY * srcStride;

                                for (int x = 0; x < ROI.Width * channel; x++)
                                {
                                    int srcX = srcStartX + x;
                                    int dstIndex = y * destStride + x;

                                    // 复制像素数据
                                    Marshal.WriteByte(bmpData.Scan0, dstIndex, Marshal.ReadByte(pBuffer, srcIndex + x));
                                }
                            }

                            m_bitmap.UnlockBits(bmpData);

                            // 显示图像
                            ShowProcessedImage(m_nOperateID, m_bitmap);
                        }

                    }
                    m_objCFps.IncreaseFrameNum();
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

5 按行复制数据提高效率,但很耗内存

        private unsafe void __OnFrameCallbackFun111(object objUserParam, IFrameData objIFrameData)
        {
            try
            {
                if (null != objIFrameData)
                {
                    // 获取图像信息
                    SrcImgWidth = (int)objIFrameData.GetWidth();
                    SrcImgHeight = (int)objIFrameData.GetHeight();

                    GX_VALID_BIT_LIST emValidBits = GX_VALID_BIT_LIST.GX_BIT_0_7;
                    emValidBits = __GetBestValudBit(objIFrameData.GetPixelFormat());

                    PixelFormat format;

                    // 判断图像是否成功接收
                    if (GX_FRAME_STATUS_LIST.GX_FRAME_STATUS_SUCCESS == objIFrameData.GetStatus())
                    {
                        // 在关键部分的代码前加锁
                        lock (this)
                        {
                            if (m_bIsColor)
                            {
                                // 彩色图像
                                pBuffer = objIFrameData.ConvertToRGB24(emValidBits, GX_BAYER_CONVERT_TYPE_LIST.GX_RAW2RGB_NEIGHBOUR, false); // 最后一个参数是否垂直翻转图像,true则翻转
                                channel = 3;
                            }
                            else
                            {
                                // 黑白图像
                                if (__IsPixelFormat8(objIFrameData.GetPixelFormat()))
                                {
                                    pBuffer = objIFrameData.GetBuffer();
                                }
                                else
                                {
                                    pBuffer = objIFrameData.ConvertToRaw8(emValidBits);
                                }

                                channel = 1;
                            }

                            // 判断像素格式
                            format = channel == 3 ? PixelFormat.Format24bppRgb : PixelFormat.Format8bppIndexed;
                            // 创建ROI 空图像
                            m_bitmap = new Bitmap(ROI.Width, ROI.Height, format);
                            BitmapData bmpData = m_bitmap.LockBits(new Rectangle(0, 0, ROI.Width, ROI.Height), ImageLockMode.WriteOnly, m_bitmap.PixelFormat);
                          
                            int srcStride = SrcImgWidth * channel; // 原图每行数据长度  
                            int destStride = bmpData.Stride;// 新图每行数据长度
                            int srcStartX = (ROI.X - 1) * channel; // ROI 起始 X 坐标在原图数据中的位置(二维视角)      
                            int srcStartY = ROI.Y - 1; // ROI 起始 Y 坐标在原图数据中的位置(二维视角)
                            // ROI 起始位置在原图数据中的偏移量(原图数据在内存中是一行,一维数组)
                            int srcOffset = srcStartY * srcStride + srcStartX;

                            unsafe
                            {
                                // 计算 ROI 区域总字节数
                                //int bytes = Math.Abs(destStride) * m_bitmap.Height;
                               
                                byte* bmpPtr = (byte*)bmpData.Scan0; // 指向 ROI Bitmap 的扫描行指针                        
                                byte* srcPtr = (byte*)pBuffer + srcOffset;    // 指向原始图像数据的指针                         
                                for (int y = 0; y < ROI.Height; y++)  // 逐行复制ROI区域的图像数据
                                {
                                    // 复制当前行的图像数据到ROI Bitmap中
                                    //从源地址 srcPtr开始,每次复制destStride个字节(一整行的数据)到目标地址 bmpPtr 处,第四个参数偏移量
                                    Buffer.MemoryCopy(srcPtr, bmpPtr, destStride, destStride);
                                    bmpPtr += destStride;
                                    srcPtr += srcStride;
                                }
                            }

                            m_bitmap.UnlockBits(bmpData);

                            // 显示图像
                            ShowProcessedImage(m_nOperateID, m_bitmap);
                        }
                    }

                    m_objCFps.IncreaseFrameNum();
                }
          
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

6 unsafe代码 解释及注意事项 看我另一篇文章

7 ConvertToRGB24详细解释 、示例、注意事项 看我另一篇文章

8 问题与反思

C# 自定义函数getRoiBmpData,该函数功能“对相机获取到的 IFrameData 转换为pBuffer ,然后进行ROI裁剪操作,将ROI图像bitmap传给ShowProcessedImage函数进行处理”。getRoiBmpData函数被5个回调函数同时调用,因为有5个相机,每个相机调用一个回调函数。那么我为什么5个相机要用5个回调呢,因为这样每个回调函数只负责一个相机,这样看起来分工更明确。

现在有两个问题:
问题1.这行代码“ Bitmap bitmap = new Bitmap(ROI.Width, ROI.Height, format);”中bitmap 被反复创建和使用,在该函数中需要手动释放吗?

问题2.这行代码“ Bitmap bitmap = new Bitmap(ROI.Width, ROI.Height, format);”中bitmap 被反复创建和使用,那么我可以创建一个全局变量Bitmap bitmap ,这样就不用每次进入函数都会创建一次bitmap ,但是呢我担心5个回调函数共享这一个bitmap 会冲突吗?当然ShowProcessedImage函数和getRoiBmpData函数也只有一个,都是同时被5个回调函数调用的。

8.1 被反复创建和使用,需手动释放吗?

在这段代码中,虽然 bitmap 被反复创建和使用,但在每次使用后都被正确地释放了。这是因为在使用完毕后调用了 bitmap.UnlockBits(bmpData); 来释放 BitmapData 对象。所以不需要手动释放 bitmap,因为在 bitmap 超出范围时会被自动回收。

bitmap 对象是在函数内部创建的,它会在函数执行完毕后自动离开作用域,从而被垃圾回收机制回收。因为它的生命周期受到函数作用域的限制,一旦函数执行完毕就会被销毁。

8.2 创建一个全局Bitmap bitma,多线程访问会冲突吗?

问题2:创建一个全局变量 Bitmap bitmap 是一种有效的方法,可以避免在每次函数调用时都重新创建 bitmap 对象。然而,如果多个回调函数同时访问和修改同一个全局 bitmap 对象,就可能会发生竞态条件或数据竞争,导致程序行为不确定或产生错误。因此,你需要确保在对 bitmap 进行读写操作时进行适当的线程同步,以避免冲突。

为了解决这个问题,可以采取以下方法之一

  • 在访问全局 bitmap 对象时使用线程同步机制(如锁),以确保在任何时候只有一个回调函数可以访问或修改 bitmap 对象。这样可以避免并发访问导致的问题。一个简单的方法是在访问 bitmap 之前使用 lock 关键字来确保线程安全,就像你在代码中对关键部分加锁一样。这样可以确保每次只有一个线程能够访问 bitmap,从而避免并发冲突。

  • 将 bitmap 对象作为函数参数传递给每个回调函数,这样每个函数都有自己的 bitmap 对象实例,不会相互干扰。

选择哪种方法取决于你的具体需求和代码结构。如果需要在多个回调函数之间共享相同的 bitmap 对象,并且需要确保线程安全,则使用第一种方法;如果每个回调函数都需要独立的 bitmap 对象,则使用第二种方法。


网站公告

今日签到

点亮在社区的每一天
去签到