文章目录
- [1 问题描述](#1 问题描述)
- [1 Form2调用Form1的公共Bitmap------显示](#1 Form2调用Form1的公共Bitmap——显示)
-
- [1.1 Form1代码实现](#1.1 Form1代码实现)
- [1.2 Form2代码实现](#1.2 Form2代码实现)
- [2 使用事件触发](#2 使用事件触发)
-
- [2.1 Form1 代码实现](#2.1 Form1 代码实现)
- [2.2 Form2代码实现](#2.2 Form2代码实现)
1 问题描述
在主界面 Form1中,多个相机采集的图像 经过处理后,实时显示 在Form2界面的 pictureBox中;共有8个区域的图像要实时显示。
此功能以下称为预览模式 ,Form1在代码中命名为 TestApp
,Form2在代码中其实是两Form分别命名为PreviewL
、PreviewR
。(实际使用中8个图像,一个屏幕预览效果不好,分成了两个屏幕预览。左屏4个预览画面,右屏4个预览画面)
下面是显示左屏4个区域的预览实现
1 Form2调用Form1的公共Bitmap------显示
1.1 Form1代码实现
在Form1声明8个 public static Bitmap:
csharp
public static Bitmap bitmapB6, bitmapC6, bitmapB5, bitmapB4, bitmapC4, bitmapB3, bitmapB2, bitmapC2, bitmapF6, bitmapF2;
在回调函数中获取8个区域的图像,
下面是bitmapB6,bitmapC6获取的示例。
csharp
private void __OnFrameCallbackFun_1(object objUserParam, IFrameData objIFrameData)
{
try
{
if (isShowSrcImg) //当开始处理图像时原视频要暂停,否则buffer中的数据会变化,图像上下颠倒交替出现(因Show(objIFrameData)中显示实现对图像数据做了垂直翻转)
{
//************************************************************
//显示相机获取的原图
//************************************************************
m_objGxBitmap1.Show(objIFrameData);
}
//获取图像宽、高、pBuffer、channel等信息
getImgInfo(objIFrameData, ref pBuffer1);
//************************************************************
// 对图像进行裁剪并显示在 PictureBox 中
//*************************************************************
if (isPreviewRoiImg)// 开启预览模式。
{
getRoiBmpData(ROI_B6, pBuffer1, ref bitmapB6);
getRoiBmpData(ROI_C6, pBuffer1, ref bitmapC6);
}
//统计帧率
m_objCFps1.IncreaseFrameNum();
}
catch (Exception ex)
{
MessageBox.Show("回调函数2" + ex.Message);
}
}
启动Form2,显示预览画面
csharp
private void bt_Preview_Click(object sender, EventArgs e)
{
isPreviewRoiImg = true;
//预览窗口实例化,并显示
PreviewL previewL = new PreviewL();
PreviewR previewR = new PreviewR();
previewL.Show();
previewR.Show();
Thread.Sleep(500);
//按键使能
bt_Preview.Enabled = false;
}
其中的 getRoiBmpData函数
csharp
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);
}
}
1.2 Form2代码实现
(实际使用中8个图像,一个屏幕预览效果不好,分成了两个屏幕预览。左屏4个预览画面,右屏4个预览画面)
下面是显示左屏4个区域的预览实现
csharp
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Timers;
using System.Windows.Forms;
using static TestAppMain.TestApp;
namespace TestAppMain
{
public partial class PreviewL : Form
{
//private System.Timers.Timer timer;
//private System.Threading.Timer timer;
//在这个案例中,使用 System.Windows.Forms.Timer,因为它是与 Windows 窗体应用程序兼容的计时器。它可以在 UI 线程上执行操作,因此更适合在窗体中刷新图像等 UI 相关的操作。
private System.Windows.Forms.Timer timer;
public PreviewL()
{
InitializeComponent();
fullScreen();
// 创建一个定时器,每隔一定时间刷新图像
timer = new System.Windows.Forms.Timer();
timer.Interval = 100; // 设置刷新间隔为100毫秒(可以根据需要调整)
timer.Tick += Timer_Tick;
timer.Start();
}
private void fullScreen()
{
// 判断当前窗体是否已经是最大化状态
if (this.WindowState == FormWindowState.Maximized)
{
// 如果已经最大化,则恢复到正常状态
this.WindowState = FormWindowState.Normal;
}
else
{
// 如果不是最大化,则将窗体最大化
this.WindowState = FormWindowState.Maximized;
}
}
private void Timer_Tick(object sender, EventArgs e)
{
// 在定时器触发事件中刷新图像
RefreshPreview();
}
private void RefreshPreview()
{
// 更新PictureBox中显示的图像
pictureBox_B6.Image = bitmapB6;
pictureBox_B5.Image = bitmapB5;
pictureBox_C6.Image = bitmapC6;
pictureBox_C4.Image = bitmapC4;
}
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
// 关闭窗口时停止定时器
timer.Stop();
timer.Dispose();
}
}
}
因为定义个了8个全局公共变量的图像,浪费资源,下面是事件函数实现;
2 使用事件触发
因为定义个了8个全局公共变量的图像,浪费资源,下面是事件函数实现;
2.1 Form1 代码实现
在Form1中声明一个枚举变量,把需要显示的8个区域 放在枚举中。定义事件函数和方法;
csharp
using GxIAPINET;
using MaterialSkin.Controls;
using System;
using System.Drawing;
using System.Windows.Forms;
namespace TestAppMain
{
//枚举类型声明
public enum enum_ImgRegion
{
//cam1
B6, C6,
//cam2
B4, C4, B5,
//cam3
B2, C2, B3,
//cam4
F6, E6,
//cam5
F2, E2
};
public partial class TestApp : Form
{
//From1主界面,这里Form1命名为TestApp
// 定义事件---使回调中的图像在另一个form中显示
public static event EventHandler<ImageEventArgs> ImageProcessed;
// 在图像处理回调函数中调用该方法,参数为相机编号和处理后的图像
public static void ShowProcessedImage(enum_ImgRegion imgRegion, Bitmap bitmap)
{
// 触发图像处理完成事件
ImageProcessed?.Invoke(null, new ImageEventArgs(imgRegion, bitmap));
}
public TestApp()
{
InitializeComponent();
}
//
//其他函数
//
//回调使用
private void __OnFrameCallbackFun_1(object objUserParam, IFrameData objIFrameData)
{
try
{
if (isShowSrcImg) //当开始处理图像时原视频要暂停,否则buffer中的数据会变化,图像上下颠倒交替出现(因Show(objIFrameData)中显示实现对图像数据做了垂直翻转)
{
//************************************************************
//显示相机获取的原图
//************************************************************
m_objGxBitmap1.Show(objIFrameData);
}
//获取图像宽、高、pBuffer、channel等信息
getImgInfo(objIFrameData, ref pBuffer1);
//************************************************************
// 对图像进行裁剪并显示在 PictureBox 中
//*************************************************************
if (isPreviewRoiImg)// 开启预览模式。
{
getRoiBmpData(ROI_B6, pBuffer1, enum_ImgRegion.B6);
getRoiBmpData(ROI_C6, pBuffer1, enum_ImgRegion.C6);
}
//统计帧率
m_objCFps1.IncreaseFrameNum();
}
catch (Exception ex)
{
MessageBox.Show("回调函数2" + ex.Message);
}
}
//其他回调函数函数
//启动Form2,显示预览画面
private void bt_Preview_Click(object sender, EventArgs e)
{
isPreviewRoiImg = true;
//预览窗口实例化,并显示
PreviewL previewL = new PreviewL();
PreviewR previewR = new PreviewR();
previewL.Show();
previewR.Show();
Thread.Sleep(500);
//按键使能
bt_Preview.Enabled = false;
}
}
//定义事件类,注意要 TestApp同级别,也就是都在namespace TestAppMain命名空间下的同一个级别
public class ImageEventArgs : EventArgs
{
public enum_ImgRegion ImgRegion { get; }
public Bitmap ProcessedImage { get; }
public ImageEventArgs(enum_ImgRegion imgRegion, Bitmap processedImage)
{
ImgRegion = imgRegion;
ProcessedImage = processedImage;
}
}
}
代码中的 getRoiBmpData函数,该函数与方法1中的函数一样,只是入口参数做了更改。
csharp
//方法法①双循环逐字节复制ROI区域的图像数据;
//方法②单循环, 逐行复制ROI区域的图像数据,提高效率,但很耗内存;
private void getRoiBmpData(Rectangle ROI, IntPtr pBuffer, enum_ImgRegion imgRegion)
{
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
{
//int bytes = Math.Abs(destStride) * m_bitmap.Height; // 计算 ROI 区域总字节数
byte* srcPtr = (byte*)pBuffer + srcOffset; // 指向原始图像数据的指针(获取位图数据的起始地址,并将其转换为 byte* 类型的指针。bmpData.Scan0 返回的是位图数据的第一个像素的内存地址)
byte* destPtr = (byte*)bmpData.Scan0; // 指向 ROI Bitmap 的扫描行指针
////双循环,逐字节操作;
//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;
//}
//单循环, 逐行操作
for (int y = 0; y < ROI.Height; y++) // 逐行复制ROI区域的图像数据
{
// 复制当前行的图像数据到ROI Bitmap中
//从源地址 srcPtr开始,每次复制destStride个字节(一整行的数据)到目标地址 bmpPtr 处,第四个参数偏移量
Buffer.MemoryCopy(srcPtr, destPtr, destStride, destStride);
destPtr += destStride;
srcPtr += srcStride;
}
}
//解锁对位图数据的访问,使其能够被其他代码访问。释放与位图数据关联的内存区域,以便系统能够对其进行管理。
bitmap.UnlockBits(bmpData);
// 在这里处理 bitmap 图像
ShowProcessedImage(imgRegion, bitmap);//传给函数,进行处理显示,
//ROI_bitmap = bitmap;
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
2.2 Form2代码实现
csharp
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Timers;
using System.Windows.Forms;
using static TestAppMain.TestApp;
namespace TestAppMain
{
public partial class PreviewL : Form
{
public PreviewL()
{
InitializeComponent();
//InitializeLayout();
fullScreen();
// 订阅图片处理事件
TestApp.ImageProcessed += ImageShowFrom_ImageProcessed;
}
private void fullScreen()
{
// 判断当前窗体是否已经是最大化状态
if (this.WindowState == FormWindowState.Maximized)
{
// 如果已经最大化,则恢复到正常状态
this.WindowState = FormWindowState.Normal;
}
else
{
// 如果不是最大化,则将窗体最大化
this.WindowState = FormWindowState.Maximized;
}
}
// 图片处理事件处理程序
private void ImageShowFrom_ImageProcessed(object sender, ImageEventArgs e)
{
switch (e.ImgRegion)
{
case enum_ImgRegion.B6:
pictureBox_B6.Image = e.ProcessedImage;
break;
case enum_ImgRegion.B5:
pictureBox_B5.Image = e.ProcessedImage;
break;
case enum_ImgRegion.C6:
pictureBox_C6.Image = e.ProcessedImage;
break;
case enum_ImgRegion.C4:
pictureBox_C4.Image = e.ProcessedImage;
break;
default:
break;
}
}
}
}