2 커밋 c02e8b6dbd ... d345f914d9

작성자 SHA1 메시지 날짜
  向羽 孟 d345f914d9 202602007001 修改环形队列,改为无锁模式提升性能,添加缺帧记录,处理图像前添加了非空判断 2 주 전
  向羽 孟 db3dc70eda 20260206001 添加了RingBuffer自定义多线程队列,性能未测试 2 주 전

+ 2 - 5
MvvmScaffoldFrame48.DLL/CameraTools/HikCamera.cs

@@ -66,11 +66,8 @@ namespace MvvmScaffoldFrame48.DLL.CameraTools
                 _device.Close();
                 _device.Dispose();
             }
-            if (device == null)
-            {
-                throw new Exception("相机实例不能为空");
-            }
-            this._device = device;
+
+            this._device = device ?? throw new Exception("相机实例不能为空");
         }
 
         /// <summary>

+ 12 - 12
MvvmScaffoldFrame48.DLL/ImageAlgorithm/ProcessingAlgorithm_CCDShuLi.cs

@@ -100,7 +100,7 @@ namespace MvvmScaffoldFrame48.DLL.ImageAlgorithm
                     {
                         item.StateCode = 7;
                         //FaultLog.RecordLogMessage("ShuLiClass-ProcessLine:非颗粒,视野异常", 3);
-                        TxtLog.log(string.Format("ShuLiClass-ProcessLine:非颗粒,视野异常"), 6);
+                        //TxtLog.log(string.Format("ShuLiClass-ProcessLine:非颗粒,视野异常"), 6);
                         //Console.WriteLine("ShuLiClass-ProcessLine:非颗粒,视野异常");
                     }
                     else if (shuLiConfig.PandingCode != -1)
@@ -109,42 +109,42 @@ namespace MvvmScaffoldFrame48.DLL.ImageAlgorithm
                         {
                             if (item.StateCode == 8)
                             {
-                                TxtLog.log(string.Format("颗粒编号{0}:疑似叠片或缺损", item.Num));
+                                //TxtLog.log(string.Format("颗粒编号{0}:疑似叠片或缺损", item.Num));
                             }
                         }
                         else if (item.Area < shuLiConfig.MinArea
                             && (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 1))
                         {
                             item.StateCode = 5;
-                            TxtLog.log(string.Format("颗粒编号{0}:面积过小", item.Num));
-                            Console.WriteLine("颗粒编号{0}:面积过小", item.Num);
+                            //TxtLog.log(string.Format("颗粒编号{0}:面积过小", item.Num));
+                            //Console.WriteLine("颗粒编号{0}:面积过小", item.Num);
                         }
                         else if (item.Area > shuLiConfig.MaxArea
                             && (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 1))
                         {
                             item.StateCode = 6;
-                            TxtLog.log(string.Format("颗粒编号{0}:面积过大", item.Num));
-                            Console.WriteLine("颗粒编号{0}:面积过大", item.Num);
+                            //TxtLog.log(string.Format("颗粒编号{0}:面积过大", item.Num));
+                            //Console.WriteLine("颗粒编号{0}:面积过大", item.Num);
                         }
                         else if (item.MaxLength < shuLiConfig.MIN_Object_LENGTH
                             && (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 0))
                         {
                             item.StateCode = 2;
-                            TxtLog.log(string.Format("颗粒编号{0}:超短粒", item.Num));
-                            Console.WriteLine("颗粒编号{0}:超短粒", item.Num);
+                            //TxtLog.log(string.Format("颗粒编号{0}:超短粒", item.Num));
+                            //Console.WriteLine("颗粒编号{0}:超短粒", item.Num);
                         }
                         else if (item.MaxLength > shuLiConfig.MAX_Object_LENGTH
                             && (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 0))
                         {
                             item.StateCode = 1;
-                            TxtLog.log(string.Format("颗粒编号{0}:超长粒", item.Num));
-                            Console.WriteLine("颗粒编号{0}:超长粒", item.Num);
+                            //TxtLog.log(string.Format("颗粒编号{0}:超长粒", item.Num));
+                            //Console.WriteLine("颗粒编号{0}:超长粒", item.Num);
                         }
                         else
                         {
                             item.StateCode = 0;
-                            TxtLog.log(string.Format("颗粒编号{0}:正常粒", item.Num));
-                            Console.WriteLine("颗粒编号{0}:正常粒", item.Num);
+                            //TxtLog.log(string.Format("颗粒编号{0}:正常粒", item.Num));
+                            //Console.WriteLine("颗粒编号{0}:正常粒", item.Num);
                         }
                     }
                     resultValue.Add(item);

