Procházet zdrojové kódy

202602007001 修改环形队列,改为无锁模式提升性能,添加缺帧记录,处理图像前添加了非空判断

向羽 孟 před 2 týdny
rodič
revize
d345f914d9

+ 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);

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

@@ -91,6 +91,7 @@
     <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" />

+ 71 - 26
MvvmScaffoldFrame48.DLL/SystemTools/RingBuffer.cs

@@ -2,56 +2,101 @@
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
+using System.Threading;
 using System.Threading.Tasks;
 
 namespace MvvmScaffoldFrame48.DLL.SystemTools
 {
-    public sealed class RingBuffer<T>
+    public sealed class LockFreeRingBuffer<T>
     {
         private readonly T[] _buffer;
-        private int _writeIndex;
-        private int _readIndex;
-        private int _count;
+        private readonly int _mask;
+        private volatile int _readIndex;
+        private volatile int _writeIndex;
         private readonly int _capacity;
-        private readonly object _lock = new object();
 
-        public RingBuffer(int capacity)
+        public LockFreeRingBuffer(int capacity)
         {
+            // 容量必须是2的幂次方
+            if (!IsPowerOfTwo(capacity))
+                capacity = RoundUpToPowerOfTwo(capacity);
+
             _capacity = capacity;
-            _buffer = new T[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)
         {
-            lock (_lock)
+            int currentWriteIndex, nextWriteIndex, currentReadIndex;
+
+            do
             {
-                if (_count >= _capacity) return false;
+                currentWriteIndex = _writeIndex;
+                currentReadIndex = _readIndex;
+                nextWriteIndex = (currentWriteIndex + 1) & _mask;
 
-                var index = (_readIndex + _count) % _capacity;
-                _buffer[index] = item;
-                _count++;
-                return true;
-            }
+                // 检查是否已满
+                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)
         {
-            lock (_lock)
+            result = default(T);
+            int currentReadIndex, nextReadIndex, currentWriteIndex;
+
+            do
             {
-                if (_count <= 0)
-                {
-                    result = default(T);
+                currentReadIndex = _readIndex;
+                currentWriteIndex = _writeIndex;
+
+                // 检查是否为空
+                if (currentReadIndex == currentWriteIndex)
                     return false;
-                }
 
-                result = _buffer[_readIndex];
-                _buffer[_readIndex] = default(T); // 避免内存泄漏
-                _readIndex = (_readIndex + 1) % _capacity;
-                _count--;
-                return true;
-            }
+                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 => _count;
+        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);
+            }
+        }
+    }
+}

+ 28 - 63
MvvmScaffoldFrame48.DLL/ThreadManager/CameraGroup.cs

@@ -1,6 +1,7 @@
 // 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;
@@ -21,6 +22,8 @@ namespace MvvmScaffoldFrame48.DLL.ThreadManager
         #region 变量与实例
         //相机线程的休眠时间,默认为0,即不休眠。在采集节拍没有那么高时,增加此值降低性能消耗
         private int CameraSleepTime = 0;
+        //帧编号记录
+        private long lastframeNum = -1;
         //相机
         public HikCamera Camera { get; set; }
         //相机ID
@@ -34,7 +37,7 @@ namespace MvvmScaffoldFrame48.DLL.ThreadManager
         //图像队列-ConcurrentQueue
         public ConcurrentQueue<IImage> ImageQueue { get; set; }
         //图像队列-自定义RingBuffer
-        public RingBuffer<IImage> ImageRingBuffer { get; set; }
+        public LockFreeRingBuffer<IImage> ImageRingBuffer = new LockFreeRingBuffer<IImage>(300);
         //信号量
         public SemaphoreSlim QueueSemaphore { get; set; }
         //取消令牌
@@ -74,10 +77,9 @@ namespace MvvmScaffoldFrame48.DLL.ThreadManager
             // 根据配置加载算法
             LoadAlgorithmFromConfiguration();
 
-            //ImageRingBuffer = new RingBuffer<IImage>(300);
 
             //Camera.StartReceiveFuntion();
-            Camera.FrameGrabbedEvent += CameraCaptureLoop;
+            Camera.FrameGrabbedEvent += CameraCaptureLoop2;
             Camera.StartReceiveFuntionSetFrameGrabedEvent();
 
             // 重置取消令牌
@@ -90,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;
@@ -236,7 +238,7 @@ namespace MvvmScaffoldFrame48.DLL.ThreadManager
             try
             {
                 // 优先使用接口方式的算法
-                if (ImageProcessor != null)
+                if (ImageProcessor != null && imageData!=null)
                 {
                     resultData = ImageProcessor.ProcessImage(imageData, cameraId);
                 }
@@ -280,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)
                         {
@@ -315,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)
                 {
@@ -342,12 +344,29 @@ namespace MvvmScaffoldFrame48.DLL.ThreadManager
             {
                 // 将图像放入队列
                 ImageRingBuffer.TryEnqueue(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)
                 {
                     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();
             }
@@ -465,6 +484,7 @@ namespace MvvmScaffoldFrame48.DLL.ThreadManager
                     // 收集一批图像(严格按队列顺序)
                     while (batch.Count < batchSize && ImageRingBuffer.TryDequeue(out IImage imageData))
                     {
+                        if (imageData == null) continue;
                         batch.Add(imageData);
                         if (ImageRingBuffer.Count > 0)
                         {
@@ -499,60 +519,5 @@ namespace MvvmScaffoldFrame48.DLL.ThreadManager
             }
         }
         #endregion
-
-        public 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);
-            }
-        }
     }
 }

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