using Microsoft.Win32; using System.ComponentModel; using SciCamera.Net; using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.Drawing.Imaging; using System.Linq; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using TeamAAS.Camera.Images; using TeamAAS.Camera.Models; using TeamAAS.Camera.Attributes; using TeamAAS.Camera.Enums; namespace TeamAAS.Camera.Cameras { /// /// 奥普特相机(SciCamera.Net SDK) /// [Camera("OPT", "奥普特")] public class OptCamera : CameraBase { #region 字段 private readonly object _stateLock = new object(); private TaskCompletionSource _tcs; SciCam mvCameraAcq; [Browsable(false)] public SciCam.SCI_DEVICE_INFO CameraInfo { get; private set; } [Browsable(false)] public SciCam.SciCamPixelType PixelType { get; private set; } [Browsable(false)] public SciCam.SciCamGrabStrategy CurrentGrabStrategy { get; private set; } = SciCam.SciCamGrabStrategy.SciCam_GrabStrategy_Latest; private bool IsHaveCamera = false; #endregion [Browsable(false)] public override string CameraBrand => "奥普特"; public OptCamera(Guid id, int index, string name, string serialNumber, string cameraIp, bool isFindByIp) : base(id, index, name, serialNumber, cameraIp, isFindByIp) { mvCameraAcq = new SciCam(); SciCam.SCI_DEVICE_INFO_LIST stDeviceList = new SciCam.SCI_DEVICE_INFO_LIST(); uint nReVal = SciCam.DiscoveryDevices(ref stDeviceList, (uint)(SciCam.SciCamTLType.SciCam_TLType_Gige)); if (nReVal != SciCam.SCI_CAMERA_OK) { return; } for (int i = 0; i < stDeviceList.count; i++) { SciCam.SCI_DEVICE_INFO device = stDeviceList.pDevInfo[i]; SciCam.SciCamTLType devTlType = device.tlType; if (devTlType == SciCam.SciCamTLType.SciCam_TLType_Gige) { SciCam.SCI_DEVICE_GIGE_INFO gigeDevInfo = (SciCam.SCI_DEVICE_GIGE_INFO)SciCam.ByteToStruct(device.info.gigeInfo, typeof(SciCam.SCI_DEVICE_GIGE_INFO)); uint ip1 = gigeDevInfo.ip; int nIp1 = (int)(ip1 & 0x000000ff); int nIp2 = (int)((ip1 & 0x0000ff00) >> 8); int nIp3 = (int)((ip1 & 0x00ff0000) >> 16); int nIp4 = (int)((ip1 & 0xff000000) >> 24); string ip = string.Format("{0}.{1}.{2}.{3}", nIp1, nIp2, nIp3, nIp4); if (IsFindByIp) { if (CameraIp == ip) { ManufacturerName = gigeDevInfo.manufactureName; ModelName = gigeDevInfo.modelName; SerialNumber = gigeDevInfo.serialNumber; CameraType = CameraType.GIGE; CameraInfo = device; IsHaveCamera = true; break; } } else { if (string.Equals(gigeDevInfo.serialNumber, serialNumber)) { ManufacturerName = gigeDevInfo.manufactureName; ModelName = gigeDevInfo.modelName; SerialNumber = gigeDevInfo.serialNumber; CameraType = CameraType.GIGE; CameraInfo = device; IsHaveCamera = true; break; } } } } IsConnected = false; } #region 设备发现 public static CameraInfo[] GetDevices() { List cameras = new List(); try { if (!CheckSoftwareInstalled().isInstalled) { return cameras.ToArray(); } SciCam.SCI_DEVICE_INFO_LIST stDeviceList = new SciCam.SCI_DEVICE_INFO_LIST(); uint nReVal = SciCam.DiscoveryDevices(ref stDeviceList, (uint)(SciCam.SciCamTLType.SciCam_TLType_Gige)); if (nReVal != SciCam.SCI_CAMERA_OK) { return cameras.ToArray(); } for (int i = 0; i < stDeviceList.count; i++) { SciCam.SCI_DEVICE_INFO device = stDeviceList.pDevInfo[i]; SciCam.SciCamTLType devTlType = device.tlType; if (devTlType == SciCam.SciCamTLType.SciCam_TLType_Gige) { SciCam.SCI_DEVICE_GIGE_INFO gigeDevInfo = (SciCam.SCI_DEVICE_GIGE_INFO)SciCam.ByteToStruct(device.info.gigeInfo, typeof(SciCam.SCI_DEVICE_GIGE_INFO)); uint ip1 = gigeDevInfo.ip; int nIp1 = (int)(ip1 & 0x000000ff); int nIp2 = (int)((ip1 & 0x0000ff00) >> 8); int nIp3 = (int)((ip1 & 0x00ff0000) >> 16); int nIp4 = (int)((ip1 & 0xff000000) >> 24); string ip = string.Format("{0}.{1}.{2}.{3}", nIp1, nIp2, nIp3, nIp4); cameras.Add(new CameraInfo() { CameraNo = i + 1, CameraName = gigeDevInfo.name, CameraBrand = "奥普特", CameraType = CameraType.GIGE, ManufacturerName = gigeDevInfo.manufactureName, Model = gigeDevInfo.modelName, SerialNumber = gigeDevInfo.serialNumber, CameraIp = ip, IsFindByIp = true, }); } } } catch (Exception) { } return cameras.ToArray(); } public static (bool isInstalled, string version) CheckSoftwareInstalled() { return (true, "4.0.0.3"); } #endregion #region SDK 实现 public override bool OpenDevice() { using (WriteLockScope()) { try { SciCam.SCI_DEVICE_INFO stDeviceInfo = CameraInfo; uint nReVal = mvCameraAcq.CreateDevice(ref stDeviceInfo); if (nReVal != SciCam.SCI_CAMERA_OK) { throw new Exception("Create Device Fail!"); } nReVal = mvCameraAcq.OpenDevice(); // 按当前触发模式下发(默认软触发,与历史硬编码行为一致);AcquisitionMode 由该方法一并置 Continuous ApplyTriggerToHardware(TriggerMode); nReVal = mvCameraAcq.SetGrabStrategy(CurrentGrabStrategy); if (!IsConnected) { OnConnectChanged(true); } mvCameraAcq.SetGrabTimeout(5000); IsConnected = mvCameraAcq.IsDeviceOpen(); if (IsConnected) { mvCameraAcq.StartGrabbing(); } return IsConnected; } catch (Exception ex) { ErrorMessage = ex.Message; IsConnected = false; return false; } } } public override void CloseDevice() { using (WriteLockScope()) { try { if (mvCameraAcq != null) { if (mvCameraAcq.IsDeviceOpen()) { mvCameraAcq.StopGrabbing(); mvCameraAcq.CloseDevice(); } } } catch { } IsConnected = false; OnConnectChanged(false); } } public override GenImage Grab() { using (WriteLockScope()) { var sw = Stopwatch.StartNew(); try { if (mvCameraAcq == null || !mvCameraAcq.IsDeviceOpen()) { ErrorMessage = "相机未连接"; return null; } if (!mvCameraAcq.IsGrabbing()) { mvCameraAcq.StartGrabbing(); } IntPtr payload = IntPtr.Zero; // 仅软触发模式需主动发触发命令;连续采集自由出图,硬触发等外部信号 bool triggerOk = true; if (IsSoftwareTrigger) triggerOk = mvCameraAcq.SetCommandValueEx( SciCam.SciCamDeviceXmlType.SciCam_DeviceXml_Camera, "TriggerSoftware") == SciCam.SCI_CAMERA_OK; if (triggerOk) { uint nReVal = mvCameraAcq.Grab(ref payload); if (nReVal == SciCam.SCI_CAMERA_OK) { Image = GetConvertedInfo(payload); mvCameraAcq.FreePayload(payload); if (Image != null) { ImageWidth = (uint)Image.Width; ImageHeight = (uint)Image.Height; ApplyGammaToImage(Image); TotalTime = sw.Elapsed; ErrorMessage = ""; return Image; } } else { CloseDevice(); OpenDevice(); ErrorMessage = "图像采集失败!"; } } else { CloseDevice(); OpenDevice(); ErrorMessage = "软触发失败!"; } } catch (Exception ex) { CloseDevice(); OpenDevice(); ErrorMessage = ex.Message; } return null; } } #region 触发模式 /// OPT(SciCamera)支持连续/软触发/硬触发(Line0) public override IReadOnlyList SupportedTriggerModes => FullTriggerModes; /// 历史行为:OpenDevice 硬编码 TriggerMode=On + TriggerSource=Software,故默认软触发 protected override CameraTriggerMode DefaultTriggerMode => CameraTriggerMode.软触发; protected override bool ApplyTriggerMode(CameraTriggerMode mode) { using (WriteLockScope()) return ApplyTriggerToHardware(mode); } /// /// 把触发模式下发到 SciCamera SDK(GenICam 字符串接口)。 /// 调用方需已持有写锁(OpenDevice/Grab 内部已在锁内;锁可重入)。 /// private bool ApplyTriggerToHardware(CameraTriggerMode mode) { if (mvCameraAcq == null || !mvCameraAcq.IsDeviceOpen()) return false; var xml = SciCam.SciCamDeviceXmlType.SciCam_DeviceXml_Camera; mvCameraAcq.SetEnumValueByString("AcquisitionMode", "Continuous"); switch (mode) { case CameraTriggerMode.连续采集: return mvCameraAcq.SetEnumValueByStringEx(xml, "TriggerMode", "Off") == SciCam.SCI_CAMERA_OK; case CameraTriggerMode.软触发: mvCameraAcq.SetEnumValueByString("TriggerSource", "Software"); return mvCameraAcq.SetEnumValueByStringEx(xml, "TriggerMode", "On") == SciCam.SCI_CAMERA_OK; case CameraTriggerMode.硬触发上升沿: case CameraTriggerMode.硬触发下降沿: mvCameraAcq.SetEnumValueByString("TriggerSource", "Line0"); mvCameraAcq.SetEnumValueByString("TriggerActivation", mode == CameraTriggerMode.硬触发上升沿 ? "Rising" : "Falling"); return mvCameraAcq.SetEnumValueByStringEx(xml, "TriggerMode", "On") == SciCam.SCI_CAMERA_OK; default: // 跟随相机:不下发,沿用相机自身配置 return true; } } #endregion public override bool SetExposureTime(float exposureTime) { using (WriteLockScope()) { try { if (mvCameraAcq != null && mvCameraAcq.IsDeviceOpen()) { mvCameraAcq.SetFloatValue("ExposureTime", exposureTime); } return true; } catch { return false; } } } public override float GetExposureTime() { using (ReadLockScope()) { try { if (mvCameraAcq != null && mvCameraAcq.IsDeviceOpen()) { return 0; } } catch { } return 0; } } public override bool SetGain(float gain) { using (WriteLockScope()) { try { if (mvCameraAcq != null && mvCameraAcq.IsDeviceOpen()) { mvCameraAcq.SetFloatValue("Gain", gain); } return true; } catch { return false; } } } public override float GetGain() { using (ReadLockScope()) { try { if (mvCameraAcq != null && mvCameraAcq.IsDeviceOpen()) { return 0; } } catch { } return 0; } } public override bool SetGamma(float gamma) { using (WriteLockScope()) { try { if (mvCameraAcq != null && mvCameraAcq.IsDeviceOpen()) { mvCameraAcq.SetFloatValue("Gamma", gamma); return true; } } catch { } UpdateGammaSnapshot(gamma); return true; } } public override float GetGamma() { using (ReadLockScope()) { try { if (mvCameraAcq != null && mvCameraAcq.IsDeviceOpen()) { return Gamma; } } catch { } return Gamma; } } #endregion #region 辅助方法 void ImageCallBack(IntPtr payload, IntPtr tag) { try { if (payload == IntPtr.Zero) return; uint nReVal = SciCam.SCI_CAMERA_OK; SciCam.SCI_CAM_PAYLOAD_ATTRIBUTE attribute = new SciCam.SCI_CAM_PAYLOAD_ATTRIBUTE(); nReVal = SciCam.PayloadGetAttribute(payload, ref attribute); if (nReVal != SciCam.SCI_CAMERA_OK || !attribute.isComplete) { _tcs?.TrySetException(new Exception($"Failed to get image attributes or frame is incomplete! nRet: {nReVal}")); } else { Image = GetConvertedInfo(payload); _tcs?.TrySetResult(Image); } } catch (Exception ex) { _tcs?.TrySetException(new Exception($"相机采集错误: {ex.Message}")); } } private GenImage GetConvertedInfo(IntPtr payload) { if (payload == IntPtr.Zero) { return null; } SciCam.SCI_CAM_PAYLOAD_ATTRIBUTE payloadAttribute = new SciCam.SCI_CAM_PAYLOAD_ATTRIBUTE(); uint nReVal = SciCam.PayloadGetAttribute(payload, ref payloadAttribute); if (nReVal != SciCam.SCI_CAMERA_OK) { return null; } bool imgIsComplete = payloadAttribute.isComplete; SciCam.SciCamPayloadMode payloadMode = payloadAttribute.payloadMode; SciCam.SciCamPixelType imgPixelType = payloadAttribute.imgAttr.pixelType; ulong imgWidth = payloadAttribute.imgAttr.width; ulong imgHeight = payloadAttribute.imgAttr.height; ulong framID = payloadAttribute.frameID; if (!imgIsComplete || payloadMode != SciCam.SciCamPayloadMode.SciCam_PayloadMode_2D) { return null; } IntPtr imgData = IntPtr.Zero; nReVal = SciCam.PayloadGetImage(payload, ref imgData); if (nReVal != SciCam.SCI_CAMERA_OK) { return null; } long destImgSize = 0; if (imgPixelType == SciCam.SciCamPixelType.Mono1p || imgPixelType == SciCam.SciCamPixelType.Mono2p || imgPixelType == SciCam.SciCamPixelType.Mono4p || imgPixelType == SciCam.SciCamPixelType.Mono8s || imgPixelType == SciCam.SciCamPixelType.Mono8 || imgPixelType == SciCam.SciCamPixelType.Mono10 || imgPixelType == SciCam.SciCamPixelType.Mono10p || imgPixelType == SciCam.SciCamPixelType.Mono12 || imgPixelType == SciCam.SciCamPixelType.Mono12p || imgPixelType == SciCam.SciCamPixelType.Mono14 || imgPixelType == SciCam.SciCamPixelType.Mono16 || imgPixelType == SciCam.SciCamPixelType.Mono10Packed || imgPixelType == SciCam.SciCamPixelType.Mono12Packed || imgPixelType == SciCam.SciCamPixelType.Mono14p) { nReVal = SciCam.PayloadConvertImage(ref payloadAttribute.imgAttr, imgData, SciCam.SciCamPixelType.Mono8, IntPtr.Zero, ref destImgSize, true); if (nReVal == SciCam.SCI_CAMERA_OK) { IntPtr destImg = Marshal.AllocHGlobal((int)destImgSize); try { nReVal = SciCam.PayloadConvertImage(ref payloadAttribute.imgAttr, imgData, SciCam.SciCamPixelType.Mono8, destImg, ref destImgSize, true); if (nReVal == SciCam.SCI_CAMERA_OK) { // destImg 此时为 Mono8 交错的灰度数据;GenImage.FromIntPtr 内部会 Marshal.Copy 出独立副本,可在 finally 中安全释放 destImg return GenImage.FromIntPtr((int)imgWidth, (int)imgHeight, Images.PixelType.Grey8, destImg, (int)imgWidth); } } finally { Marshal.FreeHGlobal(destImg); } } } else { nReVal = SciCam.PayloadConvertImage(ref payloadAttribute.imgAttr, imgData, SciCam.SciCamPixelType.RGB8, IntPtr.Zero, ref destImgSize, true); if (nReVal == SciCam.SCI_CAMERA_OK) { IntPtr destImg = Marshal.AllocHGlobal((int)destImgSize); try { nReVal = SciCam.PayloadConvertImage(ref payloadAttribute.imgAttr, imgData, SciCam.SciCamPixelType.RGB8, destImg, ref destImgSize, true); if (nReVal == SciCam.SCI_CAMERA_OK) { // destImg 此时为交错 RGB8(R-G-B)数据,可直接用于 GenImage.RGB24 return GenImage.FromIntPtr((int)imgWidth, (int)imgHeight, Images.PixelType.RGB24, destImg, (int)imgWidth * 3); } } finally { Marshal.FreeHGlobal(destImg); } } } return null; } #endregion } }