| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030 |
- using CCDCount.DLL.AlarmTools;
- using CCDCount.MODEL.ConfigModel;
- using CCDCount.MODEL.ResultModel;
- using CCDCount.MODEL.ShuLiModel;
- using LogClass;
- using MvCameraControl;
- using Org.BouncyCastle.Utilities;
- 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.Runtime.InteropServices.WindowsRuntime;
- using System.Threading;
- using System.Threading.Tasks;
- using System.Windows.Media.Media3D;
- using static iTextSharp.text.pdf.AcroFields;
- 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 ConcurrentQueue<IFrameOut> IFrameDatas = new ConcurrentQueue<IFrameOut>(); //图像数据队列
- private Thread IdentifyImageProcessThread = null; // 识别线程
- private bool IsIdentify = false; //线程是否开始识别的标志
- 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 IFrameDatas.Count; } }
- private ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim();
- private double XCoefficient = 1;
- private double YCoefficient = 2;
- private DateTime[] ChannelIntervalTime = new DateTime[8];
- #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;
- }
- List<ActiveObjectClass> lostObjects = new List<ActiveObjectClass>();
- /// <summary>
- /// 处理图像序列的主入口
- /// </summary>
- /// <param name="image">图像像素数据</param>
- /// <param name="ImageWidth">图像宽</param>
- /// <param name="currentLine">当前行数</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++)
- {
- result = ProcessLine2(spanFromArr,image.HostTimeStamp,image.Image.Width, i);
- currentLine += 1;
- }
- //识别到结果并输出
- _rwLock.EnterReadLock();
- // 清理超时未更新的物体
- lostObjects = activeObjects
- .Where(o => (currentLine - o.LastSeenLine) > shuLiConfig.MAX_GAP || (o.LastSeenLine - o.StartLine) > shuLiConfig.MAX_Idetify_Height)
- .ToList();
- _rwLock.ExitReadLock();
- List<ActiveObjectClass> OneActive = new List<ActiveObjectClass>();
- // 有物体转变为活跃物体,返回值转为true
- if (lostObjects.Count > 0)
- {
- Stopwatch stopwatch = Stopwatch.StartNew();
- result = true;
- //stopwatch.Restart();
- foreach (var item in lostObjects)
- {
- item.PictureEndReadTime = FromUnixTimestamp((long)image.HostTimeStamp);
- DateTime NowTime = DateTime.Now;
- var AllPictureUseTime = (item.PictureEndReadTime - item.PictureStartReadTime).TotalMilliseconds;
- var PictureUsetime = (NowTime - item.PictureStartReadTime).TotalMilliseconds;
- if (PictureUsetime - AllPictureUseTime > 20)
- LOG.error($"算法耗时超过了20ms,相机取图总耗时:{AllPictureUseTime},从取第一张图到现在的耗时{PictureUsetime}");
- //噪点判定
- if (item.Area < shuLiConfig.NoiseFilter_Threshold)
- {
- item.StateCode = 9;
- //LOG.log(string.Format("噪点过滤,噪点面积:{0}", item.Area), 6);
- continue;
- }
- if(item.StartLine == item.LastSeenLine)
- {
- item.MaxLength = item.MaxEndCol - item.MinStartCol;
- item.StateCode = 10;
- LOG.log(string.Format("颗粒{0}的有效高度仅一行", item.Num));
- }
- else
- {
- if((item.LastSeenLine - item.StartLine) < shuLiConfig.MAX_Idetify_Height)
- {
- var CalculationResult = SizeCalculation(item.RowsData);
- if(CalculationResult!=null)
- {
- item.MaxLength = CalculationResult.Height;
- }
- //item.hasSignificantConcavity = CalculationResult.hasSignificantConcavity;
- //item.concavityRatio = CalculationResult.concavityRatio;
- //if(CalculationResult.hasSignificantConcavity)
- //{
- // if(CalculationResult.concavityRatio>0.3)
- // {
- // item.StateCode= 11;
- // }
- //}
- }
- else
- {
- item.StateCode = 7;
- }
- }
- if (shuLiConfig.PandingCode != -1)
- {
- if (item.StateCode != -1)
- {
- if (item.StateCode == 8)
- {
- //LOG.log(string.Format("颗粒编号{0}:疑似叠片或缺损", item.Num));
- }
- else if (item.StateCode == 7)
- {
- //LOG.log(string.Format("颗粒编号{0}:视野被遮挡", item.Num));
- }
- }
- else if (item.Area < shuLiConfig.MinArea
- && (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 1))
- {
- item.StateCode = 6;
- //LOG.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 = 5;
- //LOG.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;
- //LOG.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;
- //LOG.log(string.Format("颗粒编号{0}:超长粒", item.Num));
- //Console.WriteLine("颗粒编号{0}:超长粒", item.Num);
- }
- else
- {
- item.StateCode = 0;
- //LOG.log(string.Format("颗粒编号{0}:正常粒", item.Num));
- //Console.WriteLine("颗粒编号{0}:正常粒", item.Num);
- }
- }
- if(item.StateCode != 7&&item.StateCode!=9)
- {
- //转为历史物体,添加缺少的参数
- item.Num = Interlocked.Increment(ref ObjectNum);
- item.ChannelNO = ActiveChannel(item);
- }
- item.EndCheckTime = DateTime.Now;
- if(item.StateCode != 9)
- OneActive.Add(item);
- }
- stopwatch.Stop();
- if (stopwatch.ElapsedMilliseconds > 1)
- {
- Console.WriteLine($"ShuLiClass-ProcessImageSequence:图像结果分析-结果参数补全,此次识别耗时:{stopwatch.Elapsed}");
- //FaultLog.RecordErrorMessage($"ShuLiClass-ProcessImageSequence:图像结果分析-结果参数补全,此次识别耗时:{stopwatch.Elapsed}");
- }
- //stopwatch.Restart();
- if (OneActive.Count > 0)
- {
- //触发回调事件
- OnWorkCompleted(OneActive);
- }
- // stopwatch.Stop();
- //if (stopwatch.ElapsedMilliseconds > 1)
- //{
- // FaultLog.RecordErrorMessage($"ShuLiClass-ProcessImageSequence:图像结果分析-结果完成回调函数,此次识别耗时:{stopwatch.Elapsed}");
- //}
- }
- else
- {
- OneActive.Clear();
- }
- _rwLock.EnterWriteLock();
- // 累加到总数并从活跃物体转移到历史物体
- foreach (var item in OneActive)
- {
- TryAdd(historyActiveObjects, item, 2500);
- }
- lostObjects.ForEach(o => activeObjects.Remove(o));
- _rwLock.ExitWriteLock();
- return result;
- }
- /// <summary>
- /// 处理图像序列的主入口
- /// </summary>
- /// <param name="image">图像像素数据</param>
- /// <returns>检测到的物体总数</returns>
- public bool ProcessImageSequence2(IFrameOut image)
- {
- bool result = false;
- ReadOnlySpan<byte> spanFromArr = image.Image.PixelData.AsSpan();
- for (int i = 0; i < image.Image.Height; i++)
- {
- result = ProcessLine2(spanFromArr, image.HostTimeStamp, image.Image.Width, 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}");
- }
- // 快速滤除噪点
- 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));
- }
- 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;
- }
- }
- // 分类处理
- ApplyClassificationRules(item);
- // 添加到历史记录
- if (item.StateCode != 7 && item.StateCode != 9)
- {
- item.Num = Interlocked.Increment(ref ObjectNum);
- item.ChannelNO = ActiveChannel(item);
- }
- item.EndCheckTime = endTime;
- if (item.StateCode != 9)
- {
- processedObjects.Add(item);
- }
- }
- // 抽取分类规则,便于维护
- private void ApplyClassificationRules(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(string.Format("颗粒编号{0}:视野被遮挡", 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;
- return historyActiveObjects.Last();
- }
- /// <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 int GetHistoryActiveNum()
- {
- //lock (_lockObj) // 加锁
- _rwLock.EnterReadLock();
- var result = historyActiveObjects.Where(o=>o.StateCode!=7&&o.StateCode!=9).ToList().Count;
- _rwLock.ExitReadLock();
- return result;
- }
- /// <summary>
- /// 获取历史数据中,正常数据数量
- /// </summary>
- /// <returns></returns>
- public int GetOkHistoryNum()
- {
- //lock (_lockObj)
- _rwLock.EnterReadLock();
- var result = historyActiveObjects.Where(o => o.StateCode != 7 && o.StateCode != 9&& o.StateCode == 0).ToList().Count;
- _rwLock.ExitReadLock();
- return result;
- }
- /// <summary>
- /// 获取历史数据中,异常数据数量
- /// </summary>
- /// <returns></returns>
- public int GetNgHistoryNum()
- {
- //lock (_lockObj)
- _rwLock.EnterReadLock();
- var result = historyActiveObjects.Where(o => o.StateCode != 7 && o.StateCode != 9 && o.StateCode != 0).ToList().Count;
- _rwLock.ExitReadLock();
- return result;
- }
- /// <summary>
- /// 清除历史数据
- /// </summary>
- /// <returns></returns>
- public bool ClearHistoryActive()
- {
- //lock (_lockObj)
- //{
- _rwLock.EnterWriteLock();
- historyActiveObjects.Clear();
- _rwLock.ExitWriteLock();
- return true;
- //}
- }
- /// <summary>
- /// 开启识别
- /// </summary>
- public void StartIdentifyFuntion(int ImaageWidth)
- {
- UpdateIdentifyImageWidth(ImaageWidth);
- InitChannel();
- try
- {
- // 标志位置位true
- IsIdentify = true;
- // 打开识别线程
- IdentifyImageProcessThread = new Thread(IdentifyImageProcess)
- {
- Priority = ThreadPriority.AboveNormal
- };
- IdentifyImageProcessThread.Start();
- 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 void StartIdentifyFuntion2(int ImaageWidth)
- {
- UpdateIdentifyImageWidth(ImaageWidth);
- InitChannel();
- try
- {
- CancellationTokenSource = new CancellationTokenSource();
- var token = CancellationTokenSource.Token;
- // 启动图像处理线程
- 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;
- }
- }
- /// <summary>
- /// 关闭识别
- /// </summary>
- public void StopIdentifyFuntion()
- {
- try
- {
- // 标志位设为false
- IsIdentify = false;
- if (IdentifyImageProcessThread != null && IdentifyImageProcessThread.IsAlive)
- IdentifyImageProcessThread.Join();
- 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;
- }
- }
- public async void StopIdentifyFuntion2()
- {
- 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)
- {
- IFrameDatas.Enqueue(items.Clone() as IFrameOut);
- //IFrameDatas.Enqueue(items );
- }
- /// <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 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;
- //stopwatch.Stop();
- //if (stopwatch.ElapsedMilliseconds > 1)
- //{
- // FaultLog.RecordErrorMessage($"ShuLiClass-ProcessLine:图像连通域检测超时,此次识别耗时:{stopwatch.Elapsed}");
- //}
- //stopwatch.Restart();
- foreach (var region in currentRegions)
- {
- // 查找全部可合并的活跃物体(有重叠+在允许间隔内)
- _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),
- LastSeenLineStartCol = matcheds.Min(o => o.LastSeenLineStartCol),
- LastSeenLineEndCol = matcheds.Max(o => o.LastSeenLineEndCol),
- 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,
- });
- matched.LastSeenLineStartCol = region.Start;
- matched.LastSeenLineEndCol = region.End;
- }
- else
- {
- _rwLock.EnterWriteLock();
- // 创建新物体(首次出现的区域)
- activeObjects.Add(new ActiveObjectClass
- {
- MinStartCol = region.Start,
- MaxEndCol = region.End,
- StartLine = currentLine,
- LastSeenLine = currentLine,
- LastSeenLineStartCol = region.Start,
- LastSeenLineEndCol = 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();
- }
- }
- //stopwatch.Stop();
- //if (stopwatch.ElapsedMilliseconds > 1)
- //{
- // FaultLog.RecordErrorMessage($"ShuLiClass-ProcessLine:图像区域合并超时,此次识别耗时:{stopwatch.Elapsed}");
- //}
- return result;
- }
- private bool ProcessLine2(ReadOnlySpan<byte> image, ulong ImageHostTime, uint imageWidth, int RowNo)
- {
- // 缓存配置值
- int maxGap = shuLiConfig.MAX_GAP;
- long currentTimeLine = currentLine;
- // 步骤1:检测当前行的有效区域
- currentRegions = FindValidRegions(image, (int)imageWidth, RowNo);
- if (currentRegions.Count == 0|| currentRegions == null) return false;
- if (currentRegions.Count == 1)
- {
- if (currentRegions[0].End - (currentRegions[0]).Start + 1 == imageWidth)
- {
- 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;
- // 批量处理操作列表
- var operations = new List<OperationItem>();
- var currentActiveObjects = new List<ActiveObjectClass>();
- // 一次性获取当前活跃物体快照
- _rwLock.EnterReadLock();
- try
- {
- currentActiveObjects = new List<ActiveObjectClass>(activeObjects);
- }
- finally
- {
- _rwLock.ExitReadLock();
- }
- foreach (var region in currentRegions)
- {
- // 查找所有可合并的活跃物体(重叠+在允许间隔内)
- var matcheds = currentActiveObjects.Where(o =>
- IsOverlapping(o, region) &&
- (currentTimeLine - o.LastSeenLine - 1) <= maxGap).ToList();
- // 当有多个可合并的活跃物体时,将多个物体合并
- if (matcheds.Count >= 2)
- {
- // 计算聚合值(单次遍历)
- var aggregateResult = CalculateAggregates(matcheds);
- var mergedObject = new ActiveObjectClass
- {
- MinStartCol = aggregateResult.minStartCol,
- MaxEndCol = aggregateResult.maxEndCol,
- StartLine = aggregateResult.minStartLine,
- LastSeenLine = aggregateResult.maxLastSeenLine,
- LastSeenLineStartCol = aggregateResult.minLastSeenStartCol,
- LastSeenLineEndCol = aggregateResult.maxLastSeenEndCol,
- StartCheckTime = aggregateResult.minStartTime,
- EndCheckTime = aggregateResult.maxEndTime,
- PictureStartReadTime = aggregateResult.pictureMinReadTime,
- Area = aggregateResult.totalArea,
- RowsData = aggregateResult.mergedRowsData,
- ImageWidth = aggregateResult.imageWidth,
- StateCode = 8
- };
- operations.Add(new OperationItem
- {
- Type = OperationType.MergeMultiple,
- ObjectsToRemove = matcheds,
- ObjectToAdd = mergedObject
- });
- // 从currentActiveObjects中移除已处理的物体,避免重复处理
- foreach (var obj in matcheds)
- {
- currentActiveObjects.Remove(obj);
- }
- }
- else
- {
- // 查找单一匹配物体
- var matched = matcheds.FirstOrDefault();
- if (matched != null)
- {
- operations.Add(new OperationItem
- {
- Type = OperationType.Update,
- ObjectToUpdate = matched,
- Region = region
- });
- }
- else
- {
- // 创建新物体
- var newObject = new ActiveObjectClass
- {
- MinStartCol = region.Start,
- MaxEndCol = region.End,
- StartLine = currentTimeLine,
- LastSeenLine = currentTimeLine,
- LastSeenLineStartCol = region.Start,
- LastSeenLineEndCol = region.End,
- StartCheckTime = DateTime.Now,
- PictureStartReadTime = FromUnixTimestamp((long)ImageHostTime),
- Area = region.End - region.Start + 1,
- ImageWidth = IdentifyImageWidth,
- RowsData = new List<RowStartEndCol> {
- new RowStartEndCol {
- StartCol = region.Start,
- EndCol = region.End,
- RowsCol = currentTimeLine,
- }
- }
- };
- operations.Add(new OperationItem
- {
- Type = OperationType.Add,
- ObjectToAdd = newObject
- });
- }
- }
- }
- currentRegions.Clear();
- // 执行所有操作(最小化锁持有时间)
- bool hasChanges = false;
- if (operations.Count > 0)
- {
- _rwLock.EnterWriteLock();
- try
- {
- foreach (var op in operations)
- {
- switch (op.Type)
- {
- case OperationType.MergeMultiple:
- // 移除被合并的物体
- foreach (var objItem in op.ObjectsToRemove)
- {
- activeObjects.Remove(objItem);
- }
- // 添加合并后的物体
- activeObjects.Add(op.ObjectToAdd);
- break;
- case OperationType.Update:
- // 更新现有物体
- var obj = op.ObjectToUpdate;
- obj.MinStartCol = Math.Min(obj.MinStartCol, op.Region.Start);
- obj.MaxEndCol = Math.Max(obj.MaxEndCol, op.Region.End);
- obj.Area += op.Region.End - op.Region.Start + 1;
- obj.LastSeenLine = currentTimeLine;
- obj.RowsData.Add(new RowStartEndCol
- {
- StartCol = op.Region.Start,
- EndCol = op.Region.End,
- RowsCol = currentTimeLine,
- });
- obj.LastSeenLineStartCol = op.Region.Start;
- obj.LastSeenLineEndCol = op.Region.End;
- break;
- case OperationType.Add:
- activeObjects.Add(op.ObjectToAdd);
- break;
- }
- }
- hasChanges = true;
- }
- finally
- {
- _rwLock.ExitWriteLock();
- }
- }
- return hasChanges;
- }
- // 辅助类定义
- 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; }
- }
- // 计算聚合值的方法
- private (int minStartCol, int maxEndCol, long minStartLine, long maxLastSeenLine,
- int minLastSeenStartCol, int maxLastSeenEndCol, DateTime minStartTime,
- DateTime maxEndTime,DateTime pictureMinReadTime,int totalArea, List<RowStartEndCol> mergedRowsData, int imageWidth)
- CalculateAggregates(List<ActiveObjectClass> objects)
- {
- int minStartCol = int.MaxValue;
- int maxEndCol = int.MinValue;
- long minStartLine = int.MaxValue;
- long maxLastSeenLine = int.MinValue;
- int minLastSeenStartCol = int.MaxValue;
- int maxLastSeenEndCol = int.MinValue;
- DateTime minStartTime = DateTime.MaxValue;
- DateTime maxEndTime = DateTime.MinValue;
- DateTime pictureMinReadTime = DateTime.MaxValue;
- int totalArea = 0;
- int imageWidth = objects.FirstOrDefault()?.ImageWidth ?? IdentifyImageWidth;
- var mergedRowsData = new List<RowStartEndCol>();
- foreach (var obj in objects)
- {
- minStartCol = Math.Min(minStartCol, obj.MinStartCol);
- maxEndCol = Math.Max(maxEndCol, obj.MaxEndCol);
- minStartLine = Math.Min(minStartLine, obj.StartLine);
- maxLastSeenLine = Math.Max(maxLastSeenLine, obj.LastSeenLine);
- minLastSeenStartCol = Math.Min(minLastSeenStartCol, obj.LastSeenLineStartCol);
- maxLastSeenEndCol = Math.Max(maxLastSeenEndCol, obj.LastSeenLineEndCol);
- if (obj.StartCheckTime < minStartTime) minStartTime = obj.StartCheckTime;
- if (obj.EndCheckTime > maxEndTime) maxEndTime = obj.EndCheckTime;
- if (obj.PictureStartReadTime<pictureMinReadTime) pictureMinReadTime = obj.PictureStartReadTime;
- totalArea += obj.Area;
- mergedRowsData.AddRange(obj.RowsData);
- }
- return (minStartCol, maxEndCol, minStartLine, maxLastSeenLine,
- minLastSeenStartCol, maxLastSeenEndCol, minStartTime,
- maxEndTime,pictureMinReadTime,totalArea, mergedRowsData, imageWidth);
- }
- 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();
- // 遍历所有像素列
- 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);
- }
- private List<ValidRegionModelClass> FindValidRegions(ReadOnlySpan<byte> image,int imageWidth, int RowNo)
- {
- regions.Clear();
- regions.Capacity = 10;
- ReadOnlySpan<byte> rowSpan;
- if (shuLiConfig.IsIdentifyRoiOpen)
- {
- int offset = RowNo * imageWidth + shuLiConfig.IdentifyStartX;
- int length = Math.Min(shuLiConfig.IdentifyStopX - shuLiConfig.IdentifyStartX,
- imageWidth - shuLiConfig.IdentifyStartX);
- rowSpan = image.Slice(offset, length);
- }
- else
- {
- rowSpan = image.Slice(RowNo * imageWidth, imageWidth);
- }
- int start = -1;
- byte threshold = (byte)shuLiConfig.RegionThreshold;
- for (int i = 0; i < rowSpan.Length; i++)
- {
- if (rowSpan[i] < threshold)
- {
- if (start == -1) start = i;
- }
- else if (start != -1)
- {
- regions.Add(new ValidRegionModelClass { Start = start, End = i - 1 });
- start = -1;
- }
- }
- if (start != -1)
- {
- regions.Add(new ValidRegionModelClass { Start = start, End = rowSpan.Length - 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.LastSeenLineStartCol || region.Start > obj.LastSeenLineEndCol);
- }
- /// <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 (list.Count > maxSize)
- {
- list[list.Count - maxSize].RowsData.Clear();
- }
- return true;
- }
- #endregion
- #region 线程方法
- //信号量
- public SemaphoreSlim QueueSemaphore { get; set; }
- //取消令牌
- public CancellationTokenSource CancellationTokenSource { get; set; }
- //图像处理线程
- public Task ProcessingTask { get; set; }
- /// <summary>
- /// 识别图像线程
- /// </summary>
- private void IdentifyImageProcess()
- {
- Stopwatch stopwatch = Stopwatch.StartNew();
- while (IsIdentify)
- {
- //判断队列中是否有数据
- if (IFrameDatas.Count() > 0)
- {
- stopwatch.Restart();
- if (IFrameDatas.Count() > 5)
- {
- //FaultLog.RecordErrorMessage($"图像数据队列中数据过多,请及时处理!当前数据数量为:{IFrameDatas.Count()}");
- FaultLog.RecordErrorMessage($"There is too much data in the image data queue. Please handle it in a timely manner! The current data quantity is:{IFrameDatas.Count()}");
- if (IFrameDatas.Count() > 100)
- {
- SystemAlarm.AlarmAlert(AlarmMessageList.待识别队列数据堆积,
- $"The image data queue is blocked. Please check the configuration and images",
- "待识别队列数据堆积,请检查设置和图像",
- "DLL:ShuLIClass-IdentifyImageProcess");
- }
- else
- {
- SystemAlarm.AlarmCancel(AlarmMessageList.待识别队列数据堆积);
- }
- }
- IFrameDatas.TryDequeue(out IFrameOut IframeData);
- stopwatch.Stop();
- var ShiBieLuoHouTime = (DateTime.Now - FromUnixTimestamp((long)IframeData.HostTimeStamp)).TotalMilliseconds;
- // 优化: 将日志记录改为条件执行
- if (ShiBieLuoHouTime > 30)
- {
- LOG.error($"算法落后了超过30ms,相机取图总耗时:{ShiBieLuoHouTime}");
- }
- if (stopwatch.ElapsedMilliseconds > 5)
- {
- FaultLog.RecordErrorMessage($"ShuLiClass-IdentifyImageProcess:Image reading timed out, this recognition took time:{stopwatch.Elapsed}");
- }
- if (IframeData != null)
- {
- //识别
- ProcessImageSequence2(IframeData);
- IframeData.Dispose();
- }
- else
- {
- continue;
- }
- }
- else
- {
- Thread.Sleep(1);
- }
- }
- }
- private async void IdentifyImageProcessTask(CancellationToken token)
- {
- Stopwatch stopwatch = Stopwatch.StartNew();
- const int batchSize = 5; // 每批处理的最大图像数量
- var batch = new List<IFrameOut>(batchSize); // 存储当前批次的图像
- while (!token.IsCancellationRequested)
- {
- // 等待图像数据
- await QueueSemaphore.WaitAsync(token);
- //判断队列中是否有数据
- if (IFrameDatas.Count() > 5)
- {
- //FaultLog.RecordErrorMessage($"图像数据队列中数据过多,请及时处理!当前数据数量为:{IFrameDatas.Count()}");
- FaultLog.RecordErrorMessage($"There is too much data in the image data queue. Please handle it in a timely manner! The current data quantity is:{IFrameDatas.Count()}");
- if (IFrameDatas.Count() > 100)
- {
- SystemAlarm.AlarmAlert(AlarmMessageList.待识别队列数据堆积,
- $"The image data queue is blocked. Please check the configuration and images",
- "待识别队列数据堆积,请检查设置和图像",
- "DLL:ShuLIClass-IdentifyImageProcess");
- }
- else
- {
- SystemAlarm.AlarmCancel(AlarmMessageList.待识别队列数据堆积);
- }
- }
- // 收集一批图像(严格按队列顺序)
- while (batch.Count < batchSize && IFrameDatas.TryDequeue(out IFrameOut imageData))
- {
- batch.Add(imageData);
- if (IFrameDatas.Count > 0)
- {
- QueueSemaphore.Release(); // 提前释放信号量,唤醒其他等待线程
- }
- }
- // 顺序处理当前批次的所有图像
- foreach (var image in batch)
- {
- if (image != null)
- {
- //识别
- ProcessImageSequence2(image);
- image.Dispose();
- }
- else
- {
- continue;
- }
- }
- batch.Clear();
- }
- }
- #endregion
- }
- }
|