using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using TeamAAS.Camera.Enums; using TeamAAS.Camera.Images; using TeamAAS.Camera.Interfaces; namespace TeamAAS.Camera.Cameras { /// /// 相机基类:封装所有品牌相机的通用功能。 /// 所有 SDK 操作均通过 _cameraLock(可重入读写锁)保护,防止多线程并发访问同一相机。 /// public abstract class CameraBase : ICamera, INotifyPropertyChanged { #region 字段 protected Thread m_hReceiveThread; protected bool _isHaveCamera = false; /// /// 相机操作互斥锁(可重入)。 /// 所有与 SDK 的交互(开/关/取相/参数设置)必须在写锁内执行。 /// private readonly ReaderWriterLockSlim _cameraLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); #endregion #region 基本信息 private string _name; [Category("I.基本信息"), DisplayName("1.名称"), Description("自定义相机名称")] public string Name { get => _name; set => SetProperty(ref _name, value); } [Category("I.基本信息"), DisplayName("2.曝光时间(μs)"), Description("曝光时间,单位微秒")] public float ExposureTime { get => ReadParameter(GetExposureTime, ref _exposureTime); set => SetImageParameter( () => SetExposureTime(value), () => { _exposureTime = value; _hasExposure = true; }, value, $"曝光时间 {value}μs", nameof(ExposureTime)); } [Category("I.基本信息"), DisplayName("3.增益(亮度)"), Description("增益越高画面越亮,噪点也随之增大")] public float Gain { get => ReadParameter(GetGain, ref _gain); set => SetImageParameter( () => SetGain(value), () => { _gain = value; _hasGain = true; }, value, $"增益 {value}", nameof(Gain)); } [Category("I.基本信息"), DisplayName("4.Gamma"), Description("Gamma 校正系数,默认 1.0 表示不校正")] public float Gamma { get => ReadParameter(GetGamma, ref _gamma); set => SetImageParameter( () => SetGamma(value), () => { UpdateGammaSnapshot(value); _hasGamma = true; }, value, $"Gamma {value}", nameof(Gamma)); } #endregion #region 触发模式 /// 所有相机都具备的最小能力集:跟随相机(不下发)+ 连续采集。 protected static readonly IReadOnlyList ContinuousOnlyTriggerModes = new[] { CameraTriggerMode.跟随相机, CameraTriggerMode.连续采集 }; /// 全功能触发模式集,供支持软/硬触发的工业相机直接复用。 protected static readonly IReadOnlyList FullTriggerModes = new[] { CameraTriggerMode.跟随相机, CameraTriggerMode.连续采集, CameraTriggerMode.软触发, CameraTriggerMode.硬触发上升沿, CameraTriggerMode.硬触发下降沿, }; /// /// 本相机支持的触发模式(默认仅“跟随相机/连续采集”)。 /// 子类按自家 SDK 能力重写:支持软/硬触发的直接返回 ; /// 无触发能力的(如 UVC 网络摄像头)保持默认即可——上层下拉列表会随之增减。 /// [Browsable(false)] public virtual IReadOnlyList SupportedTriggerModes => ContinuousOnlyTriggerModes; /// /// 相机连接后的默认触发模式。子类若原先在 OpenDevice 里硬编码了触发设置, /// 必须在此返回等价模式,以保证既有行为不变(如 Basler/OPT 原为软触发)。 /// protected virtual CameraTriggerMode DefaultTriggerMode => CameraTriggerMode.跟随相机; private CameraTriggerMode _triggerMode = CameraTriggerMode.跟随相机; /// 当前生效的触发模式 [Browsable(false)] public CameraTriggerMode TriggerMode { get => _triggerMode; protected set => SetProperty(ref _triggerMode, value); } /// 当前是否软触发:子类 Grab 据此决定是否发 TriggerSoftware 命令 [Browsable(false)] public bool IsSoftwareTrigger => _triggerMode == CameraTriggerMode.软触发; /// 当前是否外部硬触发:取相需等待外部信号,无信号会超时 [Browsable(false)] public bool IsExternalTrigger => _triggerMode == CameraTriggerMode.硬触发上升沿 || _triggerMode == CameraTriggerMode.硬触发下降沿; /// 该相机是否支持指定触发模式(“跟随相机”恒支持,因为它不下发任何设置) public bool SupportsTriggerMode(CameraTriggerMode mode) => mode == CameraTriggerMode.跟随相机 || (SupportedTriggerModes != null && SupportedTriggerModes.Contains(mode)); /// /// 设置触发模式:先按 校验能力,再交子类下发硬件。 /// 无论下发成败都落地期望值快照(相机未连接时由 OpenDevice 补下发)。 /// public virtual bool SetTriggerMode(CameraTriggerMode mode) { // “跟随相机”= 不接管触发设置,保持相机当前生效模式。 // 必须原封不动返回:若把快照改成“跟随相机”,软触发相机会停发 TriggerSoftware // 而硬件仍在 TriggerMode=On 等触发,导致取相一直超时卡死。 if (mode == CameraTriggerMode.跟随相机) { ErrorMessage = null; return true; } if (!SupportsTriggerMode(mode)) { ErrorMessage = $"{Name} 不支持触发模式「{mode}」(支持:{string.Join("、", SupportedTriggerModes ?? ContinuousOnlyTriggerModes)})"; return false; } try { bool ok = ApplyTriggerMode(mode); ErrorMessage = ok ? null : $"设置触发模式「{mode}」失败(若相机未连接,连接后将自动应用)"; return ok; } catch (Exception ex) { ErrorMessage = $"设置触发模式异常: {ex.Message}"; return false; } finally { TriggerMode = mode; } } /// /// 子类实现:把触发模式下发到 SDK。基类默认空实现(仅记录快照),适用于无触发能力的相机。 /// 若 SDK 调用需要互斥,实现内自行用 (锁可重入)。 /// protected virtual bool ApplyTriggerMode(CameraTriggerMode mode) => true; #endregion #region 公共属性 public abstract string CameraBrand { get; } [Browsable(false)] public Guid ID { get; protected set; } [Browsable(false)] public int Index { get; set; } [Category("杂项"), DisplayName("1.厂商"), ReadOnly(true)] public string ManufacturerName { get; protected set; } [Category("杂项"), DisplayName("2.型号"), ReadOnly(true)] public string ModelName { get; protected set; } [Category("杂项"), DisplayName("3.序列号"), ReadOnly(true)] public string SerialNumber { get; protected set; } [Category("杂项"), DisplayName("4.IP地址"), ReadOnly(true)] public string CameraIp { get; protected set; } private bool _isConnected; [Category("杂项"), DisplayName("5.连接状态"), ReadOnly(true)] public bool IsConnected { get { return _isConnected; } protected set { SetProperty(ref _isConnected, value); } } #endregion #region Orthor [Browsable(false)] public bool IsFindByIp { get; protected set; } private bool _isGrabbing; [Browsable(false)] public bool IsGrabbing { get { return _isGrabbing; } protected set { SetProperty(ref _isGrabbing, value); } } private GenImage _image; [Browsable(false)] public GenImage Image { get { return _image; } protected set { SetProperty(ref _image, value); } } [Browsable(false)] public CameraType CameraType { get; protected set; } [Browsable(false)] public UInt32 ImageWidth { get; protected set; } [Browsable(false)] public UInt32 ImageHeight { get; protected set; } [Browsable(false)] public TimeSpan TotalTime { get; protected set; } [Browsable(false)] public string ErrorMessage { get; protected set; } #endregion #region 事件 /// /// 取相 /// public event Action ImageCallbackEvent; public event Action CameraConnectChangedEvent; #endregion #region 构造函数 protected CameraBase(Guid id, int index, string name, string serialNumber, string cameraIp, bool isFindByIp) { ID = id; Name = name; Index = index; CameraIp = cameraIp; SerialNumber = serialNumber; IsFindByIp = isFindByIp; IsConnected = false; // 按子类声明的默认触发模式初始化快照(等价于原来 OpenDevice 里的硬编码设置) _triggerMode = DefaultTriggerMode; } #endregion #region 线程同步辅助 /// /// 获取写锁作用域 — 用于所有 SDK 写操作(开/关/取相/参数设置)。 /// 使用方式:using (WriteLockScope()) { ... } /// protected IDisposable WriteLockScope() { _cameraLock.EnterWriteLock(); return new WriteLockDisposable(_cameraLock); } /// /// 获取读锁作用域 — 用于只读 SDK 操作(获取参数值)。 /// protected IDisposable ReadLockScope() { _cameraLock.EnterReadLock(); return new ReadLockDisposable(_cameraLock); } /// /// 获取写锁(无 using 模式时使用,需手动调用 WriteUnlock)。 /// protected void WriteLock() { _cameraLock.EnterWriteLock(); } /// /// 释放写锁。 /// protected void WriteUnlock() { _cameraLock.ExitWriteLock(); } /// /// 获取读锁(无 using 模式时使用,需手动调用 ReadUnlock)。 /// protected void ReadLock() { _cameraLock.EnterReadLock(); } /// /// 释放读锁。 /// protected void ReadUnlock() { _cameraLock.ExitReadLock(); } private sealed class WriteLockDisposable : IDisposable { private readonly ReaderWriterLockSlim _lock; public WriteLockDisposable(ReaderWriterLockSlim rwLock) { _lock = rwLock; } public void Dispose() { _lock.ExitWriteLock(); } } private sealed class ReadLockDisposable : IDisposable { private readonly ReaderWriterLockSlim _lock; public ReadLockDisposable(ReaderWriterLockSlim rwLock) { _lock = rwLock; } public void Dispose() { _lock.ExitReadLock(); } } #endregion #region 通用实现(不需要子类重写) private int _readingParameters; private float _exposureTime; private float _gain; private float _gamma = 1.0f; // 用户是否显式设置过对应参数(含从配置加载)。只有设置过才会在连接/采集时自动下发, // 避免把默认值(曝光 0、增益 0)强行推给硬件。 private bool _hasExposure; private bool _hasGain; private bool _hasGamma; /// /// 参数写入统一路径:先尝试下发硬件(无论成败),再把期望值落地到本地快照。 /// 失败/异常只记 ErrorMessage 并返回——相机未连接时用户设置的值不会丢, /// 连接后会经 TryApplyImageParameters 自动应用。 /// private void SetImageParameter(Func apply, Action updateSnapshot, float value, string label, string notifyProperty) { try { bool ok = apply(); ErrorMessage = ok ? null : $"设置{label}失败(若相机未连接,连接后将自动应用)"; } catch (Exception ex) { ErrorMessage = $"设置{label}异常: {ex.Message}"; } finally { updateSnapshot(); } RaisePropertyChanged(notifyProperty); } /// /// 把本地保存的期望参数值推送到硬件(幂等,可重复调用)。 /// 触发时机:连接成功(OpenDeviceAsync)、开始采集(StartGrabbing)、配置初始化后首连。 /// 仅推送用户显式设置过的参数,避免把默认值强写到硬件。 /// public void TryApplyImageParameters() { if (!IsConnected) return; // 自动曝光是相机品牌特有(UVC)的运行时开关,不在这里处理; // 各品牌的 SetExposureTime/SetGain 会自行判断是否处于自动模式,并据此忽略手动写入。 if (_hasExposure) { try { SetExposureTime(_exposureTime); } catch { } } if (_hasGain) { try { SetGain(_gain); } catch { } } if (_hasGamma) { try { SetGamma(_gamma); } catch { } } } /// /// 从 SDK 读取参数值并同步到快照。 /// 重入防护:子类 GetXxx 的兜底若再读本属性(_readingParameters > 0),直接返回快照阻断递归; /// SDK 异常(未连接等)同样回退快照。计数为实例级、非原子 —— 本属性仅由 UI/业务线程按序访问。 /// private float ReadParameter(Func read, ref float snapshot) { if (_readingParameters > 0) return snapshot; _readingParameters++; try { snapshot = read(); return snapshot; } catch { return snapshot; } finally { _readingParameters--; } } /// /// 仅更新本地 Gamma 快照并通知 UI(不写硬件),供基类默认实现与子类兜底路径使用。 /// 注意:子类内部请勿写 "Gamma = x"——属性 setter 会再次进入 SetGamma 造成无限递归。 /// protected void UpdateGammaSnapshot(float gamma) { SetProperty(ref _gamma, gamma); } /// /// 打开相机异步(通用实现,包装 OpenDevice)。 /// 打开成功后自动把本地保存的参数(曝光/增益/Gamma)下发到硬件。 /// public Task OpenDeviceAsync() { return Task.Run(() => { bool opened = OpenDevice(); if (opened) TryApplyImageParameters(); return opened; }); } /// /// 开始实时采集图像(通用线程管理)。启动前把待应用的参数快照推送到硬件。 /// public virtual void StartGrabbing() { if (IsConnected) TryApplyImageParameters(); using (WriteLockScope()) { if (IsGrabbing) return; IsGrabbing = true; m_hReceiveThread = new Thread(GetStreamThreadProc) { IsBackground = true }; m_hReceiveThread.Start(); } } /// /// 停止实时采集图像(通用线程管理) /// public virtual void StopGrabbing() { using (WriteLockScope()) { try { IsGrabbing = false; if (m_hReceiveThread != null && m_hReceiveThread.IsAlive) { m_hReceiveThread.Abort(); m_hReceiveThread = null; } } catch { } } } /// /// 实时采集线程过程(通用实现) /// protected virtual void GetStreamThreadProc() { while (IsGrabbing) { try { Grab(); ImageCallbackEvent?.Invoke(Image, TotalTime, ErrorMessage); } catch { } Thread.Sleep(10); } IsGrabbing = false; } /// /// 批量设置相机参数(子类可重写以支持更多参数) /// public virtual void ApplyParameters(Dictionary parameters) { if (parameters == null) return; using (WriteLockScope()) { foreach (var kv in parameters) { try { switch (kv.Key.ToLower()) { case "exposure": case "exposuretime": if (kv.Value is float f1) SetExposureTime(f1); else if (kv.Value is double d1) SetExposureTime((float)d1); break; case "gain": if (kv.Value is float f2) SetGain(f2); else if (kv.Value is double d2) SetGain((float)d2); break; case "gamma": if (kv.Value is float f3) SetGamma(f3); else if (kv.Value is double d3) SetGamma((float)d3); break; } } catch { } } } } /// /// 获取当前相机参数快照 /// public virtual Dictionary GetParameters() { var dict = new Dictionary(); using (ReadLockScope()) { try { dict["ExposureTime"] = GetExposureTime(); } catch { } try { dict["Gain"] = GetGain(); } catch { } try { dict["Gamma"] = GetGamma(); } catch { } } return dict; } #endregion #region 保护方法 /// /// 触发连接状态变更事件 /// protected void OnConnectChanged(bool connected) { CameraConnectChangedEvent?.Invoke(ID, connected); } /// /// 设置错误信息并返回 null /// protected GenImage SetError(string message) { ErrorMessage = message; return null; } /// /// 对图像应用当前 Gamma 校正(软件后处理)。 /// 子类应在 Grab() 成功获取图像后调用此方法。 /// protected void ApplyGammaToImage(GenImage image) { if (image == null) return; if (Math.Abs(_gamma - 1.0f) < 0.001f) return; image.ApplyGamma(_gamma); image.SetMetadata("Gamma", _gamma); } /// /// 终处理:对图像应用 Gamma、写入 Metadata,返回处理后的图像。 /// 子类应在 Grab() 成功获取图像后调用。 /// protected GenImage FinalizeImage(GenImage image) { if (image == null) return null; ApplyGammaToImage(image); image.SetMetadata("CameraName", Name); image.SetMetadata("Timestamp", DateTime.Now); return image; } #endregion #region 抽象方法(子类必须实现,必须在方法体开头使用 WriteLockScope/ReadLockScope) /// 打开相机 public abstract bool OpenDevice(); /// 关闭相机 public abstract void CloseDevice(); /// 采集单张图片,返回通用 GenImage public abstract GenImage Grab(); /// 设置曝光时间 public abstract bool SetExposureTime(float exposureTime); /// 获取曝光时间 public abstract float GetExposureTime(); /// 设置增益 public abstract bool SetGain(float gain); /// 获取增益 public abstract float GetGain(); /// /// 设置 Gamma 值(默认实现为软件后处理,子类可重写以支持硬件 Gamma)。 /// 实现内部只允许通过 更新快照,禁止给 Gamma 属性赋值(会递归)。 /// public virtual bool SetGamma(float gamma) { UpdateGammaSnapshot(gamma); return true; } /// /// 获取当前 Gamma 值(直接返回本地快照字段,不经过 Gamma 属性——避免与属性 getter 互相递归) /// public virtual float GetGamma() => _gamma; #endregion #region INotifyPropertyChanged public event PropertyChangedEventHandler PropertyChanged; protected virtual bool SetProperty(ref T storage, T value, [CallerMemberName] string propertyName = null) { if (EqualityComparer.Default.Equals(storage, value)) return false; storage = value; RaisePropertyChanged(propertyName); return true; } protected virtual bool SetProperty(ref T storage, T value, Action onChanged, [CallerMemberName] string propertyName = null) { if (EqualityComparer.Default.Equals(storage, value)) return false; storage = value; onChanged?.Invoke(); RaisePropertyChanged(propertyName); return true; } protected void RaisePropertyChanged([CallerMemberName] string propertyName = null) { OnPropertyChanged(new PropertyChangedEventArgs(propertyName)); } protected virtual void OnPropertyChanged(PropertyChangedEventArgs args) { PropertyChanged?.Invoke(this, args); } #endregion } }