| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502 |
- using CCDCount.DLL.AlarmTools;
- using CCDCount.DLL.Tools;
- using CCDCount.MODEL.ConfigModel;
- using CCDCount.MODEL.ResultModel;
- using CCDCount.MODEL.ShuLiModel;
- using LogClass;
- using MvCameraControl;
- using System;
- using System.Collections.Concurrent;
- using System.Collections.Generic;
- using System.Data.Entity.Core.Common.CommandTrees.ExpressionBuilder;
- using System.Diagnostics;
- using System.Drawing;
- using System.IO;
- using System.Linq;
- using System.Threading;
- using System.Threading.Tasks;
- namespace CCDCount.DLL
- {
- public class ShuLiClass
- {
- #region 变量
- /// <summary>
- /// 当活跃物体转变为历史物体时的回调事件
- /// </summary>
- public event EventHandler<ActiveObjectEventArgsClass> WorkCompleted;
- private List<ActiveObjectClass> activeObjects = new List<ActiveObjectClass>(); // 当前跟踪中的物体
- private List<ActiveObjectClass> historyActiveObjects = new List<ActiveObjectClass>(); // 历史物体
- private LockFreeRingBuffer<IFrameOut> RingBuffer_IFrameDatas = new LockFreeRingBuffer<IFrameOut>(300);
- private long currentLine = 0; //行数记录
- private ShuLiConfigClass shuLiConfig = null;// 数粒参数配置文件
- public List<int> ChannelsRoi { get { return _ChannelsRoi; } }
- private List<int> _ChannelsRoi = new List<int>();
- private int ChannelWidth = 0;//每个区域的宽度
- private int IdentifyImageWidth = -1;
- private int ObjectNum = 0;
- public int ImageNum { get { return RingBuffer_IFrameDatas.Count; } }
- private ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim();
- private double XCoefficient = 0.1;
- private double YCoefficient = 0.2;
- private DateTime[] ChannelIntervalTime = new DateTime[8];
- private int _HistoryActiveNum = 0;
- public int HistoryActiveNum { get { return _HistoryActiveNum; } }
- private int _OkHistoryNum = 0;
- public int OkHistoryNum { get { return _OkHistoryNum; } }
- private int _NgHistoryNum = 0;
- public int NgHistoryNum { get { return _NgHistoryNum; } }
- #endregion
- #region 公共方法
- /// <summary>
- /// 初始化构造方法
- /// </summary>
- public ShuLiClass()
- {
- // 加载默认参数
- shuLiConfig = new ShuLiConfigClass()
- {
- Channel = 8,
- PandingCode = 2
- };
- for(int i = 0; i < ChannelIntervalTime.Length; i++)
- {
- ChannelIntervalTime[i] = DateTime.Now;
- }
- }
- /// <summary>
- /// 带参数的构造方法
- /// </summary>
- public ShuLiClass(ShuLiConfigClass config)
- {
- if (config.IsLoadCanfig)
- {
- // 加载传出的参数
- shuLiConfig = config;
- XCoefficient = shuLiConfig.ScaleX;
- YCoefficient = shuLiConfig.ScaleY;
- }
- else
- {
- // 加载默认参数
- shuLiConfig = new ShuLiConfigClass()
- {
- Channel = 8,
- PandingCode = 2
- };
- }
- InitChannel();
- }
- public long InitCurrentLine(int line)
- {
- currentLine = line;
- return currentLine;
- }
- public int InitNum(int num)
- {
- ObjectNum = num;
- return ObjectNum;
- }
- /// <summary>
- /// 处理图像序列的主入口
- /// </summary>
- /// <param name="image">图像像素数据</param>
- /// <returns>检测到的物体总数</returns>
- public bool ProcessImageSequence(IFrameOut image)
- {
- bool result = false;
- ReadOnlySpan<byte> spanFromArr = image.Image.PixelData.AsSpan();
- for (int i = 0; i < image.Image.Height; i++)
- {
- ProcessLine(image, i);
- currentLine += 1;
- }
- // 优化: 减少锁的使用频率,批量处理逻辑
- List<ActiveObjectClass> objectsToProcess = null;
- _rwLock.EnterReadLock();
- try
- {
- // 使用ToList()避免在锁内进行复杂查询
- objectsToProcess = activeObjects
- .Where(o => (currentLine - o.LastSeenLine) > shuLiConfig.MAX_GAP ||
- (o.LastSeenLine - o.StartLine) > shuLiConfig.MAX_Idetify_Height)
- .ToList();
- }
- finally
- {
- _rwLock.ExitReadLock();
- }
- // 优化: 提前返回,避免不必要的处理
- if (objectsToProcess.Count == 0)
- {
- return result;
- }
- // 优化: 预分配容量,减少内存重新分配
- var processedObjects = new List<ActiveObjectClass>(objectsToProcess.Count);
- // 优化: 将耗时的计算移到循环外
- var endTime = DateTime.Now;
- var pictureEndTime = FromUnixTimestamp((long)image.HostTimeStamp);
- foreach (var item in objectsToProcess)
- {
- // 优化: 将耗时的计算移到循环外
- ProcessSingleObject(item, pictureEndTime, endTime, processedObjects);
- }
- result = true;
- // 优化: 批量操作减少锁的持有时间
- if (processedObjects.Count > 0)
- {
- _rwLock.EnterWriteLock();
- try
- {
- // 批量转移对象
- foreach (var item in processedObjects)
- {
- TryAdd(historyActiveObjects, item, 2500);
- }
- // 批量移除
- foreach (var item in objectsToProcess)
- {
- activeObjects.Remove(item);
- }
- }
- finally
- {
- _rwLock.ExitWriteLock();
- }
- // 优化: 异步触发事件,避免阻塞主线程
- //ThreadPool.QueueUserWorkItem(_ =>
- //{
- OnWorkCompleted(processedObjects);
- //});
- }
- return result;
- }
- // 抽取的单个对象处理方法
- private void ProcessSingleObject(ActiveObjectClass item, DateTime pictureEndTime,
- DateTime endTime, List<ActiveObjectClass> processedObjects)
- {
- item.PictureEndReadTime = pictureEndTime;
- var nowTime = DateTime.Now;
- var ShiBieLuoHouTime = (nowTime - item.PictureEndReadTime).TotalMilliseconds;
- //// 优化: 将日志记录改为条件执行
- if (ShiBieLuoHouTime > 30)
- {
- //LOG.error($"算法识别结果落后了超过30ms,相机取图总耗时:{ShiBieLuoHouTime}");
- Console.WriteLine($"算法识别结果落后了超过30ms,相机取图总耗时:{ShiBieLuoHouTime}");
- }
- // 快速滤除噪点
- if (item.Area < shuLiConfig.NoiseFilter_Threshold)
- {
- item.StateCode = 9;
- return;
- }
- // 计算长度
- if (item.StartLine == item.LastSeenLine)
- {
- item.MaxLength = item.MaxEndCol - item.MinStartCol;
- item.StateCode = 10;
- LOG.log(string.Format("颗粒{0}的有效高度仅一行", item.Num));
- return;
- }
- else
- {
- if ((item.LastSeenLine - item.StartLine) < shuLiConfig.MAX_Idetify_Height)
- {
- var calculationResult = SizeCalculation(item.RowsData);
- if (calculationResult != null)
- {
- item.MaxLength = calculationResult.Height;
- }
- }
- else
- {
- item.StateCode = 7;
- return;
- }
- }
- // 分类处理
- ApplyClassificationRules(ref item);
- // 添加到历史记录
- if (item.StateCode != 7 && item.StateCode != 9 && item.StateCode != 10)
- {
- item.Num = Interlocked.Increment(ref ObjectNum);
- item.ChannelNO = ActiveChannel(item);
- }
- item.EndCheckTime = endTime;
- if (item.StateCode != 7 && item.StateCode != 9 && item.StateCode != 10)
- {
- processedObjects.Add(item);
- }
- }
- /// <summary>
- /// 分类规则
- /// </summary>
- /// <param name="item"></param>
- private void ApplyClassificationRules(ref ActiveObjectClass item)
- {
- if (shuLiConfig.PandingCode == -1) return;
- if (item.StateCode != -1)
- {
- if (item.StateCode == 8)
- {
- LOG.log(string.Format("颗粒编号{0}:疑似叠片或缺损", item.Num));
- }
- else if (item.StateCode == 7)
- {
- LOG.log("视野被遮挡");
- }
- else if (item.StateCode == 9)
- {
- LOG.log(string.Format("噪点", item.Num));
- }
- }
- else if (item.Area < shuLiConfig.MinArea &&
- (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 1))
- {
- item.StateCode = 6;
- LOG.log(string.Format("颗粒编号{0}:面积过小", item.Num));
- }
- else if (item.Area > shuLiConfig.MaxArea &&
- (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 1))
- {
- item.StateCode = 5;
- LOG.log(string.Format("颗粒编号{0}:面积过大", item.Num));
- }
- else if (item.MaxLength < shuLiConfig.MIN_Object_LENGTH &&
- (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 0))
- {
- item.StateCode = 2;
- LOG.log(string.Format("颗粒编号{0}:超短粒", item.Num));
- }
- else if (item.MaxLength > shuLiConfig.MAX_Object_LENGTH &&
- (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 0))
- {
- item.StateCode = 1;
- LOG.log(string.Format("颗粒编号{0}:超长粒", item.Num));
- }
- else
- {
- item.StateCode = 0;
- LOG.log(string.Format("颗粒编号{0}:正常粒", item.Num));
- }
- }
- /// <summary>
- /// 返回最后一个历史物品
- /// </summary>
- /// <returns></returns>
- public ActiveObjectClass GetLastActive()
- {
- if (historyActiveObjects.Count() == 0)
- return null;
- _rwLock.EnterReadLock();
- var result = historyActiveObjects.Last();
- _rwLock.ExitReadLock();
- return result;
- }
- /// <summary>
- /// 返回历史物品
- /// </summary>
- /// <returns></returns>
- public List<ActiveObjectClass> GetHistoryActive()
- {
- //lock (_lockObj) // 加锁
- //{
- _rwLock.EnterReadLock();
- var result = historyActiveObjects.ToList();
- _rwLock.ExitReadLock();
- return result;
- //}
- }
- /// <summary>
- /// 清除历史数据
- /// </summary>
- /// <returns></returns>
- public bool ClearHistoryActive()
- {
- _HistoryActiveNum = 0;
- _OkHistoryNum = 0;
- _NgHistoryNum = 0;
- _rwLock.EnterWriteLock();
- historyActiveObjects.Clear();
- _rwLock.ExitWriteLock();
- return true;
- //}
- }
- /// <summary>
- /// 开启识别-优化版本
- /// </summary>
- /// <param name="ImaageWidth"></param>
- public void StartIdentifyFuntion(int ImaageWidth)
- {
- UpdateIdentifyImageWidth(ImaageWidth);
- InitChannel();
- try
- {
- CancellationTokenSource = new CancellationTokenSource();
- var token = CancellationTokenSource.Token;
- LOG.log(string.Format("X系数:{0},Y系数:{1}", XCoefficient, YCoefficient), 6);
- // 启动图像处理线程
- ProcessingTask = Task.Factory.StartNew(() => IdentifyImageProcessTask(token),
- token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
- SystemAlarm.AlarmCancel(AlarmMessageList.识别线程启动失败);
- }
- catch (Exception ex)
- {
- SystemAlarm.AlarmAlert(AlarmMessageList.识别线程启动失败,
- "Start thread failed!, " + ex.Message,
- "识别线程启动失败, " + ex.Message,
- "DLL:ShuLiClass-StartIdentifyFuntion");
- //FaultLog.RecordErrorMessage("Start thread failed!, " + ex.Message);
- throw;
- }
- }
- public async void StopIdentifyFuntion()
- {
- try
- {
- // 发送取消信号
- CancellationTokenSource.Cancel();
- // 等待线程完成
- try
- {
- if (ProcessingTask != null)
- await ProcessingTask;
- }
- catch (OperationCanceledException)
- {
- // 正常取消,忽略异常
- }
- SystemAlarm.AlarmCancel(AlarmMessageList.识别线程停止失败);
- }
- catch (Exception ex)
- {
- //FaultLog.RecordErrorMessage("Stop thread failed!, " + ex.Message);
- SystemAlarm.AlarmAlert(AlarmMessageList.识别线程停止失败,
- "Stop thread failed!, " + ex.Message,
- "识别线程停止失败, " + ex.Message,
- "DLL:ShuLiClass-StopIdentifyFuntion");
- throw;
- }
- }
- /// <summary>
- /// 向识别队列添加一个数据
- /// </summary>
- /// <param name="items"></param>
- public void SetOnceIdentifyImageData(IFrameOut items)
- {
- RingBuffer_IFrameDatas.TryEnqueue(items.Clone() as IFrameOut);
- }
- /// <summary>
- /// 保存参数
- /// </summary>
- public void SaveConfig()
- {
- if (!Directory.Exists(".\\Config\\")) Directory.CreateDirectory(".\\Config\\");
- XmlStorage.SerializeToXml(shuLiConfig, ".\\Config\\ShuLiConfig.xml");
- }
- /// <summary>
- /// 更新检测宽度信息
- /// </summary>
- /// <param name="Width"></param>
- public void UpdateIdentifyImageWidth(int Width)
- {
- IdentifyImageWidth = Width;
- }
- /// <summary>
- /// 初始化通道划分
- /// </summary>
- /// <param name="ImageWidth"></param>
- public void InitChannel()
- {
- _ChannelsRoi.Clear();
- shuLiConfig.ImageWidth = IdentifyImageWidth == -1 ? shuLiConfig.ImageWidth : IdentifyImageWidth;
- if (shuLiConfig.Channel > 0)
- {
- if (shuLiConfig.IsIdentifyRoiOpen)
- {
- ChannelWidth = (shuLiConfig.IdentifyStopX - shuLiConfig.IdentifyStartX) / shuLiConfig.Channel;
- }
- else
- {
- ChannelWidth = shuLiConfig.ImageWidth / shuLiConfig.Channel;
- }
- for (int i = 0; i < shuLiConfig.Channel; i++)
- {
- _ChannelsRoi.Add(ChannelWidth + i * ChannelWidth);
- }
- }
- }
- /// <summary>
- /// 获取配置信息
- /// </summary>
- /// <returns></returns>
- public ShuLiConfigClass GetConfigValue()
- {
- ShuLiConfigClass result = shuLiConfig;
- return result;
- }
- public bool TryAddHis(ActiveObjectClass item, int maxSize)
- {
- ThreadPool.QueueUserWorkItem(_ =>
- {
- _rwLock.EnterWriteLock();
- historyActiveObjects.Add(item);
- if (historyActiveObjects.Count > maxSize)
- {
- historyActiveObjects.Remove(historyActiveObjects[historyActiveObjects.Count - maxSize]);
- }
- _rwLock.ExitWriteLock();
- });
- return true;
- }
- public int GetConfigImageWidth()
- {
- int result = -1;
- if (shuLiConfig != null)
- result = shuLiConfig.ImageWidth;
- return result;
- }
- /// <summary>
- /// 获取坐标转换系数
- /// </summary>
- /// <param name="XCoefficient"></param>
- /// <param name="YCoefficient"></param>
- public void GetXYCoefficient(out double XCoefficient, out double YCoefficient)
- {
- XCoefficient = this.XCoefficient;
- YCoefficient = this.YCoefficient;
- }
- /// <summary>
- /// 设置坐标转换X系数
- /// </summary>
- /// <param name="XCoefficient"></param>
- public void SetXCoefficient(double XCoefficient)
- {
- this.XCoefficient = XCoefficient;
- }
- /// <summary>
- /// 设置坐标转换Y系数
- /// </summary>
- public void SetYCoefficient(double YCoefficient)
- {
- this.YCoefficient = YCoefficient;
- }
- /// <summary>
- /// 获取结果最长长边
- /// </summary>
- /// <param name="Rows"></param>
- /// <returns></returns>
- public BoundingRectangleMdoel SizeCalculation(List<RowStartEndCol> Rows)
- {
- //Stopwatch stopwatch = Stopwatch.StartNew();
- var points = ConvexHull(Rows);
- //stopwatch.Stop();
- //Console.WriteLine($"凸包计算耗时:{stopwatch.Elapsed}");
- //var test = CalculateMinimumBoundingRectangle(points);
- var result = CoefficientCalculateMinimumBoundingRectangle(points);
- return result;
- }
- public MaxLengthModel SizeCalculation2(List<RowStartEndCol> Rows)
- {
- var points = ConvexHull(Rows);
- var result = CoefficientRotatingCalipers(points);
- return result;
- }
- /// <summary>
- /// 凸包点集合获取
- /// </summary>
- /// <param name="Rows"></param>
- /// <returns></returns>
- public List<Point> ConvexHull(List<RowStartEndCol> Rows)
- {
- List<Point> points = Rows.Select(o => new Point(o.StartCol, (int)o.RowsCol)).ToList();
- points.AddRange(Rows.Select(o => new Point(o.EndCol, (int)o.RowsCol)).ToList());
- points = points.OrderBy(o => o.X).ThenBy(o => o.Y).ToList();
- var lower = new List<Point>();
- foreach (var p in points)
- {
- while (lower.Count >= 2 && Cross(lower[lower.Count - 2], lower[lower.Count - 1], p) <= 0)
- lower.RemoveAt(lower.Count - 1);
- lower.Add(p);
- }
- var upper = new List<Point>();
- for (int i = points.Count - 1; i >= 0; i--)
- {
- var p = points[i];
- while (upper.Count >= 2 && Cross(upper[upper.Count - 2], upper[upper.Count - 1], p) <= 0)
- upper.RemoveAt(upper.Count - 1);
- upper.Add(p);
- }
- lower.RemoveAt(lower.Count - 1);
- upper.RemoveAt(upper.Count - 1);
- lower.AddRange(upper);
- return lower;
- }
- /// <summary>
- /// 凸包点集合获取,输出原点集
- /// </summary>
- /// <param name="Rows"></param>
- /// <returns></returns>
- public List<Point> ConvexHull(List<RowStartEndCol> Rows, out List<Point> points)
- {
- points = Rows.Select(o => new Point(o.StartCol, (int)o.RowsCol)).ToList();
- points.AddRange(Rows.Select(o => new Point(o.EndCol, (int)o.RowsCol)).ToList());
- points = points.OrderBy(o => o.X).ThenBy(o => o.Y).ToList();
- var lower = new List<Point>();
- foreach (var p in points)
- {
- while (lower.Count >= 2 && Cross(lower[lower.Count - 2], lower[lower.Count - 1], p) <= 0)
- lower.RemoveAt(lower.Count - 1);
- lower.Add(p);
- }
- var upper = new List<Point>();
- for (int i = points.Count - 1; i >= 0; i--)
- {
- var p = points[i];
- while (upper.Count >= 2 && Cross(upper[upper.Count - 2], upper[upper.Count - 1], p) <= 0)
- upper.RemoveAt(upper.Count - 1);
- upper.Add(p);
- }
- lower.RemoveAt(lower.Count - 1);
- upper.RemoveAt(upper.Count - 1);
- lower.AddRange(upper);
- return lower;
- }
- /// <summary>
- /// 凸包点集合获取,同时检测较大的内凹
- /// </summary>
- /// <param name="Rows"></param>
- /// <returns>返回凸包点集</returns>
- public ConvexResultClass ConvexHullWithConcavityDetection(List<RowStartEndCol> Rows)
- {
- ConvexResultClass result = null;
- // 构建原始点集
- var lower = ConvexHull(Rows, out List<Point> originalPoints);
- // 检测内凹
- ConvexResultClass ConvexResult = DetectConcavity(originalPoints, lower);
- return result = new ConvexResultClass()
- {
- convexHull = lower,
- hasSignificantConcavity = ConvexResult.hasSignificantConcavity,
- concavityRatio = ConvexResult.concavityRatio,
- };
- }
- #endregion
- #region 私有方法
- 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);
- }
- }
- /// <summary>
- /// 对外通知事件
- /// </summary>
- private void OnWorkCompleted(List<ActiveObjectClass> activeObject)
- {
- if(activeObject == null) return;
- ActiveObjectEventArgsClass activeObjectEventArgs = new ActiveObjectEventArgsClass(activeObject);
- // 触发事件
- WorkCompleted?.Invoke(this, activeObjectEventArgs);
- }
- private bool IsPrintLightOnError = false;
- List<ValidRegionModelClass> currentRegions = null;
- /// <summary>
- /// 处理单行像素数据
- /// 返回值为false的时候无活跃物体转变为历史物体
- /// 返回值为true的时候有活跃物体转变为历史物体
- /// </summary>
- /// <param name="image">当前行像素数组</param>
- private bool ProcessLine(IFrameOut imagedata, int RowNo)
- {
- //Stopwatch stopwatch = Stopwatch.StartNew();
- bool result = false;
- // 步骤1:检测当前行的有效区域
- var currentRegions = FindValidRegions(imagedata.Image, RowNo);
- if (currentRegions.Count == 0) return result;
- if (currentRegions.Count == 1)
- {
- if (currentRegions[0].End - (currentRegions[0]).Start + 1 == imagedata.Image.Width)
- {
- if (!IsPrintLightOnError)
- {
- FaultLog.RecordLogMessage("The current effective area is the entire row. Check the field of view and light source", 6);
- IsPrintLightOnError = true;
- }
- return false;
- }
- }
- IsPrintLightOnError = false;
- foreach (var region in currentRegions)
- {
- if(region.End-region.Start<=3)
- {
- continue;
- }
- // 查找全部可合并的活跃物体(有重叠+在允许间隔内)
- _rwLock.EnterReadLock();
- var matcheds = activeObjects.Where(o =>
- IsOverlapping(o, region) &&
- (currentLine - o.LastSeenLine - 1) <= shuLiConfig.MAX_GAP).ToList();
- _rwLock.ExitReadLock();
- //当有多个可合并的活跃物体时,将多个物体合并
- if (matcheds.Count >= 2)
- {
- // 合并有效区域队列
- var CopeRowsData = new List<RowStartEndCol>();
- matcheds.ForEach(o => CopeRowsData = CopeRowsData.Concat(o.RowsData).ToList());
- // 合并有效区域并保存在新的区域中
- var MergeMatched = new ActiveObjectClass
- {
- MinStartCol = matcheds.Min(o => o.MinStartCol),
- MaxEndCol = matcheds.Max(o => o.MaxEndCol),
- StartLine = matcheds.Min(o => o.StartLine),
- LastSeenLine = matcheds.Max(o => o.LastSeenLine),
- PreSeenLineStartCol = matcheds.Min(o => o.PreSeenLineStartCol),
- PreSeenLineEndCol = matcheds.Max(o => o.PreSeenLineEndCol),
- StartCheckTime = matcheds.Min(o => o.StartCheckTime),
- EndCheckTime = matcheds.Max(o => o.EndCheckTime),
- Area = matcheds.Sum(o => o.Area),
- RowsData = CopeRowsData,
- ImageWidth = matcheds.FirstOrDefault().ImageWidth,
- //StateCode = 8
- };
- _rwLock.EnterWriteLock();
- // 从活跃区域中删除被合并的区域
- matcheds.ForEach(o => activeObjects.Remove(o));
- // 保存新的区域到活跃区域中
- activeObjects.Add(MergeMatched);
- _rwLock.ExitWriteLock();
- }
- _rwLock.EnterReadLock();
- // 搜获可用且可合并的活跃区域
- var matched = activeObjects.FirstOrDefault(o =>
- IsOverlapping(o, region) &&
- (currentLine - o.LastSeenLine - 1) <= shuLiConfig.MAX_GAP);
- _rwLock.ExitReadLock();
- if (matched != null)
- {
- // 合并区域:扩展物体边界并更新状态
- matched.MinStartCol = Math.Min(matched.MinStartCol, region.Start);
- matched.MaxEndCol = Math.Max(matched.MaxEndCol, region.End);
- matched.Area += region.End - region.Start + 1;
- matched.LastSeenLine = currentLine;
- matched.RowsData.Add(new RowStartEndCol
- {
- StartCol = region.Start,
- EndCol = region.End,
- RowsCol = currentLine,
- });
- if(matched.LastSeenLineEndCol==-1)
- {
- matched.LastSeenLineEndCol = region.End;
- }
- else
- {
- matched.LastSeenLineEndCol = Math.Max(matched.LastSeenLineEndCol, region.End);
- }
- if (matched.LastSeenLineStartCol == -1)
- {
- matched.LastSeenLineStartCol = region.Start;
- }
- else
- {
- matched.LastSeenLineStartCol = Math.Min(matched.LastSeenLineStartCol, region.Start);
- }
- }
- else
- {
- _rwLock.EnterWriteLock();
- // 创建新物体(首次出现的区域)
- activeObjects.Add(new ActiveObjectClass
- {
- MinStartCol = region.Start,
- MaxEndCol = region.End,
- StartLine = currentLine,
- LastSeenLine = currentLine,
- PreSeenLineStartCol = region.Start,
- PreSeenLineEndCol = region.End,
- StartCheckTime = DateTime.Now,
- PictureStartReadTime = FromUnixTimestamp((long)imagedata.HostTimeStamp),
- Area = region.End - region.Start + 1,
- ImageWidth = IdentifyImageWidth,
- RowsData = new List<RowStartEndCol> {
- new RowStartEndCol {
- StartCol = region.Start,
- EndCol = region.End,
- RowsCol = currentLine,
- }
- }
- });
- _rwLock.ExitWriteLock();
- }
- }
- foreach (var obj in activeObjects)
- {
- if(obj.LastSeenLineEndCol!=-1)
- {
- obj.PreSeenLineEndCol = obj.LastSeenLineEndCol;
- obj.LastSeenLineEndCol = -1;
- }
- if (obj.LastSeenLineStartCol != -1)
- {
- obj.PreSeenLineStartCol = obj.LastSeenLineStartCol;
- obj.LastSeenLineStartCol = -1;
- }
- }
- //stopwatch.Stop();
- //if (stopwatch.ElapsedMilliseconds > 1)
- //{
- // FaultLog.RecordErrorMessage($"ShuLiClass-ProcessLine:图像区域合并超时,此次识别耗时:{stopwatch.Elapsed}");
- //}
- return result;
- }
- // 辅助类定义
- private enum OperationType
- {
- MergeMultiple,
- Update,
- Add
- }
- private class OperationItem
- {
- public OperationType Type { get; set; }
- public List<ActiveObjectClass> ObjectsToRemove { get; set; }
- public ActiveObjectClass ObjectToAdd { get; set; }
- public ActiveObjectClass ObjectToUpdate { get; set; }
- public ValidRegionModelClass Region { get; set; }
- }
- List<ValidRegionModelClass> regions = new List<ValidRegionModelClass>();
- /// <summary>
- /// 检测有效物体区域(横向连续黑色像素段)
- /// </summary>
- /// <param name="line">当前行像素数组</param>
- /// <returns>有效区域列表(起始/结束位置)</returns>
- private List<ValidRegionModelClass> FindValidRegions(IImage image, int RowNo)
- {
- regions.Clear();
- int start = -1; // 当前区域起始标记
- int end = -1;
- int imageWidth = (int)image.Width;
- //var pixelSpan = image.PixelData.AsSpan();
- var pixelSpan = image.PixelData;
- // 遍历所有像素列
- if (shuLiConfig.IsIdentifyRoiOpen)
- {
- for (int i = imageWidth * RowNo + shuLiConfig.IdentifyStartX; i < imageWidth * RowNo + shuLiConfig.IdentifyStopX; i++)
- {
- if (pixelSpan[i] < shuLiConfig.RegionThreshold&& start == -1) // 发现黑色像素
- {
- start = i % imageWidth; // 开始新区域
- }
- else if (start != -1) // 遇到白色像素且存在进行中的区域
- {
- end = (i - 1) % imageWidth;
- // 检查区域宽度是否达标
- regions.Add(new ValidRegionModelClass()
- {
- Start = start,
- End = end
- }); // 记录有效区域
- start = -1; // 重置区域标记
- end = -1;
- }
- }
- }
- else
- {
- for (int i = imageWidth * RowNo; i < imageWidth * (RowNo + 1); i++)
- {
- if (pixelSpan[i] < shuLiConfig.RegionThreshold) // 发现黑色像素
- {
- if (start == -1) start = i % imageWidth; // 开始新区域
- }
- else if (start != -1) // 遇到白色像素且存在进行中的区域
- {
- end = (i - 1) % imageWidth;
- regions.Add(new ValidRegionModelClass()
- {
- Start = start,
- End = end
- }); // 记录有效区域
- start = -1; // 重置区域标记
- end = -1;
- }
- }
- }
- // 处理行尾未闭合的区域
- if (start != -1 && image.Width - start >= shuLiConfig.NoiseFilter_Threshold)
- {
- regions.Add(new ValidRegionModelClass()
- {
- Start = start,
- End = (int)image.Width - 1
- });
- }
- return new List<ValidRegionModelClass>(regions);
- }
- /// <summary>
- /// 判断区域重叠(与活跃物体的横向坐标重叠检测)
- /// </summary>
- /// <param name="obj">活跃物体</param>
- /// <param name="region">当前区域</param>
- /// <returns>是否发生重叠</returns>
- private bool IsOverlapping(ActiveObjectClass obj, ValidRegionModelClass region)
- {
- // 判断区域是否不相交的逆条件
- return !(region.End < obj.PreSeenLineStartCol || region.Start > obj.PreSeenLineEndCol);
- }
- /// <summary>
- /// 通道区域判定
- /// </summary>
- /// <param name="activeObject"></param>
- /// <returns></returns>
- private int ActiveChannel(ActiveObjectClass activeObject)
- {
- int result = -1;
- int StartChannel = activeObject.MinStartCol / ChannelWidth;
- int EndChannel = activeObject.MaxEndCol / ChannelWidth;
- if (StartChannel == EndChannel)
- {
- result = StartChannel;
- }
- else if (EndChannel - StartChannel > 1)
- {
- Console.WriteLine("ActiveChannel-Error");
- //error
- }
- else
- {
- result = _ChannelsRoi[StartChannel] - activeObject.MinStartCol > activeObject.MaxEndCol - _ChannelsRoi[StartChannel] ? StartChannel : EndChannel;
- }
- return result;
- }
- /// <summary>
- /// 检测是否存在显著的内凹
- /// </summary>
- /// <param name="originalPoints">原始点集</param>
- /// <param name="convexHull">凸包点集</param>
- /// <returns>是否存在显著内凹和程度比值</returns>
- private ConvexResultClass DetectConcavity(List<Point> originalPoints, List<Point> convexHull)
- {
- if (convexHull.Count < 3) return new ConvexResultClass()
- {
- hasSignificantConcavity = false,
- concavityRatio = 0,
- };
- // 检查原始点集中有多少点在凸包内部(即非凸包顶点)
- HashSet<Point> hullPoints = new HashSet<Point>(convexHull);
- var internalPoints = originalPoints.Where(p => !hullPoints.Contains(p)).ToList();
- if (internalPoints.Count == 0)
- {
- // 所有点都在凸包上,没有内凹
- return new ConvexResultClass()
- {
- hasSignificantConcavity = false,
- concavityRatio = 0,
- };
- }
- // 计算凸包面积
- double convexHullArea = CalculatePolygonArea(SortPointsByNearestNeighbor(convexHull));
- double PointsArea = CalculatePolygonArea(SortPointsByNearestNeighbor(originalPoints));
- var concavityMeasure = (convexHullArea - PointsArea) / convexHullArea;
- // 判断是否存在显著内凹(可以根据实际需求调整阈值)
- bool hasSignificantConcavity = concavityMeasure > 0.3;
- return new ConvexResultClass()
- {
- hasSignificantConcavity = hasSignificantConcavity,
- concavityRatio = concavityMeasure,
- };
- }
- /// <summary>
- /// 计算封闭多边形的面积(鞋带公式)
- /// </summary>
- /// <param name="polygon">多边形顶点(必须是封闭的)</param>
- /// <returns>面积</returns>
- private double CalculatePolygonArea(List<Point> polygon)
- {
- if (polygon.Count < 3) return 0;
- // 确保多边形是封闭的
- var closedPolygon = new List<Point>(polygon);
- if (closedPolygon.First() != closedPolygon.Last())
- {
- closedPolygon.Add(closedPolygon[0]);
- }
- double area = 0;
- for (int i = 0; i < closedPolygon.Count - 1; i++)
- {
- area += (double)closedPolygon[i].X * closedPolygon[i + 1].Y;
- area -= (double)closedPolygon[i].Y * closedPolygon[i + 1].X;
- }
- return Math.Abs(area) / 2.0;
- }
- /// <summary>
- /// 凸包最长边,结果通过系数计算
- /// </summary>
- /// <param name="hull"></param>
- /// <returns></returns>
- public MaxLengthModel CoefficientRotatingCalipers(List<Point> hull)
- {
- //老方法
- MaxLengthModel result = new MaxLengthModel();
- int n = hull.Count;
- if (n == 1)
- {
- result = null;
- return result;
- }
- if (n == 2)
- {
- result.MaxLength = Distance(hull[0], hull[1]);
- result.Point1Index = 0;
- result.Point1 = hull[0];
- result.Point2Index = 1;
- result.Point2 = hull[1];
- return result;
- }
- double maxDist = 0;
- int pointIndex1 = 0;
- int pointIndex2 = 0;
- for (int i = 0; i < n; i++)
- {
- for (int j = i + 1; j < n; j++)
- {
- if (CoefficientDistance(hull[i], hull[j]) > maxDist)
- {
- maxDist = CoefficientDistance(hull[i], hull[j]);
- pointIndex1 = i;
- pointIndex2 = j;
- }
- }
- }
- result.Point1 = hull[pointIndex1];
- result.Point1Index = pointIndex1;
- result.Point2 = hull[pointIndex2];
- result.Point2Index = pointIndex2;
- result.MaxLength = maxDist;
- return result;
- }
- /// <summary>
- /// 凸包最长边
- /// </summary>
- /// <param name="hull"></param>
- /// <returns></returns>
- public MaxLengthModel RotatingCalipers(List<Point> hull)
- {
- //老方法
- MaxLengthModel result = new MaxLengthModel();
- int n = hull.Count;
- if (n == 1)
- {
- result = null;
- return result;
- }
- if (n == 2)
- {
- result.MaxLength = Distance(hull[0], hull[1]);
- result.Point1Index = 0;
- result.Point1 = hull[0];
- result.Point2Index = 1;
- result.Point2 = hull[1];
- return result;
- }
- double maxDist = 0;
- int pointIndex1 = 0;
- int pointIndex2 = 0;
- for (int i = 0; i < n; i++)
- {
- for (int j = i + 1; j < n; j++)
- {
- if (Distance(hull[i], hull[j]) > maxDist)
- {
- maxDist = Distance(hull[i], hull[j]);
- pointIndex1 = i;
- pointIndex2 = j;
- }
- }
- }
- result.Point1 = hull[pointIndex1];
- result.Point1Index = pointIndex1;
- result.Point2 = hull[pointIndex2];
- result.Point2Index = pointIndex2;
- result.MaxLength = Distance(result.Point1, result.Point2);
- return result;
- }
- /// <summary>
- /// 计算凸包的最小外接矩形
- /// </summary>
- /// <param name="convexHull">凸包顶点列表(按逆时针顺序排列)</param>
- /// <returns>最小外接矩形的四个顶点</returns>
- public BoundingRectangleMdoel CalculateMinimumBoundingRectangle(List<Point> convexHull)
- {
- BoundingRectangleMdoel result = new BoundingRectangleMdoel();
- if (convexHull == null || convexHull.Count < 3)
- return null;
- double minArea = double.MaxValue;
- int n = convexHull.Count;
- // 遍历每一条边作为基准边
- for (int i = 0; i < n; i++)
- {
- Point edgeStart = convexHull[i];
- Point edgeEnd = convexHull[(i + 1) % n];
- // 计算当前边的角度
- double angle = Math.Atan2(edgeEnd.Y - edgeStart.Y, edgeEnd.X - edgeStart.X);
- // 将所有点绕原点旋转-angle角度,使当前边与x轴平行
- var rotatedPoints = convexHull.Select(p => RotatePoint(p, -angle)).ToList();
- // 找到包围盒
- int minX = rotatedPoints.Min(p => p.X);
- int maxX = rotatedPoints.Max(p => p.X);
- int minY = rotatedPoints.Min(p => p.Y);
- int maxY = rotatedPoints.Max(p => p.Y);
- // 计算面积
- double area = (maxX - minX) * (maxY - minY);
- // 如果面积更小,则更新最小矩形
- if (area < minArea)
- {
- minArea = area;
- // 构造矩形的四个顶点并旋转回原来的角度
- var rectangle = new List<Point>
- {
- RotatePoint(new Point(minX, minY), angle),
- RotatePoint(new Point(maxX, minY), angle),
- RotatePoint(new Point(maxX, maxY), angle),
- RotatePoint(new Point(minX, maxY), angle)
- };
- result.points = rectangle;
- result.Angle = angle;
- result.CenterPoint = RotatePoint(new Point((maxX + minX) / 2, (maxY + minY) / 2), angle);
- }
- }
- double l1 = Distance(result.points[0], result.points[1]);
- double l2 = Distance(result.points[1], result.points[2]);
- if (l1 < l2)
- {
- result.WidthPoints = new List<Point>
- {
- result.points[0],
- result.points[1],
- };
- result.HeightPoints = new List<Point>
- {
- result.points[1],
- result.points[2],
- };
- }
- else
- {
- result.WidthPoints = new List<Point>
- {
- result.points[2],
- result.points[1],
- };
- result.HeightPoints = new List<Point>
- {
- result.points[1],
- result.points[0],
- };
- }
- result.Height = Math.Max(l1, l2);
- result.Width = Math.Min(l1, l2);
- return result;
- }
- /// <summary>
- /// 计算凸包的最小外接矩形
- /// </summary>
- /// <param name="convexHull">凸包顶点列表(按逆时针顺序排列)</param>
- /// <returns>最小外接矩形的四个顶点</returns>
- public BoundingRectangleMdoel CoefficientCalculateMinimumBoundingRectangle(List<Point> convexHull)
- {
- BoundingRectangleMdoel result = new BoundingRectangleMdoel();
- if (convexHull == null || convexHull.Count < 3)
- return null;
- double minArea = double.MaxValue;
- int n = convexHull.Count;
- // 遍历每一条边作为基准边
- for (int i = 0; i < n; i++)
- {
- Point edgeStart = convexHull[i];
- Point edgeEnd = convexHull[(i + 1) % n];
- // 计算当前边的角度
- double angle = Math.Atan2(edgeEnd.Y - edgeStart.Y, edgeEnd.X - edgeStart.X);
- // 将所有点绕原点旋转-angle角度,使当前边与x轴平行
- var rotatedPoints = convexHull.Select(p => RotatePoint(p, -angle)).ToList();
- // 找到包围盒
- int minX = rotatedPoints.Min(p => p.X);
- int maxX = rotatedPoints.Max(p => p.X);
- int minY = rotatedPoints.Min(p => p.Y);
- int maxY = rotatedPoints.Max(p => p.Y);
- // 计算面积
- double area = (maxX - minX) * (maxY - minY);
- // 如果面积更小,则更新最小矩形
- if (area < minArea)
- {
- minArea = area;
- // 构造矩形的四个顶点并旋转回原来的角度
- var rectangle = new List<Point>
- {
- RotatePoint(new Point(minX, minY), angle),
- RotatePoint(new Point(maxX, minY), angle),
- RotatePoint(new Point(maxX, maxY), angle),
- RotatePoint(new Point(minX, maxY), angle)
- };
- result.points = rectangle;
- result.Angle = angle;
- result.CenterPoint = RotatePoint(new Point((maxX + minX) / 2, (maxY + minY) / 2), angle);
- }
- }
- double l1 = CoefficientDistance(result.points[0], result.points[1]);
- double l2 = CoefficientDistance(result.points[1], result.points[2]);
- if (l1 < l2)
- {
- result.WidthPoints = new List<Point>
- {
- result.points[0],
- result.points[1],
- };
- result.HeightPoints = new List<Point>
- {
- result.points[1],
- result.points[2],
- };
- }
- else
- {
- result.WidthPoints = new List<Point>
- {
- result.points[2],
- result.points[1],
- };
- result.HeightPoints = new List<Point>
- {
- result.points[1],
- result.points[0],
- };
- }
- result.Height = Math.Max(l1, l2);
- result.Width = Math.Min(l1, l2);
- return result;
- }
- public List<Point> SortPointsByNearestNeighbor(List<Point> points)
- {
- if (points.Count <= 1) return points;
- var sorted = new List<Point>();
- var remaining = new HashSet<Point>(points);
- // 选择起始点(例如最左边的点)
- var current = remaining.OrderBy(p => p.X).First();
- sorted.Add(current);
- remaining.Remove(current);
- while (remaining.Any())
- {
- // 找到最近的点
- var nearest = remaining.OrderBy(p => Distance(current, p)).First();
- sorted.Add(nearest);
- remaining.Remove(nearest);
- current = nearest;
- }
- return sorted;
- }
- /// <summary>
- /// 绕原点旋转点
- /// </summary>
- /// <param name="point">待旋转的点</param>
- /// <param name="angle">旋转角度(弧度)</param>
- /// <returns>旋转后的点</returns>
- private static Point RotatePoint(Point point, double angle)
- {
- double cos = Math.Cos(angle);
- double sin = Math.Sin(angle);
- return new Point(
- (int)(point.X * cos - point.Y * sin),
- (int)(point.X * sin + point.Y * cos)
- );
- }
- /// <summary>
- /// 计算点到线段的距离
- /// </summary>
- /// <param name="a">线段点1</param>
- /// <param name="b">线段点2</param>
- /// <param name="c">点三</param>
- /// <returns></returns>
- private double DistanceToLine(Point a, Point b, Point c)
- {
- double area = Math.Abs(Area2(a, b, c));
- double baseLength = Distance(a, b);
- return area / baseLength;
- }
- // 计算向量叉积
- private int Cross(Point o, Point a, Point b) =>
- (a.X - o.X) * (b.Y - o.Y) - (a.Y - o.Y) * (b.X - o.X);
- // 计算三角形面积的两倍
- private int Area2(Point a, Point b, Point c) =>
- (b.X - a.X) * (c.Y - a.Y) - (b.Y - a.Y) * (c.X - a.X);
- // 计算两点间距离
- private double Distance(Point a, Point b)
- {
- int dx = a.X - b.X;
- int dy = a.Y - b.Y;
- return Math.Sqrt((dx * dx) + (dy * dy));
- }
- private double CoefficientDistance(Point a,Point b)
- {
- double dx = (a.X - b.X) * XCoefficient;
- double dy = (a.Y - b.Y) * YCoefficient;
- return Math.Sqrt((dx * dx) + (dy * dy));
- }
- private bool TryAdd(List<ActiveObjectClass> list, ActiveObjectClass item, int maxSize)
- {
- list.Add(item);
- if(item.StateCode!=9&& item.StateCode != 10)
- {
- Interlocked.Increment(ref _HistoryActiveNum);
- if (item.StateCode == 0)
- {
- Interlocked.Increment(ref _OkHistoryNum);
- }
- else
- {
- Interlocked.Increment(ref _NgHistoryNum);
- }
- }
- if (list.Count > maxSize)
- {
- list.Remove(list[list.Count - maxSize]);
- }
- return true;
- }
- #endregion
- #region 线程方法
- //信号量
- public SemaphoreSlim QueueSemaphore { get; set; }
- //取消令牌
- public CancellationTokenSource CancellationTokenSource { get; set; }
- //图像处理线程
- public Task ProcessingTask { get; set; }
- /// <summary>
- /// 图像处理线程运行方法
- /// </summary>
- /// <param name="token"></param>
- private async void IdentifyImageProcessTask(CancellationToken token)
- {
- const int batchSize = 5; // 每批处理的最大图像数量
- var batch = new List<IFrameOut>(batchSize); // 存储当前批次的图像
- while (!token.IsCancellationRequested)
- {
- try
- {
- // 等待图像数据
- await QueueSemaphore.WaitAsync(token);
- // 收集一批图像(严格按队列顺序)
- while (batch.Count < batchSize && RingBuffer_IFrameDatas.TryDequeue(out IFrameOut imageData))
- {
- batch.Add(imageData);
- }
- // 顺序处理当前批次的所有图像
- foreach (var image in batch)
- {
- if (image != null)
- {
- //识别
- ProcessImageSequence(image);
- image.Dispose();
- }
- else
- {
- continue;
- }
- }
- batch.Clear();
- }
- catch(Exception ex)
- {
- LOG.error("ShuLiClass-IdentifyImageProcessTask:" + ex.Message);
- }
- }
- LOG.log("图像处理线程运行结束", 6);
- }
- #endregion
- }
- }
|