using MVSDK; using LB_VisionProcesses; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing.Imaging; using System.Dynamic; using System.Linq; using System.Net.NetworkInformation; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using static System.Windows.Forms.VisualStyles.VisualStyleElement; //相机句柄,SDK支持同时打开多个相机,用该句柄区分多相机 using CameraHandle = System.Int32; using LB_VisionProcesses.Cameras; namespace LB_VisionProcesses.Cameras.MindCameras { public class MindCamera : BaseCamera { #region variable public CameraHandle m_hCamera = 0; // 句柄 protected IntPtr m_ImageBuffer; // 预览通道RGB图像缓存 protected IntPtr m_ImageBufferSnapshot; // 抓拍通道RGB图像缓存 protected tSdkCameraCapbility tCameraCapability; // 相机特性描述 protected int m_iDisplayedFrames = 0; //已经显示的总帧数 protected CAMERA_SNAP_PROC m_CaptureCallback; protected IntPtr m_iCaptureCallbackCtx; //图像回调函数的上下文参数 protected Thread m_tCaptureThread; //图像抓取线程 public bool m_bExitCaptureThread = false;//采用线程采集时,让线程退出的标志 protected IntPtr m_iSettingPageMsgCallbackCtx; //相机配置界面消息回调函数的上下文参数 protected tSdkFrameHead m_tFrameHead; protected tSdkFrameHead m_pFrameHead; //protected SnapshotDlg m_DlgSnapshot = new SnapshotDlg(); //显示抓拍图像的窗口 protected bool m_bEraseBk = false; protected bool m_bSaveImage = false; public const Int32 EXT_TRIG_MASK_LEVEL_MODE = 2; public IntPtr m_Grabber = IntPtr.Zero; protected tSdkCameraDevInfo m_DevInfo; protected tSdkCameraCapbility m_tSdkCameraCapbility; // 相机特性描述 protected ColorPalette m_GrayPal; protected pfnCameraGrabberFrameCallback m_FrameCallback; // 创建 CancellationTokenSource 实例 CancellationTokenSource cts = new CancellationTokenSource(); #endregion public MindCamera() : base() { Brand = CameraBrand.MindCamera; } public override bool AutoBalanceWhite() { throw new NotImplementedException(); } public override bool CloseDevice() { try { if (m_hCamera == 0) return true; MvApi.CameraUnInit(m_hCamera); Marshal.FreeHGlobal(m_ImageBuffer); Marshal.FreeHGlobal(m_ImageBufferSnapshot); MvApi.CameraGrabber_Destroy(m_Grabber); m_hCamera = 0; SN = string.Empty; m_bExitCaptureThread = true; isGrabbing = false; return true; } catch { return false; } } public override bool GetExpouseTime(out double value) { try { double exp = 0; MvApi.CameraGetExposureTime(m_hCamera, ref exp); value = Convert.ToDouble(exp); return true; } catch { value = 0; return false; } } public override bool GetGain(out double value) { try { MvApi.CameraGetAnalogGainX(m_hCamera, out float Gain); value = Convert.ToDouble(Gain); return true; } catch { value = 0; return false; } } public override bool GetLineStatus(IOLines line, out LineStatus lineStatus) { try { uint bchek = 0; MvApi.CameraGetIOState(m_hCamera, (int)line, ref bchek); lineStatus = (LineStatus)bchek; return true; } catch { lineStatus = LineStatus.Low; return false; } } public override List GetListEnum() { tSdkCameraDevInfo[] tCameraDevInfoList; List CamInfo = new List(); CameraSdkStatus status = MvApi.CameraEnumerateDevice(out tCameraDevInfoList); if (status == CameraSdkStatus.CAMERA_STATUS_SUCCESS) { foreach (var tCameraDevInfo in tCameraDevInfoList) { string sn = System.Text.Encoding.UTF8.GetString(tCameraDevInfo.acSn); // 查找第一个 \0 的位置 int nullIndex = System.Text.Encoding.UTF8.GetString(tCameraDevInfo.acSn).IndexOf('\0'); if (nullIndex != -1) { // 截取 \0 之前的部分 sn = sn.Substring(0, nullIndex); } CamInfo.Add(sn); } } return CamInfo; } public override bool GetTriggerDelay(out double delay) { try { uint value = 0; MvApi.CameraGetTriggerDelayTime(m_hCamera, ref value); delay = Convert.ToDouble(value); return true; } catch { delay = 0; return false; } } public override bool GetTriggerFliter(out double flitertime) { try { uint value = 0; MvApi.CameraGetExtTrigJitterTime(m_hCamera, ref value); flitertime = Convert.ToDouble(value); return true; } catch { flitertime = 0; return false; } } public override bool GetTriggerMode(out TriggerMode mode, out TriggerSource source) { try { int nRet = 0; MvApi.CameraGetTriggerMode(m_hCamera, ref nRet); switch (nRet) { case 0: mode = TriggerMode.Off; source = TriggerSource.Software; break; case 1: mode = TriggerMode.On; source = TriggerSource.Software; break; case 2: mode = TriggerMode.On; source = TriggerSource.Line0; break; default: mode = TriggerMode.Off; source = TriggerSource.Software; break; } return true; } catch { mode = TriggerMode.Off; source = TriggerSource.Software; return false; } } public override bool GetTriggerPolarity(out TriggerPolarity polarity) { try { int nRet = 0; MvApi.CameraGetExtTrigSignalType(m_hCamera, ref nRet); polarity = (TriggerPolarity)nRet; return true; } catch { polarity = TriggerPolarity.RisingEdge; return false; } } public override bool InitDevice(string CamSN, object handle) { if (!(handle is IntPtr) || handle == null) { MessageBox.Show("初始化失败,窗体句柄为null !", "异常"); return false; } tSdkCameraDevInfo[] tCameraDevInfoList; CameraSdkStatus status = MvApi.CameraEnumerateDevice(out tCameraDevInfoList); if (status == CameraSdkStatus.CAMERA_STATUS_SUCCESS) { foreach (var item in tCameraDevInfoList) { string acSn = System.Text.Encoding.UTF8.GetString(item.acSn); if (acSn.Contains(CamSN)) { tSdkCameraDevInfo tCameraDevInfo = item; status = MvApi.CameraGrabber_Create(out m_Grabber, ref tCameraDevInfo); if (status == 0) { MvApi.CameraGrabber_GetCameraDevInfo(m_Grabber, out m_DevInfo); MvApi.CameraGrabber_GetCameraHandle(m_Grabber, out m_hCamera); MvApi.CameraGetCapability(m_hCamera, out m_tSdkCameraCapbility); //让SDK来根据相机的型号动态创建该相机的配置窗口。 MvApi.CameraCreateSettingPage(m_hCamera, (IntPtr)handle, m_DevInfo.acFriendlyName, null, (IntPtr)0, 0); // 黑白相机设置ISP输出灰度图像 // 彩色相机ISP默认会输出BGR24图像 //获得相机特性描述 MvApi.CameraGetCapability(m_hCamera, out tCameraCapability); m_ImageBuffer = Marshal.AllocHGlobal(tCameraCapability.sResolutionRange.iWidthMax * tCameraCapability.sResolutionRange.iHeightMax * 3 + 1024); if (tCameraCapability.sIspCapacity.bMonoSensor != 0) { MvApi.CameraSetIspOutFormat(m_hCamera, (uint)MVSDK.emImageFormat.CAMERA_MEDIA_TYPE_MONO8); // 创建灰度调色板 Bitmap Image = new Bitmap(1, 1, System.Drawing.Imaging.PixelFormat.Format8bppIndexed); m_GrayPal = Image.Palette; for (int Y = 0; Y < m_GrayPal.Entries.Length; Y++) m_GrayPal.Entries[Y] = System.Drawing.Color.FromArgb(255, Y, Y, Y); } m_ImageBufferSnapshot = Marshal.AllocHGlobal(tCameraCapability.sResolutionRange.iWidthMax * tCameraCapability.sResolutionRange.iHeightMax * 3 + 1024); //设置抓拍通道的分辨率。 tSdkImageResolution tResolution; tResolution.uSkipMode = 0; tResolution.uBinAverageMode = 0; tResolution.uBinSumMode = 0; tResolution.uResampleMask = 0; tResolution.iVOffsetFOV = 0; tResolution.iHOffsetFOV = 0; tResolution.iWidthFOV = tCameraCapability.sResolutionRange.iWidthMax; tResolution.iHeightFOV = tCameraCapability.sResolutionRange.iHeightMax; tResolution.iWidth = tResolution.iWidthFOV; tResolution.iHeight = tResolution.iHeightFOV; //tResolution.iIndex = 0xff;表示自定义分辨率,如果tResolution.iWidth和tResolution.iHeight //定义为0,则表示跟随预览通道的分辨率进行抓拍。抓拍通道的分辨率可以动态更改。 //本例中将抓拍分辨率固定为最大分辨率。 tResolution.iIndex = 0xff; tResolution.acDescription = new byte[32];//描述信息可以不设置 tResolution.iWidthZoomHd = 0; tResolution.iHeightZoomHd = 0; tResolution.iWidthZoomSw = 0; tResolution.iHeightZoomSw = 0; // 设置为Off会一直触发回调 SetTriggerMode(TriggerMode.On, TriggerSource.Software); MvApi.CameraSetResolutionForSnap(m_hCamera, ref tResolution); #if USE_CALL_BACK //m_CaptureCallback = new CAMERA_SNAP_PROC(ImageCaptureCallback); //MvApi.CameraSetCallbackFunction(m_hCamera, m_CaptureCallback, m_iCaptureCallbackCtx, ref pCaptureCallOld); // 为了演示如何在回调中使用相机数据创建Bitmap并显示到PictureBox中,这里不使用SDK内置的绘制操作 //MvApi.CameraGrabber_SetHWnd(m_Grabber, this.DispWnd.Handle); //MvApi.CameraGrabber_StartLive(m_Grabber); //设置回调函数 m_FrameCallback = new pfnCameraGrabberFrameCallback(CameraGrabberFrameCallback); MvApi.CameraGrabber_SetRGBCallback(m_Grabber, m_FrameCallback, IntPtr.Zero); #else //如果需要采用多线程,使用下面的方式 m_bExitCaptureThread = false; cts = new CancellationTokenSource(); m_tCaptureThread = new Thread(() => CaptureThreadProc(cts.Token)); //m_tCaptureThread.Start(); cts.Cancel(); // 请求取消线程 #endif //设置回调函数 m_FrameCallback = new pfnCameraGrabberFrameCallback(CameraGrabberFrameCallback); MvApi.CameraGrabber_SetRGBCallback(m_Grabber, m_FrameCallback, IntPtr.Zero); //设置相机参数组 MvApi.CameraLoadParameter(m_hCamera, (int)emSdkParameterTeam.PARAMETER_TEAM_A); if (!StartGrabbing()) { Debug.WriteLine("开始采集失败"); return false; } this.SN = CamSN; return true; } else { MessageBox.Show(String.Format("打开相机失败,原因:{0}", status), "异常"); return false; } } } } return false; } private void CameraGrabberFrameCallback(IntPtr Grabber, IntPtr pFrameBuffer, ref tSdkFrameHead pFrameHead, IntPtr Context) { try { //说明相机处于实时 if (!cts.Token.IsCancellationRequested) return; // 数据处理回调 // 由于黑白相机在相机打开后设置了ISP输出灰度图像 // 因此此处pFrameBuffer=8位灰度数据 // 否则会和彩色相机一样输出BGR24数据 // 彩色相机ISP默认会输出BGR24图像 // pFrameBuffer=BGR24数据 // 执行一次GC,释放出内存 GC.Collect(); // 由于SDK输出的数据默认是从底到顶的,转换为Bitmap需要做一下垂直镜像 MvApi.CameraFlipFrameBuffer(pFrameBuffer, ref pFrameHead, 1); int w = pFrameHead.iWidth; int h = pFrameHead.iHeight; Boolean gray = (pFrameHead.uiMediaType == (uint)MVSDK.emImageFormat.CAMERA_MEDIA_TYPE_MONO8); // 如果是灰度图要设置调色板 try { if (this.ImageGrabbed != null) { Bitmap Image = new Bitmap(w, h, gray ? w : w * 3, gray ? System.Drawing.Imaging.PixelFormat.Format8bppIndexed : System.Drawing.Imaging.PixelFormat.Format24bppRgb, pFrameBuffer); if (gray) Image.Palette = m_GrayPal; this.ImageGrabbed(this, new CameraEventArgs(SN, Image)); } CallBackImg = new Bitmap(w, h, gray ? w : w * 3, gray ? System.Drawing.Imaging.PixelFormat.Format8bppIndexed : System.Drawing.Imaging.PixelFormat.Format24bppRgb, pFrameBuffer); if (gray) CallBackImg.Palette = m_GrayPal; try { if (GetTriggerMode(out TriggerMode mode, out TriggerSource source)) { if (mode == TriggerMode.On && source != TriggerSource.Software) TriggerRunMessageReceived?.Invoke(SN, source.ToString()); // 触发运行事件 } } catch { } } catch { } } catch { } } public void CaptureThreadProc(CancellationToken token) { CameraSdkStatus eStatus; tSdkFrameHead FrameHead; IntPtr uRawBuffer;//rawbuffer由SDK内部申请。应用层不要调用delete之类的释放函数 while (m_bExitCaptureThread == false || !token.IsCancellationRequested) { try { //500毫秒超时,图像没捕获到前,线程会被挂起,释放CPU,所以该线程中无需调用sleep eStatus = MvApi.CameraGetImageBuffer(m_hCamera, out FrameHead, out uRawBuffer, 500); if (eStatus == CameraSdkStatus.CAMERA_STATUS_SUCCESS)//如果是触发模式,则有可能超时 { //图像处理,将原始输出转换为RGB格式的位图数据,同时叠加白平衡、饱和度、LUT等ISP处理。 MvApi.CameraImageProcess(m_hCamera, uRawBuffer, m_ImageBuffer, ref FrameHead); //叠加十字线、自动曝光窗口、白平衡窗口信息(仅叠加设置为可见状态的)。 MvApi.CameraImageOverlay(m_hCamera, m_ImageBuffer, ref FrameHead); //调用SDK封装好的接口,显示预览图像 MvApi.CameraDisplayRGB24(m_hCamera, m_ImageBuffer, ref FrameHead); if (FrameHead.iWidth != m_tFrameHead.iWidth || FrameHead.iHeight != m_tFrameHead.iHeight) { m_bEraseBk = true; m_tFrameHead = FrameHead; } // 由于SDK输出的数据默认是从底到顶的,转换为Bitmap需要做一下垂直镜像 MvApi.CameraFlipFrameBuffer(m_ImageBuffer, ref m_tFrameHead, 1); int w = m_tFrameHead.iWidth; int h = m_tFrameHead.iHeight; Boolean gray = (m_tFrameHead.uiMediaType == (uint)MVSDK.emImageFormat.CAMERA_MEDIA_TYPE_MONO8); // 如果是灰度图要设置调色板 try { if (this.ImageGrabbed != null) { Bitmap Image = new Bitmap(w, h, gray ? w : w * 3, gray ? System.Drawing.Imaging.PixelFormat.Format8bppIndexed : System.Drawing.Imaging.PixelFormat.Format24bppRgb, m_ImageBuffer); if (gray) Image.Palette = m_GrayPal; this.ImageGrabbed(this, new CameraEventArgs(SN, Image)); } CallBackImg = new Bitmap(w, h, gray ? w : w * 3, gray ? System.Drawing.Imaging.PixelFormat.Format8bppIndexed : System.Drawing.Imaging.PixelFormat.Format24bppRgb, m_ImageBuffer); if (gray) CallBackImg.Palette = m_GrayPal; try { if (GetTriggerMode(out TriggerMode mode, out TriggerSource source)) { if (mode == TriggerMode.On && source != TriggerSource.Software) TriggerRunMessageReceived?.Invoke(SN, source.ToString()); // 触发运行事件 } } catch { } } catch { } } //成功调用CameraGetImageBuffer后必须释放,下次才能继续调用CameraGetImageBuffer捕获图像。 MvApi.CameraReleaseImageBuffer(m_hCamera, uRawBuffer); } catch { } } } public override bool SetExpouseTime(double value) { if (m_Grabber == IntPtr.Zero) return false; MvApi.CameraSetExposureTime(m_hCamera, value); return true; } public override bool SetGain(double gain) { if (m_Grabber == IntPtr.Zero) return false; MvApi.CameraSetAnalogGainX(m_hCamera, Convert.ToSingle(gain)); return true; } public override bool SetLineMode(IOLines line, LineMode mode) { if (m_Grabber == IntPtr.Zero) return false; //MvApi.CameraShowSettingPage(m_hCamera, 1); return true; } public override bool SetLineStatus(IOLines line, LineStatus linestatus) { if (m_Grabber == IntPtr.Zero) return false; //MvApi.CameraShowSettingPage(m_hCamera, 1); return true; } public override bool SetTriggerDelay(double delay) { if (m_Grabber == IntPtr.Zero) return false; MvApi.CameraSetTriggerDelayTime(m_hCamera, (uint)delay); return true; } public override bool SetTriggerFliter(double flitertime) { if (m_Grabber == IntPtr.Zero) return false; /*设置电平触发方式下的去抖时间 只有在电平触发模式下,去抖时间才有效,在边沿触发下,忽略去抖时间。 因此边沿触发方式,请一定要使用电子开关。电平触发方式加上去抖时间,可以去掉 机械开关的抖动。 */ MvApi.CameraSetExtTrigJitterTime(m_hCamera, (uint)flitertime); return true; } public override bool SetTriggerMode(TriggerMode mode, TriggerSource source = TriggerSource.None) { if (m_Grabber == IntPtr.Zero) return false; cts.Cancel(); // 请求取消线程 GetTriggerMode(out TriggerMode m, out TriggerSource s); if (m == TriggerMode.Off) { // 等待一段时间,避免实时线程未关闭 Thread.Sleep(2000); // 模拟等待 2 秒 } //0表示连续模式,1是软触发,2是硬触发。 if (mode == TriggerMode.Off) { MvApi.CameraSetTriggerMode(m_hCamera, 0); cts = new CancellationTokenSource(); m_tCaptureThread = new Thread(() => CaptureThreadProc(cts.Token)); m_tCaptureThread.Start(); return true; } else { cts.Cancel(); // 请求取消线程 } //没有提供触发源说明只改变触发模式 if (source == TriggerSource.None) GetTriggerMode(out mode, out source); //0表示连续模式,1是软触发,2是硬触发。 if (source == TriggerSource.Software) MvApi.CameraSetTriggerMode(m_hCamera, 1); else MvApi.CameraSetTriggerMode(m_hCamera, 2); return true; } public override bool SetTriggerPolarity(TriggerPolarity polarity) { if (m_Grabber == IntPtr.Zero) return false; MvApi.CameraSetExtTrigSignalType(m_hCamera, (int)polarity); ////硬件外触发的信号种类 //{ //EXT_TRIG_LEADING_EDGE = 0, //上升沿触发,默认为该方式 //EXT_TRIG_TRAILING_EDGE, //下降沿触发 //EXT_TRIG_HIGH_LEVEL, //高电平触发,电平宽度决定曝光时间,仅部分型号的相机支持电平触发方式。 //EXT_TRIG_LOW_LEVEL //低电平触发, //} /* CameraSetExtTrigSignalType(m_hCamera,0); //设置为上升沿信号触发。 CameraSetExtTrigSignalType(m_hCamera,1); //设置为下降沿信号触发。 CameraSetExtTrigSignalType(m_hCamera,2); //设置为高电平信号触发,如果一直为高电平,则会一直触发,相当于连续预览。 CameraSetExtTrigSignalType(m_hCamera,3); //设置为低电平信号触发,如果一直为低电平,则会一直触发,相当于连续预览。。 */ return true; } public override bool SoftTrigger() { if (m_Grabber == IntPtr.Zero) return false; int mode = 0; if (m_Grabber != IntPtr.Zero) { MvApi.CameraGetTriggerMode(m_hCamera, ref mode); if (mode != 0) { // 只触发模式下调用软触发指令 MvApi.CameraSoftTrigger(m_hCamera); } } return true; } public override bool StartGrabbing() { try { if (m_Grabber == IntPtr.Zero) return false; CallBackImg = null; MvApi.CameraGrabber_StartLive(m_Grabber); isGrabbing = true; return true; } catch { return false; } } public override bool StopGrabbing() { try { if (m_Grabber == IntPtr.Zero) return false; MvApi.CameraGrabber_StopLive(m_Grabber); isGrabbing = false; CallBackImg = null; return true; } catch { return false; } } public void SetSetting(int show = 1) { if (m_hCamera > 0) MvApi.CameraShowSettingPage(m_hCamera, (uint)show);//1 show ; 0 hide } public override bool GetImage(out Bitmap bitmap, int outtime = 3000) { bitmap = null; try { // 设置超时时间 DateTime lastTime = DateTime.Now.AddMilliseconds(outtime); // 判断是否超时 while (lastTime > DateTime.Now)// 设置超时时间为 3 秒 { try { if (CallBackImg != null) { // 保存旧 Bitmap 并释放 bitmap = CallBackImg.Clone() as Bitmap; // 创建副本 // 释放旧资源 CallBackImg.Dispose(); CallBackImg = null; return true; } } catch { } Thread.Sleep(10); } return false; } catch { return bitmap == null ? false : true; } } public override bool GetImageWithSoftTrigger(out Bitmap bitmap, int outtime = 3000) { if (!isGrabbing) { StartGrabbing(); } GetTriggerMode(out TriggerMode triggerMode, out TriggerSource triggerSource); if (triggerMode != TriggerMode.On && triggerSource != TriggerSource.Software) { SetTriggerMode(TriggerMode.On, TriggerSource.Software); } bitmap = null; CallBackImg = null; if (!SoftTrigger()) { return false; } // 开始时间 DateTime startTime = DateTime.Now; // 当前时间 // 判断是否超时 while (DateTime.Now < startTime.AddMilliseconds(outtime))// 设置超时时间为 3 秒 { GetImage(out bitmap, 50); if (bitmap != null) { break; } Thread.Sleep(10); } if (triggerMode != TriggerMode.On) { SetTriggerMode(TriggerMode.On, triggerSource); } return (bitmap != null); } public override void SetCamConfig(CameraConfig config) { if (Enum.TryParse(config.Params.Inputs["触发模式"].ToString(), out TriggerMode TriggerMode) && Enum.TryParse(config.Params.Inputs["触发方式"].ToString(), out TriggerSource TriggerSource) && Enum.TryParse(config.Params.Inputs["触发极性"].ToString(), out TriggerPolarity TriggerPolarity) ) { SetTriggerMode(TriggerMode, TriggerSource); SetTriggerPolarity(TriggerPolarity); //SetTriggerFliter(Convert.ToDouble(config.Params.Inputs["触发消抖"].ToString())); //SetTriggerDelay(Convert.ToDouble(config.Params.Inputs["触发延时"].ToString())); SetExpouseTime(Convert.ToDouble(config.Params.Inputs["曝光时间"].ToString())); SetGain(Convert.ToDouble(config.Params.Inputs["增益"].ToString())); } } public override void GetCamConfig(out CameraConfig config) { GetTriggerMode(out TriggerMode triggerMode, out TriggerSource triggerSource); GetTriggerPolarity(out TriggerPolarity triggerPolarity); //GetTriggerFliter(out double triggerfilter); //GetTriggerDelay(out double triggerdelay); GetExpouseTime(out double expouseTime); GetGain(out double gain); config = new CameraConfig(null); config.Params.Inputs.Add("触发模式", triggerMode); config.Params.Inputs.Add("触发方式", triggerSource); config.Params.Inputs.Add("触发极性", triggerPolarity); //config.Params.Inputs.Add("触发消抖", triggerfilter); //config.Params.Inputs.Add("触发延时", triggerdelay); config.Params.Inputs.Add("曝光时间", expouseTime); config.Params.Inputs.Add("增益", gain); } public override bool StartWith_SoftTriggerModel() { SetTriggerMode(TriggerMode.Off, TriggerSource.Software); return StartGrabbing(); } public override bool StartWith_HardTriggerModel(TriggerSource hardtriggeritem = TriggerSource.Line0) { if (hardtriggeritem == TriggerSource.Software) hardtriggeritem = TriggerSource.Line0; SetTriggerMode(TriggerMode.On, hardtriggeritem); return StartGrabbing(); } public override bool StartContinuousGrab() { throw new NotImplementedException(); } } }