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 变量
///
/// 当活跃物体转变为历史物体时的回调事件
///
public event EventHandler WorkCompleted;
private List activeObjects = new List(); // 当前跟踪中的物体
private List historyActiveObjects = new List(); // 历史物体
private LockFreeRingBuffer RingBuffer_IFrameDatas = new LockFreeRingBuffer(300);
private long currentLine = 0; //行数记录
private ShuLiConfigClass shuLiConfig = null;// 数粒参数配置文件
public List ChannelsRoi { get { return _ChannelsRoi; } }
private List _ChannelsRoi = new List();
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 公共方法
///
/// 初始化构造方法
///
public ShuLiClass()
{
// 加载默认参数
shuLiConfig = new ShuLiConfigClass()
{
Channel = 8,
PandingCode = 2
};
for(int i = 0; i < ChannelIntervalTime.Length; i++)
{
ChannelIntervalTime[i] = DateTime.Now;
}
}
///
/// 带参数的构造方法
///
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;
}
///
/// 处理图像序列的主入口
///
/// 图像像素数据
/// 检测到的物体总数
public bool ProcessImageSequence(IFrameOut image)
{
bool result = false;
ReadOnlySpan spanFromArr = image.Image.PixelData.AsSpan();
for (int i = 0; i < image.Image.Height; i++)
{
ProcessLine(image, i);
currentLine += 1;
}
// 优化: 减少锁的使用频率,批量处理逻辑
List 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(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 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);
}
}
///
/// 分类规则
///
///
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));
}
}
///
/// 返回最后一个历史物品
///
///
public ActiveObjectClass GetLastActive()
{
if (historyActiveObjects.Count() == 0)
return null;
_rwLock.EnterReadLock();
var result = historyActiveObjects.Last();
_rwLock.ExitReadLock();
return result;
}
///
/// 返回历史物品
///
///
public List GetHistoryActive()
{
//lock (_lockObj) // 加锁
//{
_rwLock.EnterReadLock();
var result = historyActiveObjects.ToList();
_rwLock.ExitReadLock();
return result;
//}
}
///
/// 清除历史数据
///
///
public bool ClearHistoryActive()
{
_HistoryActiveNum = 0;
_OkHistoryNum = 0;
_NgHistoryNum = 0;
_rwLock.EnterWriteLock();
historyActiveObjects.Clear();
_rwLock.ExitWriteLock();
return true;
//}
}
///
/// 开启识别-优化版本
///
///
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;
}
}
///
/// 向识别队列添加一个数据
///
///
public void SetOnceIdentifyImageData(IFrameOut items)
{
RingBuffer_IFrameDatas.TryEnqueue(items.Clone() as IFrameOut);
}
///
/// 保存参数
///
public void SaveConfig()
{
if (!Directory.Exists(".\\Config\\")) Directory.CreateDirectory(".\\Config\\");
XmlStorage.SerializeToXml(shuLiConfig, ".\\Config\\ShuLiConfig.xml");
}
///
/// 更新检测宽度信息
///
///
public void UpdateIdentifyImageWidth(int Width)
{
IdentifyImageWidth = Width;
}
///
/// 初始化通道划分
///
///
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);
}
}
}
///
/// 获取配置信息
///
///
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;
}
///
/// 获取坐标转换系数
///
///
///
public void GetXYCoefficient(out double XCoefficient, out double YCoefficient)
{
XCoefficient = this.XCoefficient;
YCoefficient = this.YCoefficient;
}
///
/// 设置坐标转换X系数
///
///
public void SetXCoefficient(double XCoefficient)
{
this.XCoefficient = XCoefficient;
}
///
/// 设置坐标转换Y系数
///
public void SetYCoefficient(double YCoefficient)
{
this.YCoefficient = YCoefficient;
}
///
/// 获取结果最长长边
///
///
///
public BoundingRectangleMdoel SizeCalculation(List 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 Rows)
{
var points = ConvexHull(Rows);
var result = CoefficientRotatingCalipers(points);
return result;
}
///
/// 凸包点集合获取
///
///
///
public List ConvexHull(List Rows)
{
List 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();
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();
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;
}
///
/// 凸包点集合获取,输出原点集
///
///
///
public List ConvexHull(List Rows, out List 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();
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();
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;
}
///
/// 凸包点集合获取,同时检测较大的内凹
///
///
/// 返回凸包点集
public ConvexResultClass ConvexHullWithConcavityDetection(List Rows)
{
ConvexResultClass result = null;
// 构建原始点集
var lower = ConvexHull(Rows, out List 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);
}
}
///
/// 对外通知事件
///
private void OnWorkCompleted(List activeObject)
{
if(activeObject == null) return;
ActiveObjectEventArgsClass activeObjectEventArgs = new ActiveObjectEventArgsClass(activeObject);
// 触发事件
WorkCompleted?.Invoke(this, activeObjectEventArgs);
}
private bool IsPrintLightOnError = false;
List currentRegions = null;
///
/// 处理单行像素数据
/// 返回值为false的时候无活跃物体转变为历史物体
/// 返回值为true的时候有活跃物体转变为历史物体
///
/// 当前行像素数组
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();
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 {
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 ObjectsToRemove { get; set; }
public ActiveObjectClass ObjectToAdd { get; set; }
public ActiveObjectClass ObjectToUpdate { get; set; }
public ValidRegionModelClass Region { get; set; }
}
List regions = new List();
///
/// 检测有效物体区域(横向连续黑色像素段)
///
/// 当前行像素数组
/// 有效区域列表(起始/结束位置)
private List 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(regions);
}
///
/// 判断区域重叠(与活跃物体的横向坐标重叠检测)
///
/// 活跃物体
/// 当前区域
/// 是否发生重叠
private bool IsOverlapping(ActiveObjectClass obj, ValidRegionModelClass region)
{
// 判断区域是否不相交的逆条件
return !(region.End < obj.PreSeenLineStartCol || region.Start > obj.PreSeenLineEndCol);
}
///
/// 通道区域判定
///
///
///
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;
}
///
/// 检测是否存在显著的内凹
///
/// 原始点集
/// 凸包点集
/// 是否存在显著内凹和程度比值
private ConvexResultClass DetectConcavity(List originalPoints, List convexHull)
{
if (convexHull.Count < 3) return new ConvexResultClass()
{
hasSignificantConcavity = false,
concavityRatio = 0,
};
// 检查原始点集中有多少点在凸包内部(即非凸包顶点)
HashSet hullPoints = new HashSet(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,
};
}
///
/// 计算封闭多边形的面积(鞋带公式)
///
/// 多边形顶点(必须是封闭的)
/// 面积
private double CalculatePolygonArea(List polygon)
{
if (polygon.Count < 3) return 0;
// 确保多边形是封闭的
var closedPolygon = new List(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;
}
///
/// 凸包最长边,结果通过系数计算
///
///
///
public MaxLengthModel CoefficientRotatingCalipers(List 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;
}
///
/// 凸包最长边
///
///
///
public MaxLengthModel RotatingCalipers(List 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;
}
///
/// 计算凸包的最小外接矩形
///
/// 凸包顶点列表(按逆时针顺序排列)
/// 最小外接矩形的四个顶点
public BoundingRectangleMdoel CalculateMinimumBoundingRectangle(List 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
{
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
{
result.points[0],
result.points[1],
};
result.HeightPoints = new List
{
result.points[1],
result.points[2],
};
}
else
{
result.WidthPoints = new List
{
result.points[2],
result.points[1],
};
result.HeightPoints = new List
{
result.points[1],
result.points[0],
};
}
result.Height = Math.Max(l1, l2);
result.Width = Math.Min(l1, l2);
return result;
}
///
/// 计算凸包的最小外接矩形
///
/// 凸包顶点列表(按逆时针顺序排列)
/// 最小外接矩形的四个顶点
public BoundingRectangleMdoel CoefficientCalculateMinimumBoundingRectangle(List 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
{
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
{
result.points[0],
result.points[1],
};
result.HeightPoints = new List
{
result.points[1],
result.points[2],
};
}
else
{
result.WidthPoints = new List
{
result.points[2],
result.points[1],
};
result.HeightPoints = new List
{
result.points[1],
result.points[0],
};
}
result.Height = Math.Max(l1, l2);
result.Width = Math.Min(l1, l2);
return result;
}
public List SortPointsByNearestNeighbor(List points)
{
if (points.Count <= 1) return points;
var sorted = new List();
var remaining = new HashSet(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;
}
///
/// 绕原点旋转点
///
/// 待旋转的点
/// 旋转角度(弧度)
/// 旋转后的点
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)
);
}
///
/// 计算点到线段的距离
///
/// 线段点1
/// 线段点2
/// 点三
///
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 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; }
///
/// 图像处理线程运行方法
///
///
private async void IdentifyImageProcessTask(CancellationToken token)
{
const int batchSize = 5; // 每批处理的最大图像数量
var batch = new List(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
}
}