|
@@ -66,23 +66,6 @@ namespace CCDCount.DLL
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- /// <summary>
|
|
|
|
- /// 处理图像序列的主入口
|
|
|
|
- /// </summary>
|
|
|
|
- /// <param name="image">图像像素数据</param>
|
|
|
|
- /// <param name="ImageWidth">图像宽</param>
|
|
|
|
- /// <param name="currentLine">当前行数</param>
|
|
|
|
- /// <returns>检测到的物体总数</returns>
|
|
|
|
- public bool ProcessImageSequence(byte[] image, int ImageWidth)
|
|
|
|
- {
|
|
|
|
- bool result = false;
|
|
|
|
- // 每张图像是Width*1的二维数组,判断图象分辨率是否正确
|
|
|
|
- if (image.Count() == ImageWidth)
|
|
|
|
- result = ProcessLine(image);
|
|
|
|
- currentLine += 1;
|
|
|
|
- return result;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
/// 处理图像序列的主入口
|
|
/// 处理图像序列的主入口
|
|
/// </summary>
|
|
/// </summary>
|
|
@@ -290,207 +273,6 @@ namespace CCDCount.DLL
|
|
WorkCompleted?.Invoke(this, activeObjectEventArgs);
|
|
WorkCompleted?.Invoke(this, activeObjectEventArgs);
|
|
}
|
|
}
|
|
|
|
|
|
- /// <summary>
|
|
|
|
- /// 处理单行像素数据
|
|
|
|
- /// 返回值为false的时候无活跃物体转变为历史物体
|
|
|
|
- /// 返回值为true的时候有活跃物体转变为历史物体
|
|
|
|
- /// </summary>
|
|
|
|
- /// <param name="image">当前行像素数组</param>
|
|
|
|
- private bool ProcessLine(byte[] image)
|
|
|
|
- {
|
|
|
|
- bool result = false;
|
|
|
|
- // 步骤1:检测当前行的有效区域
|
|
|
|
- var currentRegions = FindValidRegions(image);
|
|
|
|
-
|
|
|
|
- if (currentRegions.Count == 1)
|
|
|
|
- {
|
|
|
|
- if (currentRegions[0].End - (currentRegions[0]).Start + 1 == image.Count())
|
|
|
|
- {
|
|
|
|
- LOG.error("当前行有效区域为整行,检查视野和光源");
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- // 步骤2:处理当前行每个区域
|
|
|
|
- for (int i = 0; i < currentRegions.Count; i++)
|
|
|
|
- {
|
|
|
|
- var region = currentRegions[i];
|
|
|
|
- // 查找全部可合并的活跃物体(有重叠+在允许间隔内)
|
|
|
|
- var matcheds = activeObjects.Where(o =>
|
|
|
|
- IsOverlapping(o, region) &&
|
|
|
|
- (currentLine - o.LastSeenLine - 1) <= shuLiConfig.MAX_GAP).ToList();
|
|
|
|
- //当有多个可合并的活跃物体时,将多个物体合并
|
|
|
|
- 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,
|
|
|
|
- };
|
|
|
|
- // 从活跃区域中删除被合并的区域
|
|
|
|
- matcheds.ForEach(o => activeObjects.Remove(o));
|
|
|
|
- // 保存新的区域到活跃区域中
|
|
|
|
- activeObjects.Add(MergeMatched);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // 搜获可用且可合并的活跃区域
|
|
|
|
- var matched = activeObjects.FirstOrDefault(o =>
|
|
|
|
- IsOverlapping(o, region) &&
|
|
|
|
- (currentLine - o.LastSeenLine - 1) <= shuLiConfig.MAX_GAP);
|
|
|
|
- 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,
|
|
|
|
- });
|
|
|
|
- }
|
|
|
|
- else
|
|
|
|
- {
|
|
|
|
- // 创建新物体(首次出现的区域)
|
|
|
|
- activeObjects.Add(new ActiveObjectClass
|
|
|
|
- {
|
|
|
|
- MinStartCol = region.Start,
|
|
|
|
- MaxEndCol = region.End,
|
|
|
|
- StartLine = currentLine,
|
|
|
|
- LastSeenLine = currentLine,
|
|
|
|
- LastSeenLineStartCol = region.Start,
|
|
|
|
- LastSeenLineEndCol = region.End,
|
|
|
|
- StartCheckTime = DateTime.Now,
|
|
|
|
- Area = region.End - region.Start +1 ,
|
|
|
|
- ImageWidth = IdentifyImageWidth,
|
|
|
|
- RowsData = new List<RowStartEndCol> {
|
|
|
|
- new RowStartEndCol {
|
|
|
|
- StartCol = region.Start,
|
|
|
|
- EndCol = region.End,
|
|
|
|
- RowsCol = currentLine,
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- });
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // 更新有效物体的最后一行的起始点
|
|
|
|
- activeObjects.Where(o => o.LastSeenLine == currentLine).ToList().ForEach(o => o.LastSeenLineStartCol = o.RowsData.Where(p => p.RowsCol == currentLine).Min(p => p.StartCol));
|
|
|
|
- activeObjects.Where(o => o.LastSeenLine == currentLine).ToList().ForEach(o => o.LastSeenLineEndCol = o.RowsData.Where(p => p.RowsCol == currentLine).Max(p => p.EndCol));
|
|
|
|
-
|
|
|
|
- // 步骤3:清理超时未更新的物体
|
|
|
|
- var lostObjects = activeObjects
|
|
|
|
- .Where(o => (currentLine - o.LastSeenLine) > shuLiConfig.MAX_GAP|| (o.LastSeenLine - o.StartLine) > shuLiConfig.MAX_Idetify_Height)
|
|
|
|
- .ToList();
|
|
|
|
-
|
|
|
|
- List<ActiveObjectClass> OneActive = new List<ActiveObjectClass>();
|
|
|
|
-
|
|
|
|
- // 有物体转变为活跃物体,返回值转为true
|
|
|
|
- if (lostObjects.Count()>0)
|
|
|
|
- {
|
|
|
|
- result = true;
|
|
|
|
- foreach (var item in lostObjects)
|
|
|
|
- {
|
|
|
|
- //噪点判定
|
|
|
|
- if(item.LastSeenLine - item.StartLine <shuLiConfig.NoiseFilter_Threshold||
|
|
|
|
- item.RowsData.Max(o => o.EndCol - o.StartCol)< shuLiConfig.NoiseFilter_Threshold)
|
|
|
|
- continue;
|
|
|
|
- //转为历史物体,添加缺少的参数
|
|
|
|
- item.Num = ObjectNum += 1;
|
|
|
|
- item.ChannelNO = ActiveChannel(item);
|
|
|
|
- item.EndCheckTime = DateTime.Now;
|
|
|
|
- OneActive.Add(item);
|
|
|
|
- if((item.LastSeenLine - item.StartLine) > shuLiConfig.MAX_Idetify_Height)
|
|
|
|
- {
|
|
|
|
- item.StateCode = 7;
|
|
|
|
- LOG.error("ShuLiClass-ProcessLine:非颗粒,视野异常");
|
|
|
|
- Console.WriteLine("ShuLiClass-ProcessLine:非颗粒,视野异常");
|
|
|
|
- }
|
|
|
|
- else if (shuLiConfig.PandingCode != -1)
|
|
|
|
- {
|
|
|
|
- if (item.Area < shuLiConfig.MinArea
|
|
|
|
- && (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 1))
|
|
|
|
- {
|
|
|
|
- item.StateCode = 5;
|
|
|
|
- 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 = 6;
|
|
|
|
- LOG.log(string.Format("颗粒编号{0}:面积过大", item.Num));
|
|
|
|
- Console.WriteLine("颗粒编号{0}:面积过大", item.Num);
|
|
|
|
- }
|
|
|
|
- else if (item.LastSeenLine - item.StartLine < shuLiConfig.MIN_OBJECT_HEIGHT
|
|
|
|
- && (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 0))
|
|
|
|
- {
|
|
|
|
- item.StateCode = 2;
|
|
|
|
- LOG.log(string.Format("颗粒编号{0}:超短粒", item.Num));
|
|
|
|
- Console.WriteLine("颗粒编号{0}:超短粒", item.Num);
|
|
|
|
- }
|
|
|
|
- else if (item.LastSeenLine - item.StartLine > shuLiConfig.MAX_OBJECT_HEIGHT
|
|
|
|
- && (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 0))
|
|
|
|
- {
|
|
|
|
- item.StateCode = 1;
|
|
|
|
- LOG.log(string.Format("颗粒编号{0}:超长粒", item.Num));
|
|
|
|
- Console.WriteLine("颗粒编号{0}:超长粒", item.Num);
|
|
|
|
- }
|
|
|
|
- else if (item.RowsData.Max(o => o.EndCol - o.StartCol) > shuLiConfig.MAX_OBJECT_WIDTH
|
|
|
|
- && (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 0))
|
|
|
|
- {
|
|
|
|
- item.StateCode = 3;
|
|
|
|
- LOG.log(string.Format("颗粒编号{0}:超宽粒", item.Num));
|
|
|
|
- Console.WriteLine("颗粒编号{0}:超宽粒", item.Num);
|
|
|
|
- }
|
|
|
|
- else if (item.RowsData.Max(o => o.EndCol - o.StartCol) < shuLiConfig.MIN_OBJECT_WIDTH
|
|
|
|
- && (shuLiConfig.PandingCode == 2 || shuLiConfig.PandingCode == 0))
|
|
|
|
- {
|
|
|
|
- item.StateCode = 4;
|
|
|
|
- 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(OneActive.Count>0)
|
|
|
|
- //触发回调事件
|
|
|
|
- OnWorkCompleted(OneActive);
|
|
|
|
- }
|
|
|
|
- else
|
|
|
|
- {
|
|
|
|
- OneActive = null;
|
|
|
|
- }
|
|
|
|
- lock(_lockObj)
|
|
|
|
- {
|
|
|
|
- // 累加到总数并从活跃物体转移到历史物体
|
|
|
|
- lostObjects.Where(o => o.LastSeenLine - o.StartLine >= shuLiConfig.NoiseFilter_Threshold).ToList().ForEach(o => historyActiveObjects.Add(o));
|
|
|
|
- lostObjects.ForEach(o => activeObjects.Remove(o));
|
|
|
|
- }
|
|
|
|
- return result;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
/// 处理单行像素数据
|
|
/// 处理单行像素数据
|
|
/// 返回值为false的时候无活跃物体转变为历史物体
|
|
/// 返回值为false的时候无活跃物体转变为历史物体
|
|
@@ -693,64 +475,6 @@ namespace CCDCount.DLL
|
|
return result;
|
|
return result;
|
|
}
|
|
}
|
|
|
|
|
|
- /// <summary>
|
|
|
|
- /// 检测有效物体区域(横向连续黑色像素段)
|
|
|
|
- /// </summary>
|
|
|
|
- /// <param name="line">当前行像素数组</param>
|
|
|
|
- /// <returns>有效区域列表(起始/结束位置)</returns>
|
|
|
|
- private List<(int Start, int End)> FindValidRegions(byte[] image)
|
|
|
|
- {
|
|
|
|
- List<(int Start, int End)> regions = new List<(int Start, int End)>();
|
|
|
|
- int start = -1; // 当前区域起始标记
|
|
|
|
- // 遍历所有像素列
|
|
|
|
- if (shuLiConfig.IsIdentifyRoiOpen)
|
|
|
|
- {
|
|
|
|
- for (int i = shuLiConfig.IdentifyStartX; i < shuLiConfig.IdentifyStopX; i++)
|
|
|
|
- {
|
|
|
|
- if (image[i] < shuLiConfig.RegionThreshold) // 发现黑色像素
|
|
|
|
- {
|
|
|
|
- if (start == -1) start = i; // 开始新区域
|
|
|
|
- }
|
|
|
|
- else if (start != -1) // 遇到白色像素且存在进行中的区域
|
|
|
|
- {
|
|
|
|
- // 检查区域宽度是否达标
|
|
|
|
- if (i - start >= shuLiConfig.MIN_OBJECT_WIDTH)
|
|
|
|
- {
|
|
|
|
- regions.Add((start, i - 1)); // 记录有效区域
|
|
|
|
- }
|
|
|
|
- start = -1; // 重置区域标记
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- else
|
|
|
|
- {
|
|
|
|
- for (int i = 0; i < image.Length; i++)
|
|
|
|
- {
|
|
|
|
- if (image[i] < shuLiConfig.RegionThreshold) // 发现黑色像素
|
|
|
|
- {
|
|
|
|
- if (start == -1) start = i; // 开始新区域
|
|
|
|
- }
|
|
|
|
- else if (start != -1) // 遇到白色像素且存在进行中的区域
|
|
|
|
- {
|
|
|
|
- // 检查区域宽度是否达标
|
|
|
|
- if (i - start >= shuLiConfig.MIN_OBJECT_WIDTH)
|
|
|
|
- {
|
|
|
|
- regions.Add((start, i - 1)); // 记录有效区域
|
|
|
|
- }
|
|
|
|
- start = -1; // 重置区域标记
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
-
|
|
|
|
- // 处理行尾未闭合的区域
|
|
|
|
- if (start != -1 && image.Length - start >= shuLiConfig.MIN_OBJECT_WIDTH)
|
|
|
|
- {
|
|
|
|
- regions.Add((start, image.Length - 1));
|
|
|
|
- }
|
|
|
|
- return regions;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
/// 检测有效物体区域(横向连续黑色像素段)
|
|
/// 检测有效物体区域(横向连续黑色像素段)
|
|
/// </summary>
|
|
/// </summary>
|
|
@@ -884,41 +608,6 @@ namespace CCDCount.DLL
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- /// <summary>
|
|
|
|
- /// 线程方法 - 旧线程方法
|
|
|
|
- /// </summary>
|
|
|
|
- //private void IdentifyImageProcess()
|
|
|
|
- //{
|
|
|
|
- // //Stopwatch stopwatch = Stopwatch.StartNew();
|
|
|
|
- // byte[] ImageByte = null;
|
|
|
|
- // while (IsIdentify)
|
|
|
|
- // {
|
|
|
|
- // //判断队列中是否有数据
|
|
|
|
- // if (ImageBytes.Count() > 0)
|
|
|
|
- // {
|
|
|
|
- // //stopwatch.Restart();
|
|
|
|
- // ImageBytes.TryDequeue(out ImageByte);
|
|
|
|
- // //是否成功取得数据
|
|
|
|
- // if (ImageByte != null)
|
|
|
|
- // {
|
|
|
|
- // //识别
|
|
|
|
- // ProcessImageSequence(ImageByte, IdentifyImageWidth);
|
|
|
|
- // }
|
|
|
|
- // else
|
|
|
|
- // {
|
|
|
|
- // Console.WriteLine("识别数据为空");
|
|
|
|
- // continue;
|
|
|
|
- // }
|
|
|
|
- // //输出耗时
|
|
|
|
- // //stopwatch.Stop();
|
|
|
|
- // ///Console.WriteLine("识别线程单次运行耗时:" + stopwatch.Elapsed.ToString());
|
|
|
|
- // }
|
|
|
|
- // else
|
|
|
|
- // {
|
|
|
|
- // Thread.Sleep(5);
|
|
|
|
- // }
|
|
|
|
- // }
|
|
|
|
- //}
|
|
|
|
#endregion
|
|
#endregion
|
|
}
|
|
}
|
|
}
|
|
}
|