HK相机模块封装
本篇文章主要介绍如何使用HK官方提供的Demo(此处使用的是v1版本的Demo),来封装自己的打开相机方法。
下载MVS后,在对应的Samples目录下就能找到对应的Demo文件。
下文中的代码大部分都是对HK提供的Demo中的方法进行了逻辑上的改动,代码本身都是能直接从Demo中找到的。

文章内容源于:https://blog.csdn.net/qq_41375318/article/details/148051216 此处添加了一些额外说明,用于学习使用。
封装变量
封装变量
/// <summary>
/// 相机图像(bipmap格式)
/// </summary>
Bitmap m_bitmap = null;
/// <summary>
/// 相机是否已连接
/// </summary>
public bool IsConnect { get; private set; }
/// <summary>
/// 是否开始采集
/// </summary>
public bool isGrabbing { get; private set; }
/// <summary>
/// 是否为触发模式
/// </summary>
public bool isTriggerMode { get; private set; }
// 设备列表
private MyCamera.MV_CC_DEVICE_INFO_LIST m_stDeviceList;
// 相机对象
private MyCamera m_MyCamera = null;
// 取像线程
Thread m_hReceiveThread = null;
// 帧信息
MyCamera.MV_FRAME_OUT_INFO_EX m_stFrameInfo = new MyCamera.MV_FRAME_OUT_INFO_EX();
// 用于从驱动获取图像的缓存
IntPtr m_BufForDriver;
UInt32 m_nBufSizeForDriver = 0;
// R通道数据
byte[] m_pDataForRed = null;
// G通道数据
byte[] m_pDataForGreen = null;
// B通道数据
byte[] m_pDataForBlue = null;
// 读写图像时锁定
private Object BufForDriverLock = new Object();
private Object BufForImageLock = new Object();
启动相机方法封装
相机启动步骤
- 获取所有相机
- 根据相机序列号,看该序列号对应的相机ID是否存在,确定该相机是否存在
- 获取相机信息
- 建立设备对象
- 根据相机信息创建相机
- 打开设备
- 探测网络最佳包大小,并进行设置(针对网口相机,并且此步可忽略。默认情况下使用巨型帧)
- 设置为连续采集模式
- 注册异常回调函数(非必须,也可注册一些用于自己开发的项目的传参回调(向UI传图片、向其它类中传图片))
代码实现
此处的代码都能在对应的案例中找到。
打开相机
/// <summary>
/// 打开相机
/// </summary>
/// <returns></returns>
public bool OpenDevice()
{
// 枚举并搜索指定ID的相机是否存在
EnumDevices();
// 获取相机索引
int camIdx = GetDeviceIndex(txt_CameraSeriesNum.Text.Trim());
if (camIdx == -1)
{
MessageBox.Show("找不到该ID的相机!");
return false;
}
//获取相机信息
MyCamera.MV_CC_DEVICE_INFO device = (MyCamera.MV_CC_DEVICE_INFO)Marshal.PtrToStructure(m_stDeviceList.pDeviceInfo[camIdx],
typeof(MyCamera.MV_CC_DEVICE_INFO));
// 建立设备对象
if (null == m_MyCamera)
{
m_MyCamera = new MyCamera();
if (null == m_MyCamera)
{
MessageBox.Show("初始化相机对象失败");
return false;
}
}
// 创建设备
int nRet = m_MyCamera.MV_CC_CreateDevice_NET(ref device);
if (MyCamera.MV_OK != nRet)
{
MessageBox.Show($"创建设备失败,失败代码:{nRet}");
return false;
}
// 尝试打开设备
nRet = m_MyCamera.MV_CC_OpenDevice_NET();
if (MyCamera.MV_OK != nRet)
{
m_MyCamera.MV_CC_DestroyDevice_NET();
MessageBox.Show($"设备打开失败,失败代码:{nRet}");
return false;
}
// 探测网络最佳包大小(只对GigE相机有效)
if (device.nTLayerType == MyCamera.MV_GIGE_DEVICE)
{
int nPacketSize = m_MyCamera.MV_CC_GetOptimalPacketSize_NET();
if (nPacketSize > 0)
{
nRet = m_MyCamera.MV_CC_SetIntValue_NET("GevSCPSPacketSize", (uint)nPacketSize);
if (nRet != MyCamera.MV_OK)
{
MessageBox.Show($"设置包大小失败,失败代码:{nRet}");
}
}
else
{
MessageBox.Show($"获取包大小失败,返回的包大小为:{nPacketSize}");
}
}
// 设置采集连续模式
m_MyCamera.MV_CC_SetEnumValue_NET("AcquisitionMode", (uint)MyCamera.MV_CAM_ACQUISITION_MODE.MV_ACQ_MODE_CONTINUOUS);
m_MyCamera.MV_CC_SetEnumValue_NET("TriggerMode", (uint)MyCamera.MV_CAM_TRIGGER_MODE.MV_TRIGGER_MODE_OFF);
// 注册异常回调(此处还可以通过这种方法 注册其他回调),也可以直接注册自己项目中的回调
m_MyCamera.MV_CC_RegisterExceptionCallBack_NET(cbException, IntPtr.Zero);
IsConnect = true;
return true;
}
其他函数
// 枚举海康相机(GIGE,USB3)
public void EnumDevices()
{
// 枚举设备列表
m_stDeviceList.nDeviceNum = 0;
int nRet = MyCamera.MV_CC_EnumDevices_NET(MyCamera.MV_GIGE_DEVICE | MyCamera.MV_USB_DEVICE, ref m_stDeviceList);
if (0 != nRet)
{
MessageBox.Show("枚举HIK相机设备失败!");
return;
}
}
// 获取相机对应的枚举索引
private int GetDeviceIndex(string CameraID)
{
for (int i = 0; i < m_stDeviceList.nDeviceNum; i++)
{
MyCamera.MV_CC_DEVICE_INFO device = (MyCamera.MV_CC_DEVICE_INFO)Marshal.PtrToStructure(m_stDeviceList.pDeviceInfo[i], typeof(MyCamera.MV_CC_DEVICE_INFO));
if (device.nTLayerType == MyCamera.MV_GIGE_DEVICE)
{
MyCamera.MV_GIGE_DEVICE_INFO gigeInfo = (MyCamera.MV_GIGE_DEVICE_INFO)MyCamera.ByteToStruct(device.SpecialInfo.stGigEInfo, typeof(MyCamera.MV_GIGE_DEVICE_INFO));
if (gigeInfo.chSerialNumber == CameraID)
return i;
}
else if (device.nTLayerType == MyCamera.MV_USB_DEVICE)
{
MyCamera.MV_USB3_DEVICE_INFO usb3Info = (MyCamera.MV_USB3_DEVICE_INFO)MyCamera.ByteToStruct(device.SpecialInfo.stUsb3VInfo, typeof(MyCamera.MV_USB3_DEVICE_INFO));
if (usb3Info.chSerialNumber == CameraID)
return i;
}
}
return -1;
}
/// <summary>
/// 异常,则关闭相机
/// </summary>
/// <param name="nMsgType"></param>
/// <param name="pUser"></param>
private void cbException(uint nMsgType, IntPtr pUser)
{
IsConnect = false;
if (nMsgType == MyCamera.MV_EXCEPTION_DEV_DISCONNECT)
{
// 先关闭设备
CloseDevice();
// 在尝试重新打开设备
if (OpenDevice())
{
MessageBox.Show("尝试重新连接设备失败!");
}
}
}
/// <summary>
/// 关闭设备
/// </summary>
public void CloseDevice()
{
// 取流标志位清零
if (isGrabbing == true)
{
isGrabbing = false;
m_hReceiveThread.Join();
}
if (m_BufForDriver != IntPtr.Zero)
{
Marshal.Release(m_BufForDriver);
}
// 关闭设备
m_MyCamera.MV_CC_CloseDevice_NET();
m_MyCamera.MV_CC_DestroyDevice_NET();
IsConnect = false;
}
相机采图方法封装
相机采图流程
- 采集标志位置位,设为true,表示开始采集
- 启动取像线程(核心)
- 取流之前先清除帧长度
- 开始采集
- 如果采集失败,提示错误信息
点击查看代码
/// <summary>
/// 图像采集
/// </summary>
/// <returns></returns>
public bool StartGrab()
{
// 标志位置位true
isGrabbing = true;
m_hReceiveThread = new Thread(ReceiveThreadProcess);
m_hReceiveThread.Start();
// 取流之前先清除帧长度
m_stFrameInfo.nFrameLen = 0;
m_stFrameInfo.enPixelType = MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerBG8;
// 开始采集
int nRet = m_MyCamera.MV_CC_StartGrabbing_NET();
if (MyCamera.MV_OK != nRet)
{
isGrabbing = false;
m_hReceiveThread.Join();
MessageBox.Show($"连续采集失败,失败代码:{nRet}");
return false;
}
return true;
}
取像线程流程
- 获取单帧图像数据的有效负载大小(单位为字节),即从相机传输到客户端的每一帧图像数据(不包含协议头、尾等额外开销)的实际大小
- 获取图像高
- 获取图像宽
- 根据图像大小设置图像缓存
- 循环监听,触发相机的图像采集信号
- 获取一帧图像数据(核心)等待,软触发或者硬触发的信号
- 如果是彩色图像格式,则直接使用数据,给到pTemp
- 获取rgb三个通道的数据
- rgb三通道的指针数据转bitmap
- 进行显示
- 如果是黑白相机图像
- 灰度指针数据转bitmap
- 进行图像显示
取图线程
// 取像线程(核心)
private void ReceiveThreadProcess()
{
// 确保释放保存了旧图像数据的bitmap实例,用新图像宽高等信息new一个新的bitmap实例
if (null != m_bitmap)
{
m_bitmap.Dispose();
m_bitmap = null;
}
MyCamera.MVCC_INTVALUE stParam = new MyCamera.MVCC_INTVALUE();
int nRet = m_MyCamera.MV_CC_GetIntValue_NET("PayloadSize", ref stParam);
if (MyCamera.MV_OK != nRet)
{
MessageBox.Show($"读取PayloadSize失败,失败代码:{nRet}");
return;
}
UInt32 nPayloadSize = stParam.nCurValue;
// 获取图像高
nRet = m_MyCamera.MV_CC_GetIntValue_NET("Height", ref stParam);
if (MyCamera.MV_OK != nRet)
{
MessageBox.Show($"获取图像高失败,失败代码:{nRet}");
return;
}
uint nHeight = stParam.nCurValue;
// 获取图像宽
nRet = m_MyCamera.MV_CC_GetIntValue_NET("Width", ref stParam);
if (MyCamera.MV_OK != nRet)
{
MessageBox.Show($"获取图像宽失败,失败代码:{nRet}");
return;
}
uint nWidth = stParam.nCurValue;
// 根据图像大小设置图像缓存
//这里用于列坐标的内存对齐。
UInt32 nSupWidth = (nWidth + (UInt32)3) & 0xfffffffc;//5120
// 根据图像大小设置图像缓存
m_pDataForRed = new byte[nSupWidth * nHeight];
m_pDataForGreen = new byte[nSupWidth * nHeight];
m_pDataForBlue = new byte[nSupWidth * nHeight];
//此处的判断 是为了与后面做对应,我们统一把所有图像都转换为了3通道。防止由于当前nPayloadSize为单通道导致m_nBufSizeForDriver不足
if (3 * nPayloadSize > m_nBufSizeForDriver)
{
if (m_BufForDriver != IntPtr.Zero)
{
Marshal.Release(m_BufForDriver);
}
m_nBufSizeForDriver = 3 * nPayloadSize;
m_BufForDriver = Marshal.AllocHGlobal((Int32)m_nBufSizeForDriver);
}
if (m_BufForDriver == IntPtr.Zero)
{
return;
}
IntPtr pImageBuffer = Marshal.AllocHGlobal((int)nPayloadSize * 3);
if (pImageBuffer == IntPtr.Zero)
{
MessageBox.Show($"申请图像缓存区失败!");
return;
}
MyCamera.MV_FRAME_OUT_INFO_EX stFrameInfo = new MyCamera.MV_FRAME_OUT_INFO_EX();
//用来记录RGB三个区域的图像数据
IntPtr RedPtr = IntPtr.Zero;
IntPtr GreenPtr = IntPtr.Zero;
IntPtr BluePtr = IntPtr.Zero;
IntPtr pTemp = IntPtr.Zero;
DateTime ProStartTime = DateTime.MinValue;
while (isGrabbing)
{
//关键:防止多线程竞争访问相机
lock (BufForDriverLock)
{
//传入图像的是前面申请的内存空间的 指针以及内存区域大小,方法返回对应的帧信息类。
nRet = m_MyCamera.MV_CC_GetOneFrameTimeout_NET(m_BufForDriver, m_nBufSizeForDriver, ref stFrameInfo, 1000);
if (nRet == MyCamera.MV_OK)
{
ProStartTime = DateTime.Now;
// MessageBox.Show("相机取图完成,开始处理...");
m_stFrameInfo = stFrameInfo;
}
}
if (nRet == MyCamera.MV_OK)
{
// 彩色相机(对于所有的彩色图像,此处统一转换为RGB3通道图像)
if (IsColorData(stFrameInfo.enPixelType))
{
if (stFrameInfo.enPixelType == MyCamera.MvGvspPixelType.PixelType_Gvsp_RGB8_Packed)
{
//直接获取图像起始地址
pTemp = m_BufForDriver;
}
else
{
//转换彩色图像
nRet = ConvertToRGB(m_MyCamera, m_BufForDriver, stFrameInfo.nHeight, stFrameInfo.nWidth, stFrameInfo.enPixelType, pImageBuffer);
if (MyCamera.MV_OK != nRet)
{
return;
}
pTemp = pImageBuffer;
}
unsafe
{
//根据前面得到的指针地址,此处将完整的RGB图像数据 划分为三个通道分别存放到三个数组中
//这里用Marshal的CopyMemoer方法应该会更快,但我还没试
byte* pBufForSaveImage = (byte*)pTemp;
//UInt32 nSupWidth = (stFrameInfo.nWidth + (UInt32)3) & 0xfffffffc;//5120
for (int nRow = 0; nRow < stFrameInfo.nHeight; nRow++)
{
for (int col = 0; col < stFrameInfo.nWidth; col++)
{
m_pDataForRed[nRow * nSupWidth + col] = pBufForSaveImage[nRow * stFrameInfo.nWidth * 3 + (3 * col)];
m_pDataForGreen[nRow * nSupWidth + col] = pBufForSaveImage[nRow * stFrameInfo.nWidth * 3 + (3 * col + 1)];
m_pDataForBlue[nRow * nSupWidth + col] = pBufForSaveImage[nRow * stFrameInfo.nWidth * 3 + (3 * col + 2)];
}
}
}
RedPtr = Marshal.UnsafeAddrOfPinnedArrayElement(m_pDataForRed, 0);
GreenPtr = Marshal.UnsafeAddrOfPinnedArrayElement(m_pDataForGreen, 0);
BluePtr = Marshal.UnsafeAddrOfPinnedArrayElement(m_pDataForBlue, 0);
//显示采集图像(防止多线程竞争访问UI资源)
lock (BufForImageLock)
{
//从RGB数据中创建Bitmap
m_bitmap = CreateBitmapFromRGBPointers(RedPtr, GreenPtr, BluePtr,stFrameInfo.nWidth, stFrameInfo.nHeight);
pictureBox1.Image = m_bitmap;
}
}
// 黑白图像(同理)
else if (IsMonoData(stFrameInfo.enPixelType))
{
if (stFrameInfo.enPixelType == MyCamera.MvGvspPixelType.PixelType_Gvsp_Mono8)
{
pTemp = m_BufForDriver;
}
else
{
nRet = ConvertToMono8(m_MyCamera, m_BufForDriver, pImageBuffer, stFrameInfo.nHeight, stFrameInfo.nWidth, stFrameInfo.enPixelType);
if (MyCamera.MV_OK != nRet)
{
return;
}
pTemp = pImageBuffer;
}
// 显示采集图像
lock (BufForImageLock)
{
m_bitmap = CreateGrayscaleBitmapFromIntPtr(pTemp, stFrameInfo.nWidth, stFrameInfo.nHeight);
pictureBox1.Image = m_bitmap;
}
}
//如果解析不了当前像素合适 就跳过
else
{
continue;
}
}
else
{
if (isTriggerMode)
{
Thread.Sleep(5);
}
}
}
}
辅助方法
/// <summary>
/// 判断是否为黑白图像
/// </summary>
/// <param name="enGvspPixelType"></param>
/// <returns></returns>
private Boolean IsMonoData(MyCamera.MvGvspPixelType enGvspPixelType)
{
switch (enGvspPixelType)
{
case MyCamera.MvGvspPixelType.PixelType_Gvsp_Mono8:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_Mono10:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_Mono10_Packed:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_Mono12:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_Mono12_Packed:
return true;
default:
return false;
}
}
/// <summary>
/// 判断是否为彩色图像
/// </summary>
/// <param name="enGvspPixelType"></param>
/// <returns></returns>
private Boolean IsColorData(MyCamera.MvGvspPixelType enGvspPixelType)
{
switch (enGvspPixelType)
{
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerGR8:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerRG8:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerGB8:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerBG8:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerGR10:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerRG10:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerGB10:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerBG10:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerGR12:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerRG12:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerGB12:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerBG12:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerGR10_Packed:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerRG10_Packed:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerGB10_Packed:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerBG10_Packed:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerGR12_Packed:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerRG12_Packed:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerGB12_Packed:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_BayerBG12_Packed:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_RGB8_Packed:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_YUV422_Packed:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_YUV422_YUYV_Packed:
case MyCamera.MvGvspPixelType.PixelType_Gvsp_YCBCR411_8_CBYYCRYY:
return true;
default:
return false;
}
}
/// <summary>
/// 转换为RGB格式
/// </summary>
/// <param name="obj"></param>
/// <param name="pSrc"></param>
/// <param name="nHeight"></param>
/// <param name="nWidth"></param>
/// <param name="nPixelType"></param>
/// <param name="pDst"></param>
/// <returns></returns>
private Int32 ConvertToRGB(object obj, IntPtr pSrc, ushort nHeight, ushort nWidth, MyCamera.MvGvspPixelType nPixelType, IntPtr pDst)
{
if (IntPtr.Zero == pSrc || IntPtr.Zero == pDst)
{
return MyCamera.MV_E_PARAMETER;
}
int nRet = MyCamera.MV_OK;
MyCamera device = obj as MyCamera;
MyCamera.MV_PIXEL_CONVERT_PARAM stPixelConvertParam = new MyCamera.MV_PIXEL_CONVERT_PARAM();
//指针
stPixelConvertParam.pSrcData = pSrc;//源数据
if (IntPtr.Zero == stPixelConvertParam.pSrcData)
{
return -1;
}
//源数据设置
stPixelConvertParam.nWidth = nWidth;//图像宽度
stPixelConvertParam.nHeight = nHeight;//图像高度
stPixelConvertParam.enSrcPixelType = nPixelType;//源数据的格式
//源数据长度=宽*高*每像素字节数,这与HK中定义的PixelType有关(高 16 位通常存储 “每像素的位数")
stPixelConvertParam.nSrcDataLen = (uint)(nWidth * nHeight * ((((uint)nPixelType) >> 16) & 0x00ff) >> 3);
//与上面同理,这里计算的是目标像素类型的总字节数=宽*高*RGB三通道类型每像素字节数(这里实际上就是*3)
stPixelConvertParam.nDstBufferSize = (uint)(nWidth * nHeight * ((((uint)MyCamera.MvGvspPixelType.PixelType_Gvsp_RGB8_Packed) >> 16) & 0x00ff) >> 3);
//存放转换后数据的 指针控件
stPixelConvertParam.pDstBuffer = pDst;
//目标像素类型
stPixelConvertParam.enDstPixelType = MyCamera.MvGvspPixelType.PixelType_Gvsp_RGB8_Packed;
stPixelConvertParam.nDstBufferSize = (uint)nWidth * nHeight * 3;
//格式转换
nRet = device.MV_CC_ConvertPixelType_NET(ref stPixelConvertParam);//格式转换
if (MyCamera.MV_OK != nRet)
{
return -1;
}
return MyCamera.MV_OK;
}
/// <summary>
/// 转换为Mono8格式
/// </summary>
/// <param name="obj"></param>
/// <param name="pInData"></param>
/// <param name="pOutData"></param>
/// <param name="nHeight"></param>
/// <param name="nWidth"></param>
/// <param name="nPixelType"></param>
/// <returns></returns>
private Int32 ConvertToMono8(object obj, IntPtr pInData, IntPtr pOutData, ushort nHeight, ushort nWidth, MyCamera.MvGvspPixelType nPixelType)
{
if (IntPtr.Zero == pInData || IntPtr.Zero == pOutData)
{
return MyCamera.MV_E_PARAMETER;
}
int nRet = MyCamera.MV_OK;
MyCamera device = obj as MyCamera;
MyCamera.MV_PIXEL_CONVERT_PARAM stPixelConvertParam = new MyCamera.MV_PIXEL_CONVERT_PARAM();
stPixelConvertParam.pSrcData = pInData;//源数据
if (IntPtr.Zero == stPixelConvertParam.pSrcData)
{
return -1;
}
stPixelConvertParam.nWidth = nWidth;//图像宽度
stPixelConvertParam.nHeight = nHeight;//图像高度
stPixelConvertParam.enSrcPixelType = nPixelType;//源数据的格式
stPixelConvertParam.nSrcDataLen = (uint)(nWidth * nHeight * ((((uint)nPixelType) >> 16) & 0x00ff) >> 3);
//Mono8用不了这么大的空间 ,因为其是单通道的。此处的空间我们是按照一个3通道数据申请的,这也与上面的if判断对应起来了。
stPixelConvertParam.nDstBufferSize = (uint)(nWidth * nHeight * ((((uint)MyCamera.MvGvspPixelType.PixelType_Gvsp_RGB8_Packed) >> 16) & 0x00ff) >> 3);
stPixelConvertParam.pDstBuffer = pOutData;//转换后的数据
stPixelConvertParam.enDstPixelType = MyCamera.MvGvspPixelType.PixelType_Gvsp_Mono8;
stPixelConvertParam.nDstBufferSize = (uint)(nWidth * nHeight * 3);
//stPixelConvertParam.nDstBufferSize = (uint)(nWidth * nHeight * 1);
nRet = device.MV_CC_ConvertPixelType_NET(ref stPixelConvertParam);//格式转换
if (MyCamera.MV_OK != nRet)
{
return -1;
}
return nRet;
}
RGB/灰度图 转Bitmap
/// <summary>
/// 灰度数据intptr转bitmap图像
/// </summary>
/// <param name="grayData"></param>
/// <param name="width"></param>
/// <param name="height"></param>
/// <returns></returns>
public Bitmap CreateGrayscaleBitmapFromIntPtr(IntPtr grayData, int width, int height)
{
// 创建一个8bpp的灰度位图
Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format8bppIndexed);
// 设置灰度调色板
ColorPalette palette = bitmap.Palette;
for (int i = 0; i < 256; i++)
{
palette.Entries[i] = Color.FromArgb(i, i, i);
}
bitmap.Palette = palette;
// 锁定位图数据
BitmapData bitmapData = bitmap.LockBits(
new Rectangle(0, 0, width, height),
ImageLockMode.WriteOnly,
bitmap.PixelFormat);
try
{
// 将灰度数据复制到位图
byte[] pixelData = new byte[width * height];
Marshal.Copy(grayData, pixelData, 0, pixelData.Length);
// 将数据复制到位图内存中
Marshal.Copy(pixelData, 0, bitmapData.Scan0, pixelData.Length);
}
finally
{
bitmap.UnlockBits(bitmapData);
}
return bitmap;
}
/// <summary>
/// RGB三通道数据转bitmap
/// </summary>
/// <param name="rPtr"></param>
/// <param name="gPtr"></param>
/// <param name="bPtr"></param>
/// <param name="width"></param>
/// <param name="height"></param>
/// <returns></returns>
public Bitmap CreateBitmapFromRGBPointers(IntPtr rPtr, IntPtr gPtr, IntPtr bPtr, int width, int height)
Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format24bppRgb);
BitmapData bitmapData = bitmap.LockBits(
new Rectangle(0, 0, width, height),
ImageLockMode.WriteOnly,
bitmap.PixelFormat);
try
{
int bytesPerPixel = 3;
int pixelCount = width * height;
byte[] pixelData = new byte[pixelCount * bytesPerPixel];
// 将指针数据复制到托管数组
Marshal.Copy(rPtr, pixelData, 0 * pixelCount, pixelCount); // Red
Marshal.Copy(gPtr, pixelData, 1 * pixelCount, pixelCount); // Green
Marshal.Copy(bPtr, pixelData, 2 * pixelCount, pixelCount); // Blue
// 重新排列为BGR格式
for (int i = 0; i < pixelCount; i++)
{
byte temp = pixelData[i * 3]; // R
pixelData[i * 3] = pixelData[i * 3 + 2]; // B
pixelData[i * 3 + 2] = temp; // R
// G remains in the middle
}
// 复制到位图
Marshal.Copy(pixelData, 0, bitmapData.Scan0, pixelData.Length);
}
finally
{
bitmap.UnlockBits(bitmapData);
}
return bitmap;
</details>

浙公网安备 33010602011771号