+ 2 - 0
MvvmScaffoldFrame48.DLL/MvvmScaffoldFrame48.Dll.csproj

@@ -89,7 +89,9 @@
     <Compile Include="ImageAlgorithm\ProcessingAlgorithm_CCDShuLi.cs" />
     <Compile Include="LogTools\TxtLog.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="SystemTools\RingBuffer.cs" />
     <Compile Include="SystemTools\SystemRunTimeTools.cs" />
+    <Compile Include="SystemTools\TimeStampTools.cs" />
     <Compile Include="ThreadManager\CameraGroup.cs" />
     <Compile Include="ThreadManager\CommunicationThread.cs" />
     <Compile Include="ThreadManager\IImageProcessingAlgorithmHikVision.cs" />

+ 102 - 0
MvvmScaffoldFrame48.DLL/SystemTools/RingBuffer.cs

@@ -0,0 +1,102 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MvvmScaffoldFrame48.DLL.SystemTools
+{
+    public sealed class LockFreeRingBuffer<T>
+    {
+        private readonly T[] _buffer;
+        private readonly int _mask;
+        private volatile int _readIndex;
+        private volatile int _writeIndex;
+        private readonly int _capacity;
+
+        public LockFreeRingBuffer(int capacity)
+        {
+            // 容量必须是2的幂次方
+            if (!IsPowerOfTwo(capacity))
+                capacity = RoundUpToPowerOfTwo(capacity);
+
+            _capacity = capacity;
+            _mask = _capacity - 1;
+            _buffer = new T[_capacity];
+        }
+
+        private static bool IsPowerOfTwo(int x)
+        {
+            return x > 0 && (x & (x - 1)) == 0;
+        }
+
+        private static int RoundUpToPowerOfTwo(int x)
+        {
+            if (x <= 1) return 1;
+            x--;
+            x |= x >> 1;
+            x |= x >> 2;
+            x |= x >> 4;
+            x |= x >> 8;
+            x |= x >> 16;
+            return x + 1;
+        }
+
+        public bool TryEnqueue(T item)
+        {
+            int currentWriteIndex, nextWriteIndex, currentReadIndex;
+
+            do
+            {
+                currentWriteIndex = _writeIndex;
+                currentReadIndex = _readIndex;
+                nextWriteIndex = (currentWriteIndex + 1) & _mask;
+
+                // 检查是否已满
+                if (nextWriteIndex == currentReadIndex)
+                    return false;
+
+            } while (Interlocked.CompareExchange(ref _writeIndex, nextWriteIndex, currentWriteIndex) != currentWriteIndex);
+
+            // 设置值并更新发布标记
+            _buffer[currentWriteIndex & _mask] = item;
+
+            return true;
+        }
+
+        public bool TryDequeue(out T result)
+        {
+            result = default(T);
+            int currentReadIndex, nextReadIndex, currentWriteIndex;
+
+            do
+            {
+                currentReadIndex = _readIndex;
+                currentWriteIndex = _writeIndex;
+
+                // 检查是否为空
+                if (currentReadIndex == currentWriteIndex)
+                    return false;
+
+                nextReadIndex = (currentReadIndex + 1) & _mask;
+
+            } while (Interlocked.CompareExchange(ref _readIndex, nextReadIndex, currentReadIndex) != currentReadIndex);
+
+            result = _buffer[currentReadIndex & _mask];
+            _buffer[currentReadIndex & _mask] = default(T); // 防止内存泄漏
+
+            return true;
+        }
+
+        public int Count
+        {
+            get
+            {
+                int read = _readIndex;
+                int write = _writeIndex;
+                return (write - read) & _mask;
+            }
+        }
+    }
+}

+ 66 - 0
MvvmScaffoldFrame48.DLL/SystemTools/TimeStampTools.cs

