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
}
}