C3032
2026-01-08 37aff9db0932e3e274b3c26650060f7d2d336888
添加增益选择下拉框并优化相机初始化逻辑

在 `2DCameraForm.cs` 中添加了增益选择下拉框 `cmbGain`,并在加载时初始化。新增的事件处理方法确保相机增益与下拉框选择同步。修改了 `btnOpen_Click` 方法以支持相机复用,避免重复初始化。更新了 `txtGain_TextChanged` 方法以忽略文本框变化。

在 `LBCamera.cs` 中调整了 `SetGain` 和 `GetGain` 方法,增益参数类型从 `float` 改为 `int`。添加了异常处理逻辑以捕获同步配置时的错误,并增强了 `SetParam` 和 `GetParam` 方法对 `int` 类型参数的支持。
简化相机逻辑,支持手动数据处理

在 `2DCameraForm.cs` 中移除了对 `CameraBrand.LBCamera` 的特定处理,确保 `onlinePictureBox` 始终可见,并更新了图像抓取完成逻辑以支持手动处理模式。

在 `BaseCamera.cs` 中修改了 `StartGrabbing` 方法的注释,明确指出默认使用单次采集模式。

在 `LBCamera.cs` 中添加了采集回调和采集完成回调的处理逻辑,移除了与 SDK 自动显示模式相关的代码,确保在单次和连续采集模式下正确生成图像。

更新了相机初始化和关闭逻辑,确保在连接相机时正确设置显示句柄,并在关闭时清理资源,添加了对触发模式的支持。

改进了图像获取方法的超时处理逻辑,确保在软触发模式下能够正确等待图像数据,并在超时后进行适当的错误处理。

增强了对相机参数的设置和获取逻辑,确保在连接相机时能够正确同步配置,并在需要时更新相机参数。
已修改3个文件
851 ■■■■■ 文件已修改
LB_VisionProcesses/Cameras/2DCameraForm.cs 282 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
LB_VisionProcesses/Cameras/BaseCamera.cs 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
LB_VisionProcesses/Cameras/LBCameras/LBCamera.cs 567 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
LB_VisionProcesses/Cameras/2DCameraForm.cs
@@ -80,9 +80,23 @@
        private System.Windows.Forms.Timer updateTimer;
        Total total = new Total { iImageCount = 0, iScanCount = 0 };
        DateTime startGrabtime = DateTime.Now;
        // 动态添加的增益下拉框
        private ComboBox cmbGain;
        private void CameraForm_Load(object sender, EventArgs e)
        {
            // 初始化增益下拉框
            cmbGain = new ComboBox();
            cmbGain.Visible = false; // 默认隐藏
            cmbGain.DropDownStyle = ComboBoxStyle.DropDownList;
            cmbGain.Size = txtGain.Size;
            cmbGain.Location = txtGain.Location;
            cmbGain.Font = txtGain.Font;
            cmbGain.SelectedIndexChanged += CmbGain_SelectedIndexChanged;
            this.txtGain.Parent.Controls.Add(cmbGain);
            cmbGain.BringToFront();
            // 设置一个定时器,每 100 毫秒触发一次
            updateTimer = new System.Windows.Forms.Timer();
            updateTimer.Interval = 1000;  // 设置定时器间隔为 1000 毫秒
@@ -124,6 +138,16 @@
                if (camConfig.OutputImage != null && camConfig.OutputImage is Bitmap bitmap)
                    onlinePictureBox.Image = bitmap;
                lblCapTime.Text = $"{camConfig.RunTime}ms";
            }
        }
        private void CmbGain_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (camera != null && camera is LBCamera && cmbGain.Visible)
            {
                // LBCamera SetGain expects double which is cast to int for enum value
                // ComboBox index corresponds directly to EnumAnalogGain value (0-4)
                camera.SetGain((double)cmbGain.SelectedIndex);
            }
        }
@@ -232,42 +256,71 @@
            // 尝试将输入字符串转换为枚举值
            if (Enum.TryParse(cmbBrand.Text, true, out CameraBrand brand))
            {
                if (camera != null)
                string selectedSN = cmbSN.Text.ToString();
                bool isReusingExisting = false;
                // 1. 尝试从现有字典中查找已初始化的相机
                if (dicCameras != null && dicCameras.ContainsKey(selectedSN))
                {
                    var existingCamera = dicCameras[selectedSN];
                    // 确保品牌匹配
                    if (existingCamera.Brand == brand)
                    {
                        camera = existingCamera;
                        isReusingExisting = true;
                    }
                }
                // 2. 如果没有复用现有相机,且当前camera对象不是我们要复用的对象,则清理旧对象
                if (!isReusingExisting && camera != null)
                {
                    camera.ImageGrabbed -= GetImageBllComplete;
                    camera.Dispose();
                    camera = null;
                }
                // 使用 switch 语句来判断枚举值
                switch (brand)
                // 3. 如果没有复用,则创建新实例
                if (!isReusingExisting)
                {
                    case CameraBrand.LBCamera:
                        camera = new LBCamera();
                        break;
                    case CameraBrand.HRCamera:
                        camera = new HRCamera();
                        break;
                    default:
                        Debug.WriteLine($"【{DateTime.Now:HH:mm:ss.fff}】未知品牌");
                        return;
                    // 使用 switch 语句来判断枚举值
                    switch (brand)
                    {
                        case CameraBrand.LBCamera:
                            camera = new LBCamera();
                            break;
                        case CameraBrand.HRCamera:
                            camera = new HRCamera();
                            break;
                        default:
                            Debug.WriteLine($"【{DateTime.Now:HH:mm:ss.fff}】未知品牌");
                            return;
                    }
                }
                IntPtr displayHandle = onlinePictureBox.Handle;
                if (brand == CameraBrand.LBCamera)
                onlinePictureBox.Visible = true;
                bool initSuccess = false;
                if (isReusingExisting)
                {
                    onlinePictureBox.Visible = false;
                    displayHandle = this.panel_Picture.Handle;
                    // 复用时不需要再次InitDevice,但可能需要更新显示句柄(视具体SDK实现而定,LBCamera通常不需要)
                    initSuccess = true;
                    MessageBox.Show(camera.SN + "已连接 (复用)");
                }
                else
                {
                    onlinePictureBox.Visible = true;
                    if (cmbSN.Items.Count > 0 && camera.InitDevice(selectedSN, displayHandle))
                    {
                        initSuccess = true;
                        MessageBox.Show(camera.SN + "打开成功");
                    }
                }
                if (cmbSN.Items.Count > 0 && camera.InitDevice(cmbSN.Text.ToString(), displayHandle))
                if (initSuccess)
                {
                    // 重新绑定显示回调
                    camera.ImageGrabbed -= GetImageBllComplete;
                    camera.ImageGrabbed += GetImageBllComplete;
                    MessageBox.Show(camera.SN + "打开成功");
                    this.btnEdit.Enabled = true;
                }
            }