@@ -0,0 +1,66 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MvvmScaffoldFrame48.DLL.SystemTools
+{
+    public static class TimeStampTools
+    {
+        public static DateTime FromUnixTimestamp(long timestamp, bool isMilliseconds = false)
+        {
+            try
+            {
+                // 如果未指定单位,尝试自动检测
+                if (!isMilliseconds)
+                {
+                    // 如果时间戳看起来像毫秒级(13位数字)
+                    if (timestamp.ToString().Length > 10)
+                    {
+                        isMilliseconds = true;
+                    }
+                }
+
+                if (isMilliseconds)
+                {
+                    // 验证毫秒级时间戳范围
+                    const long minMilliTimestamp = -62135596800000; // 0001-01-01 00:00:00 UTC
+                    const long maxMilliTimestamp = 253402300799999; // 9999-12-31 23:59:59 UTC
+
+                    if (timestamp < minMilliTimestamp || timestamp > maxMilliTimestamp)
+                    {
+                        throw new ArgumentOutOfRangeException(nameof(timestamp),
+                            "毫秒级时间戳超出有效范围");
+                    }
+
+                    DateTime origin = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(timestamp);
+                    return TimeZoneInfo.ConvertTimeFromUtc(origin, TimeZoneInfo.Local);
+                }
+                else
+                {
+                    // 验证秒级时间戳范围
+                    const long minTimestamp = -62135596800;
+                    const long maxTimestamp = 253402300799;
+
+                    if (timestamp < minTimestamp || timestamp > maxTimestamp)
+                    {
+                        throw new ArgumentOutOfRangeException(nameof(timestamp),
+                            "秒级时间戳超出有效范围");
+                    }
+
+                    DateTime origin = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddSeconds(timestamp);
+                    return TimeZoneInfo.ConvertTimeFromUtc(origin, TimeZoneInfo.Local);
+                }
+            }
+            catch (ArgumentOutOfRangeException)
+            {
+                throw;
+            }
+            catch (Exception ex)
+            {
+                throw new ArgumentException($"无法转换时间戳 {timestamp}: {ex.Message}", ex);
+            }
+        }
+    }
+}

+ 86 - 40
MvvmScaffoldFrame48.DLL/ThreadManager/CameraGroup.cs

@@ -1,6 +1,8 @@
 // CameraGroup.cs 相机组线程管理类,控制相机采集和识别的线程运行,开放有算法的接口
 using MvCameraControl;
 using MvvmScaffoldFrame48.DLL.CameraTools;
+using MvvmScaffoldFrame48.DLL.LogTools;
+using MvvmScaffoldFrame48.DLL.SystemTools;
 using MvvmScaffoldFrame48.Model.ResultModel;
 using MvvmScaffoldFrame48.Model.StorageModel.SystemConfig;
 using System;
@@ -20,6 +22,8 @@ namespace MvvmScaffoldFrame48.DLL.ThreadManager
         #region 变量与实例
         //相机线程的休眠时间,默认为0,即不休眠。在采集节拍没有那么高时,增加此值降低性能消耗
         private int CameraSleepTime = 0;
+        //帧编号记录
+        private long lastframeNum = -1;
         //相机
         public HikCamera Camera { get; set; }
         //相机ID
@@ -30,8 +34,10 @@ namespace MvvmScaffoldFrame48.DLL.ThreadManager
         public Task CameraTask { get; set; }
         //图像处理线程
         public Task ProcessingTask { get; set; }
-        //图像队列
+        //图像队列-ConcurrentQueue
         public ConcurrentQueue<IImage> ImageQueue { get; set; }
+        //图像队列-自定义RingBuffer
+        public LockFreeRingBuffer<IImage> ImageRingBuffer = new LockFreeRingBuffer<IImage>(300);
         //信号量
         public SemaphoreSlim QueueSemaphore { get; set; }
         //取消令牌
@@ -71,9 +77,9 @@ namespace MvvmScaffoldFrame48.DLL.ThreadManager
             // 根据配置加载算法
             LoadAlgorithmFromConfiguration();
 
-            Camera.FrameGrabbedEvent += CameraCaptureLoop;
 
             //Camera.StartReceiveFuntion();
+            Camera.FrameGrabbedEvent += CameraCaptureLoop2;
             Camera.StartReceiveFuntionSetFrameGrabedEvent();
 
             // 重置取消令牌
@@ -86,7 +92,7 @@ namespace MvvmScaffoldFrame48.DLL.ThreadManager
 
 
             // 启动图像处理线程
