文章目录
当我们只要显示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 对象,则使用第二种方法。