using MvCamCtrl.NET; using Sunny.UI; using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; namespace Check.Main.Camera { public class HikvisionCamera : IDisposable { [DllImport("kernel32.dll", EntryPoint = "RtlMoveMemory", SetLastError = false)] private static extern void CopyMemory(IntPtr dest, IntPtr src, uint count); // 事件委托 public delegate void ImageAcquiredEventHandler(HikvisionCamera sender, Bitmap bmp); // public delegate void ImageAcquiredEventHandler(object sender, Bitmap bmp); public delegate void CameraMessageEventHandler(object sender, string message, int errorCode = 0); // 事件 public event ImageAcquiredEventHandler ImageAcquired; public event CameraMessageEventHandler CameraMessage; // 私有成员变量 private MyCamera m_MyCamera; private MyCamera.MV_CC_DEVICE_INFO_LIST m_stDeviceList; private bool m_bGrabbing = false; private Thread m_hReceiveThread = null; private Bitmap m_bitmap = null; private IntPtr m_ConvertDstBuf = IntPtr.Zero; private uint m_nConvertDstBufLen = 0; public string Name { get; set; } /// /// 由外部逻辑分配的、唯一的相机软件编号。 /// 主要用作图像路由和与逻辑处理器匹配的键。 /// public int CameraIndex { get; set; } /// /// 相机是否已经打开 /// public bool IsOpen { get; private set; } = false; public TriggerModeType TriggerMode { get; internal set; } // internal set 保证只有 CameraManager 能设置它 /// /// 相机是否正在采集 /// public bool IsGrabbing => m_bGrabbing; public HikvisionCamera() { // 初始化SDK MyCamera.MV_CC_Initialize_NET(); } /// /// 查找所有可用的相机设备 /// /// 设备名称列表 public List FindDevices() { var deviceList = new List(); m_stDeviceList = new MyCamera.MV_CC_DEVICE_INFO_LIST(); int nRet = MyCamera.MV_CC_EnumDevices_NET(MyCamera.MV_GIGE_DEVICE | MyCamera.MV_USB_DEVICE, ref m_stDeviceList); if (nRet != MyCamera.MV_OK) { OnCameraMessage("设备枚举失败!", nRet); return deviceList; } for (int i = 0; i < m_stDeviceList.nDeviceNum; i++) { var 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) { var gigeInfo = (MyCamera.MV_GIGE_DEVICE_INFO_EX)MyCamera.ByteToStruct(device.SpecialInfo.stGigEInfo, typeof(MyCamera.MV_GIGE_DEVICE_INFO_EX)); if (!string.IsNullOrEmpty(Encoding.Default.GetString(gigeInfo.chUserDefinedName).TrimEnd('\0'))) deviceList.Add("GEV: " + gigeInfo.chUserDefinedName + " (" + gigeInfo.chSerialNumber + ")"); else deviceList.Add("GEV: " + gigeInfo.chManufacturerName + " " + gigeInfo.chModelName + " (" + gigeInfo.chSerialNumber + ")"); } else if (device.nTLayerType == MyCamera.MV_USB_DEVICE) { var usbInfo = (MyCamera.MV_USB3_DEVICE_INFO_EX)MyCamera.ByteToStruct(device.SpecialInfo.stUsb3VInfo, typeof(MyCamera.MV_USB3_DEVICE_INFO_EX)); if (!string.IsNullOrEmpty(Encoding.Default.GetString(usbInfo.chUserDefinedName).TrimEnd('\0'))) deviceList.Add("U3V: " + usbInfo.chUserDefinedName + " (" + usbInfo.chSerialNumber + ")"); else deviceList.Add("U3V: " + usbInfo.chManufacturerName + " " + usbInfo.chModelName + " (" + usbInfo.chSerialNumber + ")"); } } return deviceList; } /// /// 根据索引打开相机 /// /// 设备索引 /// 成功返回true,否则返回false public bool Open(CameraSettings camConfig) { string ipCam = ""; string ipDevice = ""; FindDevices(); if (camConfig.IsEnabled == true) { ipCam = camConfig.IPAddress; ipDevice = camConfig.IPDeviceAddress; } try { if (m_stDeviceList.nDeviceNum == 0 || string.IsNullOrEmpty(camConfig.IPAddress)) { OnCameraMessage("没有找到设备或索引无效。", -1); return false; } if (IsOpen) { OnCameraMessage("相机已经打开。", 0); return true; } MyCamera.MV_CC_DEVICE_INFO device = new MyCamera.MV_CC_DEVICE_INFO(); device.nTLayerType = MyCamera.MV_GIGE_DEVICE; MyCamera.MV_GIGE_DEVICE_INFO stGigEDev = new MyCamera.MV_GIGE_DEVICE_INFO(); var parts = ipCam.Split('.'); int nIp1 = Convert.ToInt32(parts[0]); int nIp2 = Convert.ToInt32(parts[1]); int nIp3 = Convert.ToInt32(parts[2]); int nIp4 = Convert.ToInt32(parts[3]); stGigEDev.nCurrentIp = (uint)((nIp1 << 24) | (nIp2 << 16) | (nIp3 << 8) | nIp4); parts = ipDevice.Split('.'); nIp1 = Convert.ToInt32(parts[0]); nIp2 = Convert.ToInt32(parts[1]); nIp3 = Convert.ToInt32(parts[2]); nIp4 = Convert.ToInt32(parts[3]); stGigEDev.nNetExport = (uint)((nIp1 << 24) | (nIp2 << 16) | (nIp3 << 8) | nIp4); IntPtr stGigeInfoPtr = Marshal.AllocHGlobal(216); Marshal.StructureToPtr(stGigEDev, stGigeInfoPtr, false); device.SpecialInfo.stGigEInfo = new Byte[540]; Marshal.Copy(stGigeInfoPtr, device.SpecialInfo.stGigEInfo, 0, 540); //释放内存空间 Marshal.FreeHGlobal(stGigeInfoPtr); //var device = (MyCamera.MV_CC_DEVICE_INFO)Marshal.PtrToStructure(m_stDeviceList.pDeviceInfo[index], typeof(MyCamera.MV_CC_DEVICE_INFO)); m_MyCamera = new MyCamera(); int nRet = m_MyCamera.MV_CC_CreateDevice_NET(ref device); if (nRet != MyCamera.MV_OK) { OnCameraMessage("创建设备句柄失败!", nRet); return false; } nRet = m_MyCamera.MV_CC_OpenDevice_NET(); if (nRet != MyCamera.MV_OK) { OnCameraMessage("打开设备失败!", nRet); m_MyCamera.MV_CC_DestroyDevice_NET(); return false; } // 探测网络最佳包大小(只对GigE相机有效) if (device.nTLayerType == MyCamera.MV_GIGE_DEVICE) { int nPacketSize = m_MyCamera.MV_CC_GetOptimalPacketSize_NET(); if (nPacketSize > 0) { m_MyCamera.MV_CC_SetIntValueEx_NET("GevSCPSPacketSize", nPacketSize); } } // 默认设置为连续模式 //SetContinuousMode(); // 设置为硬触发模式(Line0) SetTriggerMode(false); IsOpen = true; OnCameraMessage("相机打开成功。", 0); return true; } catch (Exception ex) { OnCameraMessage("相机打开失败。"+ ex.ToString(), 0); return false; } } /// /// 关闭相机 /// public void Close() { if (!IsOpen) return; if (m_bGrabbing) { StopGrabbing(); } m_MyCamera.MV_CC_CloseDevice_NET(); m_MyCamera.MV_CC_DestroyDevice_NET(); IsOpen = false; m_MyCamera = null; OnCameraMessage("相机已关闭。", 0); } /// /// 开始采集图像 /// /// 成功返回true public bool StartGrabbing() { if (!IsOpen || m_bGrabbing) { OnCameraMessage(m_bGrabbing ? "已经在采集中。" : "相机未打开。", -1); return false; } // 取图前的必要操作 if (NecessaryOperBeforeGrab() != MyCamera.MV_OK) { return false; } int nRet = m_MyCamera.MV_CC_StartGrabbing_NET(); if (nRet != MyCamera.MV_OK) { OnCameraMessage("开始采集失败!", nRet); return false; } m_bGrabbing = true; m_hReceiveThread = new Thread(ReceiveThreadProcess); m_hReceiveThread.IsBackground = true; m_hReceiveThread.Start(); OnCameraMessage("开始采集。", 0); return true; } /// /// 停止采集图像 /// public void StopGrabbing() { if (!m_bGrabbing) return; m_bGrabbing = false; if (m_hReceiveThread != null && m_hReceiveThread.IsAlive) { m_hReceiveThread.Join(1000); // 等待线程退出 } int nRet = m_MyCamera.MV_CC_StopGrabbing_NET(); if (nRet != MyCamera.MV_OK) { OnCameraMessage("停止采集失败!", nRet); } OnCameraMessage("停止采集。", 0); } /// /// 设置为连续采集模式 /// public void SetContinuousMode() { if (!IsOpen) return; 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); OnCameraMessage("已设置为连续模式。", 0); } /// /// 设置为触发模式 /// /// true为软触发, false为硬触发(Line0) public void SetTriggerMode(bool isSoftwareTrigger) { if (!IsOpen) return; m_MyCamera.MV_CC_SetEnumValue_NET("AcquisitionMode", (uint)MyCamera.MV_CAM_ACQUISITION_MODE.MV_ACQ_MODE_CONTINUOUS); // 触发模式也需要在Continuous模式下 m_MyCamera.MV_CC_SetEnumValue_NET("TriggerMode", (uint)MyCamera.MV_CAM_TRIGGER_MODE.MV_TRIGGER_MODE_ON); if (isSoftwareTrigger) { m_MyCamera.MV_CC_SetEnumValue_NET("TriggerSource", (uint)MyCamera.MV_CAM_TRIGGER_SOURCE.MV_TRIGGER_SOURCE_SOFTWARE); OnCameraMessage("已设置为软触发模式。", 0); } else { m_MyCamera.MV_CC_SetEnumValue_NET("TriggerSource", (uint)MyCamera.MV_CAM_TRIGGER_SOURCE.MV_TRIGGER_SOURCE_LINE0); OnCameraMessage("已设置为硬触发(Line0)模式。", 0); } } /// /// 执行一次软触发 /// /// 成功返回true public bool SoftwareTrigger() { if (!IsOpen || !m_bGrabbing) { OnCameraMessage("请先打开相机并开始采集。", -1); return false; } int nRet = m_MyCamera.MV_CC_SetCommandValue_NET("TriggerSoftware"); if (nRet != MyCamera.MV_OK) { OnCameraMessage("软触发失败!", nRet); return false; } return true; } /// /// 图像接收线程 /// private void ReceiveThreadProcess() { MyCamera.MV_FRAME_OUT stFrameInfo = new MyCamera.MV_FRAME_OUT(); var stConvertParam = new MyCamera.MV_PIXEL_CONVERT_PARAM(); while (m_bGrabbing) { int nRet = m_MyCamera.MV_CC_GetImageBuffer_NET(ref stFrameInfo, 1000); if (nRet == MyCamera.MV_OK) { Bitmap bmpForEvent = null; // 在 try 外部声明 try { // 初始化转换参数 stConvertParam.nWidth = stFrameInfo.stFrameInfo.nWidth; stConvertParam.nHeight = stFrameInfo.stFrameInfo.nHeight; stConvertParam.pSrcData = stFrameInfo.pBufAddr; stConvertParam.nSrcDataLen = stFrameInfo.stFrameInfo.nFrameLen; stConvertParam.enSrcPixelType = stFrameInfo.stFrameInfo.enPixelType; stConvertParam.pDstBuffer = m_ConvertDstBuf; stConvertParam.nDstBufferSize = m_nConvertDstBufLen; // 创建用于事件的Bitmap,它的格式与类成员m_bitmap一致 bmpForEvent = new Bitmap((int)stConvertParam.nWidth, (int)stConvertParam.nHeight, m_bitmap.PixelFormat); // 判断并设置目标像素格式 if (bmpForEvent.PixelFormat == PixelFormat.Format8bppIndexed) { stConvertParam.enDstPixelType = MyCamera.MvGvspPixelType.PixelType_Gvsp_Mono8; } else { stConvertParam.enDstPixelType = MyCamera.MvGvspPixelType.PixelType_Gvsp_BGR8_Packed; } // 执行像素格式转换 nRet = m_MyCamera.MV_CC_ConvertPixelType_NET(ref stConvertParam); if (nRet == MyCamera.MV_OK) { // 转换成功,锁定位图内存并拷贝数据 BitmapData bmpData = bmpForEvent.LockBits(new Rectangle(0, 0, stConvertParam.nWidth, stConvertParam.nHeight), ImageLockMode.WriteOnly, bmpForEvent.PixelFormat); // 使用更健壮的逐行拷贝 int pixelSize = (bmpForEvent.PixelFormat == PixelFormat.Format8bppIndexed) ? 1 : 3; for (int i = 0; i < bmpData.Height; ++i) { IntPtr pDst = new IntPtr(bmpData.Scan0.ToInt64() + i * bmpData.Stride); IntPtr pSrc = new IntPtr(m_ConvertDstBuf.ToInt64() + i * stConvertParam.nWidth * pixelSize); CopyMemory(pDst, pSrc, (uint)(stConvertParam.nWidth * pixelSize)); } bmpForEvent.UnlockBits(bmpData); // 触发图像采集事件,转移 bmpForEvent 的所有权 ImageAcquired?.Invoke(this, bmpForEvent); // 【关键】所有权已转移,将本地引用设为null,防止它在finally块中被错误地释放 bmpForEvent = null; } // 如果转换失败,不执行任何操作,让 finally 块来处理 bmpForEvent 的释放 } finally { // 【关键】如果 bmpForEvent 不为 null,意味着它被成功创建, // 但没有被成功传递给事件处理器(可能因为转换失败或发生其他异常), // 在这里必须将其释放。 bmpForEvent?.Dispose(); // 无论成功与否,都必须释放相机SDK的内部图像缓存 m_MyCamera.MV_CC_FreeImageBuffer_NET(ref stFrameInfo); } } else { // 超时,继续等待 Thread.Sleep(5); } } } /// /// 取图前的内存和参数准备 /// private int NecessaryOperBeforeGrab() { var stWidth = new MyCamera.MVCC_INTVALUE_EX(); int nRet = m_MyCamera.MV_CC_GetIntValueEx_NET("Width", ref stWidth); if (nRet != MyCamera.MV_OK) { OnCameraMessage("获取宽度失败!", nRet); return nRet; } var stHeight = new MyCamera.MVCC_INTVALUE_EX(); nRet = m_MyCamera.MV_CC_GetIntValueEx_NET("Height", ref stHeight); if (nRet != MyCamera.MV_OK) { OnCameraMessage("获取高度失败!", nRet); return nRet; } var stPixelFormat = new MyCamera.MVCC_ENUMVALUE(); nRet = m_MyCamera.MV_CC_GetEnumValue_NET("PixelFormat", ref stPixelFormat); if (nRet != MyCamera.MV_OK) { OnCameraMessage("获取像素格式失败!", nRet); return nRet; } PixelFormat bitmapPixelFormat; // 判断当前像素格式是否为Mono bool isMono = IsMono(stPixelFormat.nCurValue); if (isMono) { bitmapPixelFormat = PixelFormat.Format8bppIndexed; m_nConvertDstBufLen = (uint)(stWidth.nCurValue * stHeight.nCurValue); } else { bitmapPixelFormat = PixelFormat.Format24bppRgb; m_nConvertDstBufLen = (uint)(stWidth.nCurValue * stHeight.nCurValue * 3); } // 分配转换后的图像数据缓存 if (m_ConvertDstBuf != IntPtr.Zero) Marshal.FreeHGlobal(m_ConvertDstBuf); m_ConvertDstBuf = Marshal.AllocHGlobal((int)m_nConvertDstBufLen); // 创建Bitmap对象 if (m_bitmap != null) m_bitmap.Dispose(); m_bitmap = new Bitmap((int)stWidth.nCurValue, (int)stHeight.nCurValue, bitmapPixelFormat); // 如果是Mono8格式,设置调色板 if (isMono) { ColorPalette palette = m_bitmap.Palette; for (int i = 0; i < 256; i++) palette.Entries[i] = Color.FromArgb(i, i, i); m_bitmap.Palette = palette; } return MyCamera.MV_OK; } private bool IsMono(uint enPixelType) { switch (enPixelType) { case (uint)MyCamera.MvGvspPixelType.PixelType_Gvsp_Mono8: case (uint)MyCamera.MvGvspPixelType.PixelType_Gvsp_Mono10: case (uint)MyCamera.MvGvspPixelType.PixelType_Gvsp_Mono10_Packed: case (uint)MyCamera.MvGvspPixelType.PixelType_Gvsp_Mono12: case (uint)MyCamera.MvGvspPixelType.PixelType_Gvsp_Mono12_Packed: return true; default: return false; } } /// /// 触发相机消息事件 /// private void OnCameraMessage(string message, int errorCode = 0) { CameraMessage?.Invoke(this, message, errorCode); } /// /// 释放资源 /// public void Dispose() { Close(); if (m_ConvertDstBuf != IntPtr.Zero) { Marshal.FreeHGlobal(m_ConvertDstBuf); m_ConvertDstBuf = IntPtr.Zero; } if (m_bitmap != null) { m_bitmap.Dispose(); m_bitmap = null; } MyCamera.MV_CC_Finalize_NET(); } } }