-            ProcessingTask = Task.Factory.StartNew(() => ImageProcessingbatchLoop(token),
+            ProcessingTask = Task.Factory.StartNew(() => ImageProcessingbatchLoop2(token),
                 token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
 
             IsRunning = true;
@@ -232,7 +238,7 @@ namespace MvvmScaffoldFrame48.DLL.ThreadManager
             try
             {
                 // 优先使用接口方式的算法
-                if (ImageProcessor != null)
+                if (ImageProcessor != null && imageData!=null)
                 {
                     resultData = ImageProcessor.ProcessImage(imageData, cameraId);
                 }
@@ -276,7 +282,7 @@ namespace MvvmScaffoldFrame48.DLL.ThreadManager
                     {
                         // 将图像放入队列
                         ImageQueue.Enqueue(imageData.Image.Clone() as IImage);
-                        double QuTuYanshi = (DateTime.Now - FromUnixTimestamp((long)imageData.HostTimeStamp)).TotalMilliseconds;
+                        double QuTuYanshi = (DateTime.Now - TimeStampTools.FromUnixTimestamp((long)imageData.HostTimeStamp)).TotalMilliseconds;
                         //Console.WriteLine($"相机 {CameraId} 采集到图像,帧号{imageData.FrameNum}");
                         if(QuTuYanshi > 12)
                         {
@@ -311,7 +317,7 @@ namespace MvvmScaffoldFrame48.DLL.ThreadManager
             {
                 // 将图像放入队列
                 ImageQueue.Enqueue(e.FrameOut.Image.Clone() as IImage);
-                double QuTuYanshi = (DateTime.Now - FromUnixTimestamp((long)e.FrameOut.HostTimeStamp)).TotalMilliseconds;
+                double QuTuYanshi = (DateTime.Now - TimeStampTools.FromUnixTimestamp((long)e.FrameOut.HostTimeStamp)).TotalMilliseconds;
                 //Console.WriteLine($"相机 {CameraId} 采集到图像,帧号{imageData.FrameNum}");
                 if (QuTuYanshi > 12)
                 {
@@ -326,6 +332,51 @@ namespace MvvmScaffoldFrame48.DLL.ThreadManager
             }
         }
 
+        /// <summary>
+        /// 相机回调取图
+        /// 更换了存储图像的队列
+        /// </summary>
+        /// <param name="sender"></param>
+        /// <param name="e"></param>
+        private void CameraCaptureLoop2(object sender, FrameGrabbedEventArgs e)
+        {
+            try
+            {
+                // 将图像放入队列
+                ImageRingBuffer.TryEnqueue(e.FrameOut.Image.Clone() as IImage);
+                double QuTuYanshi = (DateTime.Now - TimeStampTools.FromUnixTimestamp((long)e.FrameOut.HostTimeStamp)).TotalMilliseconds;
+                //Console.WriteLine($"相机 {CameraId} 采集到图像,帧号{imageData.FrameNum}");
+                if (QuTuYanshi > 12)
+                {
+                    Console.WriteLine($"识别落后时间{QuTuYanshi}");
+                }
+                if (lastframeNum == -1)
+                {
+                    lastframeNum = e.FrameOut.FrameNum;
+                }
+                else if (lastframeNum == e.FrameOut.FrameNum - 1)
+                {
+                    lastframeNum = e.FrameOut.FrameNum;
+                }
+                else
+                {
+                    //丢帧记录
+                    TxtLog.log(string.Format("lost frame: Width[{0}] , Height[{1}] , FrameNum[{2}] ,Frevous[{3}]",
+                        e.FrameOut.Image.Width, e.FrameOut.Image.Height, e.FrameOut.FrameNum, lastframeNum), 6);
+                    Console.WriteLine("lost frame: Width[{0}] , Height[{1}] , FrameNum[{2}] ,Frevous[{3}]",
+                        e.FrameOut.Image.Width, e.FrameOut.Image.Height, e.FrameOut.FrameNum, lastframeNum);
+                    lastframeNum = e.FrameOut.FrameNum;
+                }
+                QueueSemaphore.Release(); // 通知处理线程有新数据
+                e.FrameOut.Dispose();
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine($"相机 {CameraId} 采集异常: {ex.Message}");
+            }
+        }
+
+
         /// <summary>
         /// 图像处理循环
         /// 适用取图频率不高的处理,或者处理速度远快于取图速度的方法
@@ -417,61 +468,56 @@ namespace MvvmScaffoldFrame48.DLL.ThreadManager
                 Console.WriteLine($"相机 {CameraId} 处理异常: {ex.Message}");
             }
         }
-        #endregion
 
-        public DateTime FromUnixTimestamp(long timestamp, bool isMilliseconds = false)
+        private async void ImageProcessingbatchLoop2(CancellationToken token)
         {
+            const int batchSize = 5; // 每批处理的最大图像数量
+            var batch = new List<IImage>(batchSize); // 存储当前批次的图像
+
             try
             {
-                // 如果未指定单位,尝试自动检测
-                if (!isMilliseconds)
+                while (!token.IsCancellationRequested)
                 {
-                    // 如果时间戳看起来像毫秒级(13位数字)
-                    if (timestamp.ToString().Length > 10)
+                    // 等待至少一张图像数据
+                    await QueueSemaphore.WaitAsync(token);
+
+                    // 收集一批图像(严格按队列顺序)
+                    while (batch.Count < batchSize && ImageRingBuffer.TryDequeue(out IImage imageData))
                     {
-                        isMilliseconds = true;
+                        if (imageData == null) continue;
+                        batch.Add(imageData);
+                        if (ImageRingBuffer.Count > 0)
+                        {
+                            QueueSemaphore.Release(); // 提前释放信号量,唤醒其他等待线程
+                        }
                     }
-                }
-
-                if (isMilliseconds)
-                {
-                    // 验证毫秒级时间戳范围
-                    const long minMilliTimestamp = -62135596800000; // 0001-01-01 00:00:00 UTC
-                    const long maxMilliTimestamp = 253402300799999; // 9999-12-31 23:59:59 UTC
 
-                    if (timestamp < minMilliTimestamp || timestamp > maxMilliTimestamp)
+                    // 顺序处理当前批次的所有图像
+                    foreach (var image in batch)
                     {
-                        throw new ArgumentOutOfRangeException(nameof(timestamp),
-                            "毫秒级时间戳超出有效范围");
+                        ProcessImage(image, CameraId); // 严格按顺序执行处理逻辑
+                        image.Dispose(); // 处理完成后立即释放资源
                     }
 
-                    DateTime origin = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(timestamp);
-                    return TimeZoneInfo.ConvertTimeFromUtc(origin, TimeZoneInfo.Local);
-                }
-                else
-                {
-                    // 验证秒级时间戳范围
-                    const long minTimestamp = -62135596800;
-                    const long maxTimestamp = 253402300799;
+                    // 清空当前批次,准备下一轮处理
+                    batch.Clear();
 
-                    if (timestamp < minTimestamp || timestamp > maxTimestamp)
+                    // 如果队列为空,短暂休眠以避免过度占用CPU
+                    if (ImageRingBuffer.Count == 0)
                     {
-                        throw new ArgumentOutOfRangeException(nameof(timestamp),
-                            "秒级时间戳超出有效范围");
+                        await Task.Delay(1, token); // 固定短时间休眠
                     }
-
-                    DateTime origin = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddSeconds(timestamp);
-                    return TimeZoneInfo.ConvertTimeFromUtc(origin, TimeZoneInfo.Local);
                 }
             }
-            catch (ArgumentOutOfRangeException)
+            catch (OperationCanceledException)
             {
-                throw;
+                // 线程被取消,正常退出
             }
             catch (Exception ex)
             {
-                throw new ArgumentException($"无法转换时间戳 {timestamp}: {ex.Message}", ex);
+                Console.WriteLine($"相机 {CameraId} 处理异常: {ex.Message}");
             }
         }
+        #endregion
     }
 }

+ 1 - 1
MvvmScaffoldFrame48.DLL/ThreadManager/CommunicationThread.cs

@@ -114,7 +114,7 @@ namespace MvvmScaffoldFrame48.DLL.ThreadManager
         private void PerformCommunication(CameraProcessEventArgsResultModel data)
         {
             // 实现具体通信逻辑
-            Console.WriteLine($"通信线程发送数据: 相机{data.CameraId}, 时间{data.Timestamp}");
+            //Console.WriteLine($"通信线程发送数据: 相机{data.CameraId}, 时间{data.Timestamp}");
         }
         #endregion
     }

+ 1 - 1
MvvmScaffoldFrame48.DLL/ThreadManager/ThreadManager.cs

@@ -272,7 +272,7 @@ namespace MvvmScaffoldFrame48.DLL.ThreadManager
             _communicationThread.SendData(e);
 
             // 可以在这里添加其他处理逻辑,如更新显示等
-            Console.WriteLine($"收到相机{e.CameraId}的处理结果");
+            //Console.WriteLine($"收到相机{e.CameraId}的处理结果");
         }
         #endregion
     }