@@ -275,18 +328,51 @@
            {
                Debug.WriteLine($"【{DateTime.Now:HH:mm:ss.fff}】无效的枚举值!");
            }
            // ... (rest of the UI update logic)
            if (camera != null)
            {
                this.BeginInvoke(new Action(() =>
                {
                    double exp = 0;
                    double gain = 0;
                    camera.GetExpouseTime(out exp);
                    txtExp.Text = exp.ToString();
                    if (camera is LBCamera lbCam)
                    {
                        // 切换到下拉框显示
                        txtGain.Visible = false;
                        cmbGain.Visible = true;
                        // 填充选项 (对应 EnumAnalogGain: Gain_1_0, Gain_1_3, Gain_1_9, Gain_2_8, Gain_5_5)
                        cmbGain.Items.Clear();
                        cmbGain.Items.AddRange(new object[] { "1.0x", "1.3x", "1.9x", "2.8x", "5.5x" });
                    camera.GetGain(out gain);
                    txtGain.Text = gain.ToString();
                        var config = lbCam.GetSensorConfig();
                        txtExp.Text = config.ExposureTime.ToString();
                        // 设置当前选中的增益
                        int gainIndex = (int)config.AnalogGain;
                        if (gainIndex >= 0 && gainIndex < cmbGain.Items.Count)
                        {
                            cmbGain.SelectedIndex = gainIndex;
                        }
                        else
                        {
                            cmbGain.SelectedIndex = 0;
                        }
                    }
                    else
                    {
                        // 恢复文本框显示
                        txtGain.Visible = true;
                        cmbGain.Visible = false;
                        double exp = 0;
                        double gain = 0;
                        camera.GetExpouseTime(out exp);
                        txtExp.Text = exp.ToString();
                        camera.GetGain(out gain);
                        txtGain.Text = gain.ToString();
                    }
                    camera.GetTriggerMode(out TriggerMode mode, out TriggerSource source);
@@ -402,18 +488,7 @@
        /// <param name="image"></param>
        private void GetImageBllComplete(object sender, CameraEventArgs e)
        {
            // 对于LBCamera(SDK自动显示模式),Bitmap为null,我们只需要统计
            if (camera.Brand == CameraBrand.LBCamera)
            {
                if (e is LBCameraEventArgs args && args.IsComplete)
                {
                    total.iImageCount++;
                    // 不需要更新onlinePictureBox.Image,SDK会自动显示
                }
                return;
            }
            // 对于2D相机(手动处理模式)
            // 对于2D相机(手动处理模式)和现在的LBCamera(手动处理模式)
            if (e.Bitmap == null)
                return;
@@ -466,89 +541,47 @@
                if (success)
                {
                    // 对于LBCamera(SDK自动显示模式),等待采集完成即可
                    // 对于2D相机,等待Bitmap事件
                    if (camera.Brand == CameraBrand.LBCamera)
                    // 等待图像数据
                    using (AutoResetEvent waitHandle = new AutoResetEvent(false))
                    {
                        // SDK自动显示模式下,等待采集完成
                        using (AutoResetEvent waitHandle = new AutoResetEvent(false))
                        Bitmap captured = null;
                        EventHandler<CameraEventArgs> handler = (s, evt) =>
                        {
                            bool captured = false;
                            EventHandler<CameraEventArgs> handler = (s, evt) =>
                            if (evt.Bitmap != null)
                            {
                                if (evt is LBCameraEventArgs args && args.IsComplete)
                                {
                                    captured = true;
                                    waitHandle.Set();
                                }
                            };
                            camera.ImageGrabbed += handler;
                            try
                            {
                                // 等待5秒超时
                                if (waitHandle.WaitOne(5000))
                                {
                                    // SDK自动显示模式,不返回Bitmap
                                    // 但采集已完成
                                }
                                this.BeginInvoke(new Action(() =>
                                {
                                    this.lblCapTime.Text = $"{(DateTime.Now - StartTime).TotalMilliseconds}ms";
                                }));
                                captured = evt.Bitmap.Clone() as Bitmap;
                                waitHandle.Set();
                            }
                            finally
                        };
                        camera.ImageGrabbed += handler;
                        try
                        {
                            // 等待5秒超时
                            if (waitHandle.WaitOne(5000))
                            {
                                camera.ImageGrabbed -= handler;
                                camera.StopGrabbing();
                                bitmap = captured;
                            }
                        }
                    }
                    else
                    {
                        // 对于2D相机,等待图像数据
                        using (AutoResetEvent waitHandle = new AutoResetEvent(false))
                        finally
                        {
                            Bitmap captured = null;
                            EventHandler<CameraEventArgs> handler = (s, evt) =>
                            {
                                // 对于2D相机,直接接收图像
                                if (!(evt is LBCameraEventArgs) && evt.Bitmap != null)
                                {
                                    captured = evt.Bitmap.Clone() as Bitmap;
                                    waitHandle.Set();
                                }
                            };
                            camera.ImageGrabbed += handler;
                            try
                            {
                                // 等待5秒超时
                                if (waitHandle.WaitOne(5000))
                                {
                                    bitmap = captured;
                                }
                            }
                            finally
                            {
                                camera.ImageGrabbed -= handler;
                                camera.StopGrabbing();
                            }
                            camera.ImageGrabbed -= handler;
                            camera.StopGrabbing();
                        }
                        this.BeginInvoke(new Action(() =>
                        {
                            if (bitmap != null)
                            {
                                this.lblCapTime.Text = $"{(DateTime.Now - StartTime).TotalMilliseconds}ms";
                                onlinePictureBox.Image = bitmap;
                            }
                            else
                            {
                                this.lblCapTime.Text = "-1ms";
                            }
                        }));
                    }
                    this.BeginInvoke(new Action(() =>
                    {
                        if (bitmap != null)
                        {
                            this.lblCapTime.Text = $"{(DateTime.Now - StartTime).TotalMilliseconds}ms";
                            onlinePictureBox.Image = bitmap;
                        }
                        else
                        {
                            this.lblCapTime.Text = "-1ms";
                        }
                    }));
                }
                else
                {
@@ -683,7 +716,8 @@
        private void txtGain_TextChanged(object sender, EventArgs e)
        {
            if (camera == null)
            // 如果正在使用下拉框模式(LBCamera),忽略文本框的变化
            if (camera == null || (cmbGain != null && cmbGain.Visible))
                return;
            double gain = 10;
@@ -785,19 +819,25 @@
            if (camera != null)
            {
                camera.ImageGrabbed -= GetImageBllComplete;
                camera.StopGrabbing();
                // 检查该相机是否由主系统管理
                bool isManagedBySystem = dicCameras != null && dicCameras.ContainsKey(camera.SN);
                camera.GetTriggerMode(out _, out TriggerSource actualSource);
                if (radioButtonSoft.Checked)
                    camera.SetTriggerMode(TriggerMode.On, TriggerSource.Software);
                else
                    camera.SetTriggerMode(TriggerMode.On, actualSource);
                // LBCamera在StartGrabbing时会直接开启激光和采集,因此关闭窗口时不应自动重启采集
                // 其他相机(如2D相机)通常需要保持Grabbing状态以接收触发
                if (camera.Brand != CameraBrand.LBCamera)
                if (!isManagedBySystem)
                {
                    camera.StartGrabbing();
                    // 只有非托管的(临时打开的)相机才停止采集和释放
                    camera.StopGrabbing();
                    camera.GetTriggerMode(out _, out TriggerSource actualSource);
                    if (radioButtonSoft.Checked)
                        camera.SetTriggerMode(TriggerMode.On, TriggerSource.Software);
                    else
                        camera.SetTriggerMode(TriggerMode.On, actualSource);
                    if (camera.Brand != CameraBrand.LBCamera)
                    {
                        camera.StartGrabbing();
                    }
                }
            }
LB_VisionProcesses/Cameras/BaseCamera.cs
@@ -186,7 +186,7 @@
        #region  protected abstract
        /// <summary>
        /// 开始采图(默认连续采集)
        /// 开始采图(默认单次采集)
        /// </summary>
        /// <returns></returns>
        public abstract bool StartGrabbing();
LB_VisionProcesses/Cameras/LBCameras/LBCamera.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
@@ -30,91 +31,39 @@
    {
        private IntPtr _cameraHandle = IntPtr.Zero;
        private PHM6000SensorConfig _sensorConfig;
        // 采集回调
        private AcquisitionCallbackZA _acquisitionCallback;
        private AcquisitionCompletedCallback _acquisitionCompletedCallback;
        private bool _isConnected = false;
        private int _frameCount = 0; // 采集帧计数
        // 图像缓冲
        private List<byte[]> _lineDataBuffer = new List<byte[]>();
        private readonly object _bufferLock = new object();
        private int _currentLineCount = 0;
        // 实时显示用的Bitmap
        private Bitmap _currentBitmap = null;
        private byte[] _rawPixelBuffer = null; // 用于存储像素数据,避免频繁LockBits
        private byte[] _rawPixelBuffer = null; // 用于存储整张图的像素数据 (8bpp)
        private int _currentBitmapHeight = 0;
        private int _currentBitmapWidth = 0;
        private object _bitmapLock = new object();
        private DateTime _lastUpdateTime = DateTime.MinValue;
        private int _currentLineCount = 0;
        private object _bufferLock = new object();
        private bool _isBufferReady = false;
        // 显示句柄和函数(与LLSystem示例保持一致)
        private IntPtr _lightPic = IntPtr.Zero;
        private IntPtr _deepPic = IntPtr.Zero;
        private IntPtr _pointPic = IntPtr.Zero;
        private IntPtr _outlinePic = IntPtr.Zero;
        // 显示控制函数结构体
        private PILOT2D_FUNC _func2d;
        private VTK3D_FUNC _func3d;
        // 临时行缓冲,用于接收回调数据
        private byte[] _tempLineBuffer = null;
        private bool _isContinuous = false;
        public LBCamera()
        {
            Brand = CameraBrand.LBCamera;
            _sensorConfig = new PHM6000SensorConfig();
            // 初始化2D显示函数
            _func2d = new PILOT2D_FUNC
            {
                AddBarycentreDataZA = Pilot2D.AddBarycentreDataZA,
                AddDepthData = Pilot2D.AddDepthData,
                AddIntensityData = Pilot2D.AddIntensityData,
                ClearAllPoints = Pilot2D.ClearAllPoints,
                RefreshPilot2D = Pilot2D.RefreshPilot2D,
                SetImageSize = Pilot2D.SetImageSize,
            };
            // 初始化3D显示函数
            _func3d = new VTK3D_FUNC
            {
                AddZAPoints = PHM6000Profiler.AddZAPoints,
                ClearPCLPoints = PHM6000Profiler.ClearPCLPoints,
                GetPointCloudBound = PHM6000Profiler.GetPointCloudBound,
                RenderPCLWindow = PHM6000Profiler.RenderPCLWindow,
                SetLookUpTableRange = PHM6000Profiler.SetLookUpTableRange,
                ShowCubeAxes = PHM6000Profiler.ShowCubeAxes,
                ShowLookUpTable = PHM6000Profiler.ShowLookUpTable,
                UpdatePCLPointColors = PHM6000Profiler.UpdatePCLPointColors,
            };
        }
        #region ICamera Implementation
        public override bool InitDevice(string sn, object handle = null)
        {
            // 如果已连接,仅检查是否需要更新显示句柄
            // 如果已连接,直接返回true
            if (_isConnected && _cameraHandle != IntPtr.Zero)
            {
                if (handle != null && handle is IntPtr hPtr && hPtr != IntPtr.Zero)
                {
                    // 销毁旧的显示句柄
                    if (_lightPic != IntPtr.Zero)
                    {
                        Pilot2D.ClearAllPoints(_lightPic);
                        Pilot2D.DestroyPilot2DEntry(_lightPic);
                    }
                    // 创建新的显示句柄
                    _lightPic = Pilot2D.CreatePilot2DEntry(hPtr);
                    if (_lightPic != IntPtr.Zero)
                    {
                        var config = GetSensorConfig();
                        Pilot2D.SetImageSize(_lightPic, 4096, config.ScanLineCount > 0 ? config.ScanLineCount : 5000);
                        // 更新相机绑定的显示句柄
                        PHM6000Profiler.SetShowHandles(_cameraHandle, _lightPic, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
                        AsyncLogHelper.Info($"LBCamera[{SN}]: Display handle updated");
                    }
                }
                return true;
            }
@@ -148,9 +97,8 @@
                    {
                        string currentSn = Encoding.UTF8.GetString(serialNumberBytes).TrimEnd('\0');
                        
                        // 如果传入的 sn 是 IP 地址,则直接尝试匹配 IP
                        // 或者匹配序列号
                        if (currentSn == sn || sn.Contains(currentSn)) // 简单匹配逻辑
                        // 匹配SN或IP
                        if (currentSn == sn || sn.Contains(currentSn))
                        {
                            byte[] addressBytes = new byte[64];
                            int port = 0;
@@ -165,17 +113,15 @@
                    }
                }
                // 销毁临时句柄
                PHM6000Profiler.DestroyCameraEntry(tempHandle);
                tempHandle = IntPtr.Zero;
                if (!found)
                {
                    // 如果没找到但 sn 本身看起来像 IP,尝试直接连接
                    if (System.Net.IPAddress.TryParse(sn, out _))
                    {
                        targetIp = sn;
                        targetPort = 5577; // 默认端口
                        targetPort = 5577;
                    }
                    else
                    {
@@ -197,35 +143,18 @@
                    // 加载相机当前参数到 _sensorConfig
                    SyncConfigFromCamera();
                    // 创建亮度图显示句柄(类似LLSystem示例)
                    IntPtr hPtr = IntPtr.Zero;
                    if (handle is IntPtr p) hPtr = p;
                    if (hPtr != IntPtr.Zero)
                    {
                        _lightPic = Pilot2D.CreatePilot2DEntry(hPtr);
                        if (_lightPic != IntPtr.Zero)
                        {
                            // 设置图像尺寸,宽度4096,高度根据配置
                            var config = GetSensorConfig();
                            Pilot2D.SetImageSize(_lightPic, 4096, config.ScanLineCount > 0 ? config.ScanLineCount : 5000);
                        }
                    }
                    // 初始化并注册采集回调 (获取数据用)
                    _acquisitionCallback = new AcquisitionCallbackZA(OnAcquisitionCallbackZA);
                    PHM6000Profiler.SetAcquisitionCallbackZA(_cameraHandle, _acquisitionCallback, IntPtr.Zero);
                    // 初始化回调
                    // 初始化并注册采集完成回调 (状态通知用)
                    _acquisitionCompletedCallback = new AcquisitionCompletedCallback(OnAcquisitionCompleted);
                    // 注册采集完成回调(类似LLSystem示例)
                    PHM6000Profiler.RegisterAcquisitionCompletedCallback(_cameraHandle, _acquisitionCompletedCallback, IntPtr.Zero);
                    // 设置2D和3D显示函数(类似LLSystem示例)
                    PHM6000Profiler.SetPilot2dFunc(_cameraHandle, _func2d);
                    PHM6000Profiler.SetVTK3dFunc(_cameraHandle, _func3d);
                    // 强制应用当前配置(确保触发模式等参数正确,避免相机处于未知状态)
                    UpdateSensorConfig(_sensorConfig);
                    // 设置显示句柄(类似LLSystem示例的SetShowHandles)
                    PHM6000Profiler.SetShowHandles(_cameraHandle, _lightPic, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
                    AsyncLogHelper.Info($"LBCamera[{SN}]: Connected and initialized successfully with SDK auto-display mode");
                    AsyncLogHelper.Info($"LBCamera[{SN}]: Connected and initialized successfully (Manual Data Mode)");
                    return true;
                }
@@ -247,20 +176,10 @@
            if (_isConnected && _cameraHandle != IntPtr.Zero)
            {
                StopGrabbing();
                // 销毁显示句柄
                if (_lightPic != IntPtr.Zero)
                {
                    Pilot2D.ClearAllPoints(_lightPic);
                    Pilot2D.DestroyPilot2DEntry(_lightPic);
                    _lightPic = IntPtr.Zero;
                }
                PHM6000Profiler.DestroyCameraEntry(_cameraHandle);
                _cameraHandle = IntPtr.Zero;
                _isConnected = false;
                AsyncLogHelper.Info($"LBCamera[{SN}]: Closed and cleaned up");
                AsyncLogHelper.Info($"LBCamera[{SN}]: Closed");
            }
            return true;
        }
@@ -282,8 +201,6 @@
                        if (PHM6000Profiler.GetCameraInformation(tempHandle, i, moduleTypeBytes, serialNumberBytes) == 0)
                        {
                            string sn = Encoding.UTF8.GetString(serialNumberBytes).TrimEnd('\0');
                            string type = Encoding.UTF8.GetString(moduleTypeBytes).TrimEnd('\0');
                            // 格式参考:PHM6000[SN123456]
                            if (!string.IsNullOrEmpty(sn))
                            {
                                cameraList.Add(sn);
@@ -294,7 +211,7 @@
            }
            catch (Exception ex)
            {
                AsyncLogHelper.Error($"LBCamera: GetListEnum异常 - {ex.Message}");
                AsyncLogHelper.Error($"LBCamera: 获取设备列表异常 - {ex.Message}");
            }
            finally
            {
@@ -306,132 +223,121 @@
        private void InitBuffer()
        {
            lock (_bitmapLock)
            lock (_bufferLock)
            {
                _currentBitmapHeight = _sensorConfig.ScanLineCount > 0 ? _sensorConfig.ScanLineCount : 5000;
                // 宽度暂时未知,将在第一行数据到达时初始化
                // 宽度在第一行数据到达时确定
                _currentBitmapWidth = 0;
                if (_currentBitmap != null)
                {
                    _currentBitmap.Dispose();
                    _currentBitmap = null;
                }
                _rawPixelBuffer = null;
                _currentLineCount = 0;
                _isBufferReady = false;
            }
        }
        public override bool StartGrabbing()
        {
            // 线扫相机默认使用连续采集模式
            // 参考LLSystem示例的ContinuousScan方法:SetAcquisitionMode(1, 1)
            // 默认连续模式
            return StartSingleGrab();
        }
        /// <summary>
        /// 单次采集模式(适用于线扫相机)
        /// 参考LLSystem示例的StartScan方法:SetAcquisitionMode(1, 0)
        /// 使用SDK自动显示模式,不手动处理数据
        /// </summary>
        public bool StartSingleGrab()
        {
            if (!_isConnected) return false;
            _isContinuous = false;
            InitBuffer();
            AsyncLogHelper.Info($"LBCamera[{SN}]: 开始单次采集");
            AsyncLogHelper.Info($"LBCamera[{SN}]: Starting single grab mode");
            // 使用SDK自动显示模式,类似LLSystem示例
            // 不设置AcquisitionCallbackZA,让SDK自动处理图像显示
            PHM6000Profiler.SetAcquisitionCallbackZA(_cameraHandle, IntPtr.Zero, IntPtr.Zero);
            // 设置采集模式:1=扫描模式,0=单次模式(与LLSystem示例保持一致)
            // 1=扫描模式, 0=单次
            PHM6000Profiler.SetAcquisitionMode(_cameraHandle, 1, 0);
            int result = PHM6000Profiler.StartAcquisition(_cameraHandle, 0, 0, 0.0);
            if (result == 0)
            {
                isGrabbing = true;
                AsyncLogHelper.Info($"LBCamera[{SN}]: Single grab started successfully");
                return true;
            }
            else
            {
                AsyncLogHelper.Error($"LBCamera[{SN}]: Failed to start single grab, result={result}");
            }
            return false;
        }
        /// <summary>
        /// 连续采集模式(适用于线扫相机)
        /// 参考LLSystem示例的ContinuousScan方法:SetAcquisitionMode(1, 1)
        /// 使用SDK自动显示模式,不手动处理数据
        /// </summary>
        public override bool StartContinuousGrab()
        {
            if (!_isConnected) return false;
            AsyncLogHelper.Info($"LBCamera[{SN}]: Starting continuous grab mode");
            _isContinuous = true;
            InitBuffer();
            AsyncLogHelper.Info($"LBCamera[{SN}]:开始连续采集");
            // 使用SDK自动显示模式,类似LLSystem示例
            // 不设置AcquisitionCallbackZA,让SDK自动处理图像显示
            PHM6000Profiler.SetAcquisitionCallbackZA(_cameraHandle, IntPtr.Zero, IntPtr.Zero);
            // 设置采集模式:1=扫描模式,1=连续模式(与LLSystem示例保持一致)
            // 1=扫描模式, 1=连续
            PHM6000Profiler.SetAcquisitionMode(_cameraHandle, 1, 1);
            int result = PHM6000Profiler.StartAcquisition(_cameraHandle, 0, 0, 0.0);
            if (result == 0)
            {
                isGrabbing = true;
                AsyncLogHelper.Info($"LBCamera[{SN}]: Continuous grab started successfully");
                return true;
            }
            else
            {
                AsyncLogHelper.Error($"LBCamera[{SN}]: Failed to start continuous grab, result={result}");
            }
            return false;
        }
        public override bool StopGrabbing()
        {
            _isContinuous = false;
            if (!_isConnected) return true;
            PHM6000Profiler.StopAcquisition(_cameraHandle);
            // 停止时如果有未显示的缓存数据,将其显示出来(支持显示不完整的帧)
            lock (_bufferLock)
            {
                if (_currentLineCount > 0)
                {
                    AsyncLogHelper.Info($"LBCamera[{SN}]: Flushing partial buffer ({_currentLineCount} lines) on stop");
                    CreateAndFireBitmap();
                    _currentLineCount = 0;
                }
            }
            isGrabbing = false;
            return true;
        }
        public override bool StartWith_SoftTriggerModel() => StartContinuousGrab();
        public override bool StartWith_SoftTriggerModel()
        {
            // 对于LBCamera(线扫相机),软件触发连续采集
            // 参考LLSystem示例的ContinuousScan方法:SetAcquisitionMode(1, 1)
            return StartContinuousGrab();
        }
        public override bool StartWith_HardTriggerModel(TriggerSource hardtriggeritem = TriggerSource.Line0) => StartSingleGrab();
        public override bool StartWith_HardTriggerModel(TriggerSource hardtriggeritem = TriggerSource.Line0)
        {
            // 对于LBCamera(线扫相机),硬件触发也使用连续采集模式
            // 参考LLSystem示例的ContinuousScan方法:SetAcquisitionMode(1, 1)
            // 外部硬件信号会触发相机开始采集
            return StartSingleGrab();
        }
        public override bool SoftTrigger()
        {
            // 线扫相机通常不需要传统软触发,但在某些模式下可模拟
            return true;
        }
        public override bool SoftTrigger() => true;
        #region 参数设置映射
        public override bool SetExpouseTime(double value) => SetParam(EnumNameId.ExposureTime, (float)value);
        public override bool GetExpouseTime(out double value) { float v; bool r = GetParam(EnumNameId.ExposureTime, out v); value = v; return r; }
        public override bool SetGain(double gain) => SetParam(EnumNameId.AnalogGain, (float)gain);
        public override bool GetGain(out double gain) { float v; bool r = GetParam(EnumNameId.AnalogGain, out v); gain = v; return r; }
        public override bool SetGain(double gain) => SetParam(EnumNameId.AnalogGain, (int)gain);
        public override bool GetGain(out double gain) { int v; bool r = GetParam(EnumNameId.AnalogGain, out v); gain = v; return r; }
        // 其他接口占位实现
        public override bool SetTriggerMode(TriggerMode mode, TriggerSource triggerEnum = TriggerSource.Line0) => true;
        public override bool GetTriggerMode(out TriggerMode mode, out TriggerSource source) { mode = TriggerMode.Off; source = TriggerSource.Software; return true; }
        public override bool SetTriggerMode(TriggerMode mode, TriggerSource triggerEnum = TriggerSource.Line0)
        {
            if (!_isConnected) return false;
            if (triggerEnum == TriggerSource.Software)
            {
                _sensorConfig.LineScanTriggerSource = EnumLineScanTriggerSource.固定频率;
                _sensorConfig.DataAcquisitionTriggerSource = EnumDataAcquisitionTriggerSource.软触发;
            }
            else
            {
                _sensorConfig.LineScanTriggerSource = EnumLineScanTriggerSource.编码器;
                _sensorConfig.DataAcquisitionTriggerSource = EnumDataAcquisitionTriggerSource.外部触发;
            }
            UpdateSensorConfig(_sensorConfig);
            return true;
        }
        public override bool GetTriggerMode(out TriggerMode mode, out TriggerSource source)
        {
            mode = TriggerMode.On;
            source = _sensorConfig.DataAcquisitionTriggerSource == EnumDataAcquisitionTriggerSource.软触发 ? TriggerSource.Software : TriggerSource.Line0;
            return true;
        }
        public override bool SetTriggerPolarity(TriggerPolarity polarity) => true;
        public override bool GetTriggerPolarity(out TriggerPolarity polarity) { polarity = TriggerPolarity.RisingEdge; return true; }
        public override bool SetTriggerFliter(double flitertime) => true;
@@ -442,58 +348,63 @@
        public override bool SetLineStatus(IOLines line, LineStatus linestatus) => true;
        public override bool GetLineStatus(IOLines line, out LineStatus lineStatus) { lineStatus = LineStatus.Low; return true; }
        public override bool AutoBalanceWhite() => true;
        // 不实现的方法
        public override void SetCamConfig(CameraConfig config) { }
        public override void GetCamConfig(out CameraConfig config) { config = new CameraConfig(null); }
        public override bool GetImage(out Bitmap bitmap, int outtime = 3000) { bitmap = null; return false; }
        public override bool GetImageWithSoftTrigger(out Bitmap bitmap, int outtime = 3000)
        public override bool GetImageWithSoftTrigger(out Bitmap bitmap, int outtime = 3000)
        {
            // 简单实现:软触发等待
            bitmap = null;
            if (!_isConnected) return false;
            if(!_isConnected) return false;
            // 对于线扫相机,使用SDK自动显示模式
            // 注意:SDK自动显示模式下,我们无法直接获取Bitmap
            // 但SDK会自动将图像显示到_lightPic句柄对应的控件上
            // 我们只需要启动单次采集,等待采集完成即可
            // 计算理论最小耗时 (仅当使用固定频率触发时)
            int minTime = 0;
            if (_sensorConfig.LineScanTriggerSource == EnumLineScanTriggerSource.固定频率)
            {
                float rate = _sensorConfig.SoftwareTriggerRate > 0 ? _sensorConfig.SoftwareTriggerRate : 1000f;
                int lines = _sensorConfig.ScanLineCount > 0 ? _sensorConfig.ScanLineCount : 5000;
                minTime = (int)((lines / rate) * 1000);
            }
            // 如果传入超时时间不够,自动延长
            int actualTimeout = outtime;
            if (actualTimeout < minTime + 2000)
            {
                actualTimeout = minTime + 3000; // 预留3秒余量
                AsyncLogHelper.Warn($"LBCamera: Provided timeout {outtime}ms is too short for {minTime}ms scan. Extended to {actualTimeout}ms.");
            }
            using (AutoResetEvent waitHandle = new AutoResetEvent(false))
            {
                bool captured = false;
                EventHandler<CameraEventArgs> handler = (s, e) =>
                {
                    // 对于线扫相机,仅当IsComplete为true时才表示采集完成
                    if (e is LBCameraEventArgs args && args.IsComplete)
                    {
                        captured = true;
                        waitHandle.Set();
                Bitmap res = null;
                EventHandler<CameraEventArgs> handler = (s, e) => {
                    if(e.Bitmap != null) {
                         res = e.Bitmap.Clone() as Bitmap;
                         waitHandle.Set();
                    }
                };
                this.ImageGrabbed += handler;
                try
                ImageGrabbed += handler;
                if (StartSingleGrab())
                {
                    // 使用单次采集模式(与LLSystem示例保持一致)
                    if (StartSingleGrab())
                    if (!waitHandle.WaitOne(actualTimeout))
                    {
                        // 等待采集完成
                        if (waitHandle.WaitOne(outtime))
                        {
                            // 由于SDK自动显示,我们无法返回Bitmap
                            // 但采集已完成,图像已显示到界面上
                            bitmap = null;  // SDK自动显示模式下不返回Bitmap
                            return captured;
                        }
                        AsyncLogHelper.Error($"LBCamera: GetImageWithSoftTrigger timeout after {actualTimeout}ms");
                    }
                }
                finally
                else
                {
                    this.ImageGrabbed -= handler;
                    StopGrabbing();
                    AsyncLogHelper.Error("LBCamera: StartSingleGrab failed");
                }
                ImageGrabbed -= handler;
                // 确保停止采集
                StopGrabbing();
                bitmap = res;
                return bitmap != null;
            }
            return false;
        }
        public PHM6000SensorConfig GetSensorConfig()
@@ -505,27 +416,16 @@
        public void UpdateSensorConfig(PHM6000SensorConfig config)
        {
            _sensorConfig = config;
            if (!_isConnected) return;
            // 基础图像参数
            SetParam(EnumNameId.ExposureTime, (float)config.ExposureTime);
            SetParam(EnumNameId.AnalogGain, (float)config.AnalogGain);
            // 扫描控制参数 - 决定单次采集的数据量
            PHM6000Profiler.SetProfilerParameter(_cameraHandle, (int)EnumNameId.ScanLineCount, config.ScanLineCount, 0, 0);
            // 触发设置 - 决定采集速度
            PHM6000Profiler.SetProfilerParameter(_cameraHandle, (int)EnumNameId.LineScanTriggerSource, 0, 0, (int)config.LineScanTriggerSource);
            PHM6000Profiler.SetProfilerParameter(_cameraHandle, (int)EnumNameId.DataAcquisitionTriggerSource, 0, 0, (int)config.DataAcquisitionTriggerSource);
            // 如果是软触发(固定频率),设置频率
            if (config.LineScanTriggerSource == EnumLineScanTriggerSource.固定频率)
            {
                PHM6000Profiler.SetProfilerParameter(_cameraHandle, (int)EnumNameId.SoftwareTriggerRate, 0, config.SoftwareTriggerRate, 0);
            }
            // 确保参数生效
            PHM6000Profiler.SaveAllParametersToDevice(_cameraHandle);
        }
@@ -533,42 +433,205 @@
        
        #endregion
        #region Private Callback & Helpers
        #region Callbacks
        private void OnAcquisitionCallbackZA(IntPtr pInstance, IntPtr buffer, int points)
        {
            if (buffer == IntPtr.Zero || points <= 0) return;
            lock (_bufferLock)
            {
                // 初始化缓冲区
                if (_rawPixelBuffer == null)
                {
                    _currentBitmapWidth = points;
                    if (_currentBitmapHeight <= 0) _currentBitmapHeight = 2000; // 默认防呆
                    _rawPixelBuffer = new byte[_currentBitmapWidth * _currentBitmapHeight];
                    _currentLineCount = 0;
                }
                if (_currentLineCount >= _currentBitmapHeight) return; // 缓冲区满,忽略多余数据
                // 准备临时缓冲区接收行数据 (LBPointZA = 8 bytes)
                int lineBytes = points * 8;
                if (_tempLineBuffer == null || _tempLineBuffer.Length != lineBytes)
                {
                    _tempLineBuffer = new byte[lineBytes];
                }
                // 拷贝非托管内存到托管数组
                Marshal.Copy(buffer, _tempLineBuffer, 0, lineBytes);
                // 提取灰度(Intensity/Alpha)数据填充到 _rawPixelBuffer
                // LBPointZA结构: float(4) + res(3) + alpha(1). Alpha在偏移7
                int bufferOffset = _currentLineCount * _currentBitmapWidth;
                for (int i = 0; i < points; i++)
                {
                    if (bufferOffset + i < _rawPixelBuffer.Length)
                    {
                        _rawPixelBuffer[bufferOffset + i] = _tempLineBuffer[i * 8 + 7];
                    }
                }
                _currentLineCount++;
                // 如果达到预定高度,生成图像
                if (_currentLineCount >= _currentBitmapHeight)
                {
                    CreateAndFireBitmap();
                    // 重置,准备下一帧 (如果是连续采集)
                    _currentLineCount = 0;
                    // _rawPixelBuffer 可以复用,不需要置空
                }
            }
        }
        private void OnAcquisitionCompleted(IntPtr pInstance, int nOption)
        {
            // nOption含义:
            // 0 = 一批数据结束
            // 1 = 全部采集完成(仅单次模式)
            // 2 = 对应数据处理完成(每次采集完成都会触发)
            // 记录回调信息
            AsyncLogHelper.Info($"LBCamera[{SN}]: OnAcquisitionCompleted called, nOption={nOption}");
            if (nOption == 2)
            // nOption: 0=Batch End, 1=All End(Single), 2=Processing End
            // 此处主要用于日志或状态监控
            // 实际图像生成在 Data Callback 中完成
            if (nOption == 1) // 单次采集结束
            {
                // 连续采集模式下,每完成一帧SDK都会自动继续采集下一帧
                // SDK会自动处理图像显示到_lightPic句柄
                // 我们只需要统计采集次数
                if (_isContinuous && isGrabbing)
                {
                    // 如果在连续模式下收到结束信号,尝试自动重启采集
                    AsyncLogHelper.Info($"LBCamera[{SN}]: Continuous mode frame ended, restarting...");
                    Task.Run(() =>
                    {
                        if (_isContinuous && _isConnected)
                        {
                            PHM6000Profiler.StartAcquisition(_cameraHandle, 0, 0, 0.0);
                        }
                    });
                }
                else
                {
                    isGrabbing = false;
                    AsyncLogHelper.Info($"LBCamera[{SN}]: Single grab completed by SDK");
                    // 单次采集结束时,如果有未显示的缓冲数据,立即生成图像
                    // 防止因数据量不足(小于ScanLineCount)导致GetImageWithSoftTrigger一直等待
                    lock (_bufferLock)
                    {
                        if (_currentLineCount > 0)
                        {
                            AsyncLogHelper.Info($"LBCamera[{SN}]: Flushing partial buffer ({_currentLineCount} lines) on completion");
                            CreateAndFireBitmap();
                            _currentLineCount = 0;
                        }
                    }
                }
            }
        }
        private void CreateAndFireBitmap()
        {
            try
            {
                int width = _currentBitmapWidth;
                int height = _currentLineCount; // 使用实际采集到的行数
                if (width <= 0 || height <= 0 || _rawPixelBuffer == null) return;
                Bitmap bmp = new Bitmap(width, height, PixelFormat.Format8bppIndexed);
                // 设置灰度调色板
                ColorPalette palette = bmp.Palette;
                for (int i = 0; i < 256; i++)
                {
                    palette.Entries[i] = Color.FromArgb(i, i, i);
                }
                bmp.Palette = palette;
                // 拷贝数据
                BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
                // 注意:Bitmap Stride 可能不等于 Width,需要逐行拷贝
                int stride = bmpData.Stride;
                IntPtr ptr = bmpData.Scan0;
                for (int y = 0; y < height; y++)
                {
                    // 确保不越界
                    if ((y * width) + width <= _rawPixelBuffer.Length)
                    {
                        Marshal.Copy(_rawPixelBuffer, y * width, ptr + y * stride, width);
                    }
                }
                bmp.UnlockBits(bmpData);
                _frameCount++;
                AsyncLogHelper.Info($"LBCamera[{SN}]: Frame {_frameCount} generated ({width}x{height})");
                AsyncLogHelper.Info($"LBCamera[{SN}]: Frame {_frameCount} completed (continuous mode, SDK auto-display)");
                // 触发事件通知UI更新(但不传递Bitmap,因为SDK自动显示)
                ImageGrabbed?.Invoke(this, new LBCameraEventArgs(SN, null, true));
                // 异步触发事件,避免阻塞SDK回调线程
                Task.Run(() =>
                {
                    try
                    {
                        ImageGrabbed?.Invoke(this, new LBCameraEventArgs(SN, bmp, true));
                    }
                    catch (Exception ex)
                    {
                        AsyncLogHelper.Error($"LBCamera: Event Invoke error - {ex.Message}");
                        bmp.Dispose(); // 异常时释放资源
                    }
                });
            }
            catch (Exception ex)
            {
                AsyncLogHelper.Error($"LBCamera: CreateBitmap error - {ex.Message}");
            }
        }
        private void SyncConfigFromCamera()
        {
            // 从相机读取所有参数并填充到 _sensorConfig
            foreach (EnumNameId id in Enum.GetValues(typeof(EnumNameId)))
            try
            {
                int iVal = 0; double dVal = 0; int eVal = 0;
                if (PHM6000Profiler.GetProfilerParameter(_cameraHandle, (int)id, ref iVal, ref dVal, ref eVal) == 0)
                if (!_isConnected) return;
                PropertyInfo[] props = _sensorConfig.GetType().GetProperties();
                foreach (PropertyInfo p in props)
                {
                    // 实际项目中应使用反射将值写回 _sensorConfig
                    // 跳过自定义参数
                    var iscustomAttr = p.GetCustomAttribute<IsCustomAttribute>();
                    if (iscustomAttr != null) continue;
                    if (Enum.TryParse(typeof(EnumNameId), p.Name, out object nameIdObj))
                    {
                        EnumNameId nameId = (EnumNameId)nameIdObj;
                        int intValue = 0;
                        double doubleValue = 0;
                        int enumValue = 0;
                        if (PHM6000Profiler.GetProfilerParameter(_cameraHandle, (int)nameId, ref intValue, ref doubleValue, ref enumValue) == 0)
                        {
                            if (p.PropertyType == typeof(int))
                            {
                                p.SetValue(_sensorConfig, intValue);
                            }
                            else if (p.PropertyType == typeof(float))
                            {
                                p.SetValue(_sensorConfig, (float)doubleValue);
                            }
                            else if (p.PropertyType == typeof(double))
                            {
                                p.SetValue(_sensorConfig, doubleValue);
                            }
                            else // Enum or other types
                            {
                                p.SetValue(_sensorConfig, enumValue);
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                AsyncLogHelper.Error($"LBCamera: SyncConfigFromCamera error - {ex.Message}");
            }
        }
@@ -576,6 +639,13 @@
        {
            if (!_isConnected) return false;
            return PHM6000Profiler.SetProfilerParameter(_cameraHandle, (int)id, 0, value, 0) == 0;
        }
        private bool SetParam(EnumNameId id, int value)
        {
            if (!_isConnected) return false;
            // 对于枚举类型,通常通过 enumValue (最后一个参数) 传递
            return PHM6000Profiler.SetProfilerParameter(_cameraHandle, (int)id, 0, 0, value) == 0;
        }
        private bool GetParam(EnumNameId id, out float value)
@@ -590,6 +660,19 @@
            }
            return false;
        }
        private bool GetParam(EnumNameId id, out int value)
        {
            value = 0;
            if (!_isConnected) return false;
            int iVal = 0; double dVal = 0; int eVal = 0;
            if (PHM6000Profiler.GetProfilerParameter(_cameraHandle, (int)id, ref iVal, ref dVal, ref eVal) == 0)
            {
                value = eVal; // Assuming it returns in enumValue
                return true;
            }
            return false;
        }
        #endregion
    }
}
}