using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ComponentModel; using Misc; using FLY.Thick.Base.Common; using FLY.Thick.Base.IService; using FObjBase; using AutoMapper; using System.IO; using Newtonsoft.Json; namespace FLY.Thick.Base.Server { /// <summary> /// 边界查找 /// </summary> public class BorderSearch : IBorderSearchService, INotifyPropertyChanged { /// <summary> /// 报警测试 /// </summary> //public static FLY.OBJComponents.Common.ERRNO BORDER_ERRNO_NOFILM = new FLY.OBJComponents.Common.ERRNO() { Code = 50, Descrption = "边界异常,没有被测物" }; #region 延时执行,Markno public const int MARKNO_SAVE = 1; #endregion #region 属性,成员变量的代理 /// <summary> /// 启动与否 /// </summary> public bool Enable { get; set; } /// <summary> /// 单一材料,找空气与膜均值的 中间位置 /// </summary> public bool IsOneMaterial { get; set; } /// <summary> /// 边界拐点检测,找到的边界更加精确 /// </summary> public bool IsBreakDetect { get; set; } = true; /// <summary> /// 有效范围 /// </summary> public RangeStruct Valid { get; set; } = RangeStruct.InvalidValue; /// <summary> /// 边界 /// </summary> public Range Border_Forw { get { return borders[0].value; } } /// <summary> /// 边界 /// </summary> public Range Border_Backw { get { return borders[1].value; } } /// <summary> /// 膜范围 /// </summary> public RangeStruct Border { get; set; } = RangeStruct.InvalidValue; /// <summary> /// 当前膜宽,测量出来的,单位是 脉冲 /// </summary> public int Width { get; protected set; } /// <summary> /// 膜中间位置 单位 脉冲 /// </summary> public int Mid { get; protected set; } /// <summary> /// 手动设置温修AD值 /// </summary> public bool TempADBySet { get; set; } /// <summary> /// 温修AD值 /// </summary> public int CurrTempAD { get; set; } = 50000; /// <summary> /// 温修AD值 /// </summary> public int TempAD { get; set; } = 50000; /// <summary> /// AD超过了范围, 就认为开始找到边界 /// </summary> public int TempRange { get; set; } = 1000; /// <summary> /// 温修范围是温修的百分比 /// </summary> public bool IsTempRangeByPercent { get; set; } = true; /// <summary> /// 温修范围百分比 /// </summary> public double TempRangePercent { get; set; } = 0.02; /// <summary> /// 有滤波器,只有非空的连续N个pos以上,才开始算边界开始 /// </summary> public int N { get; set; } /// <summary> /// 探头直径,单位脉冲, 膜宽 = 边界范围 - 探头直径 /// </summary> public int SensorWidth { get; set; } /// <summary> /// 找到边界后,再往内缩N2个脉冲,防止边界找得不准的问题。 /// </summary> public int N2 { get; set; } /// <summary> /// 记录两个边界以后扫描,以它们再外扩N3个脉冲,作为扫描范围 /// </summary> public int N3 { get; set; } /// <summary> /// 数据更新时间 /// </summary> public DateTime UpdateTime { get; protected set; } /// <summary> /// 产品宽度,设置值 单位:脉冲 /// 当膜超过范围(没有经过阀值),就使用它,猜边界在哪 /// </summary> public int ProductWidth { get; protected set; } #endregion /// <summary> /// 边界 /// </summary> protected class BorderWithOk { /// <summary> /// 边界值 /// </summary> public Range value = new Range(); /// <summary> /// 开始边界100%可靠 /// </summary> public bool isBeginOk; /// <summary> /// 结束边界100%可靠 /// </summary> public bool isEndOk; /// <summary> /// 真实可靠的边界值 /// </summary> public Range real = new Range(); public void UpdateReal(int sensorWidth, int expect, int mid, bool isBeginOk, bool isEndOk) { if (value.IsValid) { real.Begin = value.Begin + sensorWidth / 2; real.End = value.End - sensorWidth / 2; //修正!!! if (real.Width < expect) { if (isBeginOk && isEndOk) { } else if (isBeginOk) { real.End = real.Begin + expect; } else if (isEndOk) { real.Begin = real.End - expect; } else //开始边界与结束边界都是假的 { //居中处理 real.Begin = mid - expect / 2; real.End = real.Begin + expect; } } } } public void UpdateReal(int sensorWidth, int expect, int mid) { UpdateReal(sensorWidth, expect, mid, isBeginOk, isEndOk); } public void UpdateReal(int sensorWidth) { if (value.IsValid) { real.Begin = value.Begin + sensorWidth / 2; real.End = value.End - sensorWidth / 2; } } } /// <summary> /// 正反向边界数据 /// </summary> protected BorderWithOk[] borders = new BorderWithOk[2] { new BorderWithOk(), new BorderWithOk() }; OBJComponents.Server.WarningSystem2 warningSystem; /// <summary> /// 用于调试, 进入到边界查找的数据 /// </summary> protected BorderSearchGetViewReponse getViewReponse; private string param_path = "bordersearch.json"; public BorderSearch() { Default(); this.PropertyChanged += new PropertyChangedEventHandler(BorderSearch_PropertyChanged); } public BorderSearch(string param_path) { if (!string.IsNullOrEmpty(param_path)) this.param_path = param_path; Default(); this.PropertyChanged += new PropertyChangedEventHandler(BorderSearch_PropertyChanged); this.Border_Forw.PropertyChanged += (s, e) => { NotifyPropertyChanged(nameof(Border_Forw)); }; this.Border_Backw.PropertyChanged += (s, e) => { NotifyPropertyChanged(nameof(Border_Backw)); }; if (!Load()) { Save(); } } public void Init(OBJComponents.Server.WarningSystem2 warningSystem) { this.warningSystem = warningSystem; } void Default() { Enable = false; Valid = new RangeStruct(1200,7400); TempAD = -1; N = 20; N2 = 400; N3 = 1000; SensorWidth = 250; TempRange = 500; TempRangePercent = 500.0 / 50000; IsTempRangeByPercent = true; Reset(); } void BorderSearch_PropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(CurrTempAD)) { if (!TempADBySet) TempAD = CurrTempAD; } else if (e.PropertyName == nameof(TempAD)) { if (IsTempRangeByPercent) { TempRange = (int)(TempAD * TempRangePercent); } } else if ((e.PropertyName == nameof(TempRangePercent)) || (e.PropertyName == nameof(IsTempRangeByPercent))) { if (IsTempRangeByPercent) { TempRange = (int)(TempAD * TempRangePercent); } } } public void Reset() { Border_Backw.Reset(); Border_Forw.Reset(); Width = 0; Border = RangeStruct.InvalidValue; Mid = Valid.Mid; } public void SetProductWidth(int productWidth) { ProductWidth = productWidth; } /// <summary> /// 获取扫描范围 /// </summary> /// <returns></returns> public Range GetScanRange() { GetScanRange(out int b, out int e); return new Range() { Begin = b, End = e }; } /// <summary> /// 获取扫描范围 /// </summary> /// <param name="posBegin"></param> /// <param name="posEnd"></param> public void GetScanRange(out int posBegin, out int posEnd) { int min, max; if (borders.All(b => b.value.IsValid)) { min = borders.Min(b => b.value.Begin) - N3; max = borders.Max(b => b.value.End) + N3; } else if (borders[0].value.IsValid) { min = borders[0].value.Begin - N3; max = borders[0].value.End + N3; } else if (borders[1].value.IsValid) { min = borders[1].value.Begin - N3; max = borders[1].value.End + N3; } else { //都不能找到边界 min = Valid.Begin; max = Valid.End; } //范围限制 posBegin = Math.Max(min, Valid.Begin); posEnd = Math.Min(max, Valid.End); } /// <summary> /// 初略操作,超过范围 就是边界 /// </summary> /// <param name="dat">全部数据</param> /// <param name="validBegin_grid">有效范围开始</param> /// <param name="validEnd_grid">有效范围结束</param> /// <param name="borderBegin_grid">输出的开始边界</param> /// <param name="borderEnd_grid">输出的结束边界</param> /// <param name="isBorderBeginOk">开始边界经过阀值线</param> /// <param name="isBorderEndOk">结束边界经过阀值线</param> /// <returns></returns> protected bool FindFilm_Threshold( int[] dat, int validBegin_grid, int validEnd_grid, out int borderBegin_grid, out int borderEnd_grid, out bool isBorderBeginOk, out bool isBorderEndOk) { borderBegin_grid = -1; borderEnd_grid = -1; isBorderBeginOk = false; isBorderEndOk = false; int mid_grid = (validBegin_grid + validEnd_grid) / 2; //从中间开始向左边找 //必须先找到膜 for (int i = mid_grid; i >= validBegin_grid; i--) { if (i >= 0 && i < dat.Length) { if (Misc.MyBase.ISVALIDATA(dat[i])) { if (Math.Abs(TempAD - dat[i]) > TempRange) { //膜 borderBegin_grid = i; } else { //空气 if (borderBegin_grid != -1) { isBorderBeginOk = true; //之前有膜 break; } } } } else if (i < 0) { break; } } if (borderBegin_grid == -1)//没有膜 { //从左边开始找 //找左边界 ,找阀值点 for (int i = mid_grid; i <= validEnd_grid; i++) { if (i >= 0 && i < dat.Length) { if (Misc.MyBase.ISVALIDATA(dat[i])) { if (Math.Abs(TempAD - dat[i]) > TempRange) { //膜 borderBegin_grid = i; isBorderBeginOk = true; break; } } } } if (borderBegin_grid == -1)//没有膜 return false; } //从左边界开始找 for (int i = borderBegin_grid; i <= validEnd_grid; i++) { if (i >= 0 && i < dat.Length) { if (Misc.MyBase.ISVALIDATA(dat[i])) { if (Math.Abs(TempAD - dat[i]) > TempRange) { //膜 borderEnd_grid = i; } else { //空气 if (borderEnd_grid != -1) { isBorderEndOk = true; //之前有膜 break; } } } } } if (borderEnd_grid == -1)//没有膜 return false; return true; } /// <summary> /// 精细操作,再向外找 折点 /// </summary> /// <param name="dat"></param> /// <param name="sensor_grid">探头直径</param> /// <param name="filter_grid">滤波</param> /// <param name="borderbegin_grid"></param> /// <param name="borderend_grid"></param> /// <returns></returns> bool FindFilm_Break(int[] dat, int sensor_grid, int filter_grid, ref int borderbegin_grid, ref int borderend_grid) { //当前肯定在斜波上 //边界肯定在 粗边界以外 探头半径 内 //int N4 = SensorWidth / posOfGrid; if (sensor_grid <= 2) { //不用找,传感器直径非常小,已经很准确了 return true; } //int num = N / posOfGrid / 2;//滤波半径 int num = filter_grid / 2; if (num < 1) num = 1; int[] filterdats = new int[dat.Length];//数据滤波 for (int i = 0; i < dat.Length; i++) { int b = i - num; int e = i + num; if (b < 0) { b = 0; e = b + num * 2; } else if (e >= (dat.Length - 1)) { e = dat.Length - 1; b = e - num * 2; } filterdats[i] = Misc.MyMath.Avg(dat, b, e); } int bg = borderbegin_grid; int eg = borderend_grid; int[] breakdats = new int[dat.Length]; num = 1; for (int i = 0; i < dat.Length; i++) { if ((i - num) < 0) breakdats[i] = Misc.MyBase.NULL_VALUE; else if ((i + num) >= dat.Length) breakdats[i] = Misc.MyBase.NULL_VALUE; else if (!Misc.MyBase.ISVALIDATA(filterdats[i - num])) breakdats[i] = Misc.MyBase.NULL_VALUE; else if (!Misc.MyBase.ISVALIDATA(filterdats[i + num])) breakdats[i] = Misc.MyBase.NULL_VALUE; else if (!Misc.MyBase.ISVALIDATA(filterdats[i])) breakdats[i] = Misc.MyBase.NULL_VALUE; else { breakdats[i] = Math.Abs((filterdats[i] - filterdats[i - num]) * (filterdats[i + num] - filterdats[i])); } } bool isdown = false; //从第1次找到的边界开始,往外找,找折点的最小值 for (int i = borderbegin_grid; i >= (borderbegin_grid - sensor_grid); i--) { if ((i - num) < 0) break; if ((i + num) >= dat.Length) break; if (!Misc.MyBase.ISVALIDATA(breakdats[i - num])) break; if (!Misc.MyBase.ISVALIDATA(breakdats[i + num])) break; if (!Misc.MyBase.ISVALIDATA(breakdats[i])) break; if (breakdats[i] < breakdats[i + num]) { //在减少 bg = i; isdown = true; } if (isdown) { if (breakdats[i] < breakdats[i - num]) { //之前那个就是最小值 break; } } } isdown = false; for (int i = borderend_grid; i <= (borderend_grid + sensor_grid); i++) { if ((i - num) < 0) break; if ((i + num) >= dat.Length) break; if (!Misc.MyBase.ISVALIDATA(filterdats[i - num])) break; if (!Misc.MyBase.ISVALIDATA(filterdats[i + num])) break; if (!Misc.MyBase.ISVALIDATA(filterdats[i])) break; if (breakdats[i] < breakdats[i - num]) { //在减少 eg = i; isdown = true; } if (isdown) { if (breakdats[i] < breakdats[i + num]) { //之前那个就是最小值 break; } } } borderbegin_grid = bg; borderend_grid = eg; return true; } /// <summary> /// 放入grid数据,分析边界 /// </summary> /// <param name="direction"></param> /// <param name="posOfGrid"></param> /// <param name="gridBegin"></param> /// <param name="dat"></param> /// <returns></returns> bool FindFilm_Break(DIRECTION direction, int posOfGrid, int gridBegin, int[] dat) { //把数据记录下来 getViewReponse = new BorderSearchGetViewReponse() { direction = direction, posOfGrid = posOfGrid, gridBegin = gridBegin, dat = dat }; UpdateTime = DateTime.Now; //转为 dat 的序号 int validbegin_grid = Valid.Begin / posOfGrid - gridBegin; int validend_grid = Valid.End / posOfGrid - gridBegin; if (!FindFilm_Threshold(dat, validbegin_grid, validend_grid, out int borderbegin_grid, out int borderend_grid, out bool isBorderBeginOk, out bool isBorderEndOk)) { //warningSystem.Add(BORDER_ERRNO_NOFILM.Code, BORDER_ERRNO_NOFILM.Descrption, OBJComponents.Common.ERR_STATE.ON); return false; } else { //warningSystem.Add(BORDER_ERRNO_NOFILM.Code, BORDER_ERRNO_NOFILM.Descrption, OBJComponents.Common.ERR_STATE.OFF); } //N2 只是用来判断一下膜够不够宽而已,没用 int N2_grid = (N2*3+SensorWidth) / posOfGrid; if ((borderend_grid - borderbegin_grid) < N2_grid)//膜太短 return false; if (IsBreakDetect) { int sensor_grid = SensorWidth / posOfGrid; int filter_grid = N / posOfGrid; //精细查找 FindFilm_Break(dat, sensor_grid, filter_grid, ref borderbegin_grid, ref borderend_grid); } int idx = (direction == DIRECTION.FORWARD) ? 0 : 1; var border = borders[idx]; //转为 脉冲 border.value.Begin = (borderbegin_grid + gridBegin) * posOfGrid; border.value.End = (borderend_grid +gridBegin) * posOfGrid; border.isBeginOk = isBorderBeginOk; border.isEndOk = isBorderEndOk; if (borders.All(b => b.value.IsValid)) { foreach(var bo in borders) bo.UpdateReal(SensorWidth, ProductWidth, Valid.Mid, borders.All(b=>b.isBeginOk), borders.All(b => b.isEndOk)); Border = new RangeStruct( (int)borders.Average(b => b.real.Begin), (int)borders.Average(b => b.real.End)); } else if (border.value.IsValid) { foreach (var bo in borders) bo.UpdateReal(SensorWidth, ProductWidth, Valid.Mid); Border = border.real.ToStruct(); } getViewReponse.border = border.real; Width = Border.Width; Mid = Border.Mid; return true; } /// <summary> /// 找空气与膜均值的 中间位置 /// </summary> /// <returns></returns> void FindFilm_Mean(int[] dat, int sensor_grid, int n2_grid, ref int borderbegin_grid, ref int borderend_grid) { Range r1_grid = new Range(); //算膜中间的平均值 r1_grid.Begin = borderbegin_grid + sensor_grid + n2_grid; r1_grid.End = borderend_grid - (sensor_grid + n2_grid); int avg = MyMath.Avg(dat, r1_grid.Begin, r1_grid.End); //算空气的平均值 Range r2_grid = new Range(); r2_grid.Begin = borderbegin_grid - sensor_grid / 3; r2_grid.End = borderend_grid + sensor_grid / 3; if (r2_grid.Begin < 0) r2_grid.Begin = 0; if (r2_grid.End > dat.Length - 1) r2_grid.End = dat.Length - 1; int avg1 = MyMath.Avg(dat, 0, r2_grid.Begin); int avg2 = MyMath.Avg(dat, r2_grid.End, dat.Length - 1); int threshold; if ((Misc.MyBase.ISVALIDATA(avg1)) && (Misc.MyBase.ISVALIDATA(avg2))) { threshold = (avg + (avg1 + avg2) / 2) / 2; } else if (Misc.MyBase.ISVALIDATA(avg1)) { threshold = (avg + avg1) / 2; } else if (Misc.MyBase.ISVALIDATA(avg2)) { threshold = (avg + avg2) / 2; } else { threshold = (TempAD + avg) / 2; } //左边界 for (int i = r1_grid.Begin; i >= r2_grid.Begin; i--) { if (Misc.MyBase.ISVALIDATA(dat[i])) { if (dat[i] < threshold) { borderbegin_grid = i; } } } //右边界 for (int i = r1_grid.End; i < r2_grid.End; i++) { if (Misc.MyBase.ISVALIDATA(dat[i])) { if (dat[i] < threshold) { borderend_grid = i; } } } } /// <summary> /// 放入grid数据,分析边界 /// </summary> /// <param name="direction"></param> /// <param name="posOfGrid"></param> /// <param name="gridBegin"></param> /// <param name="dat"></param> /// <returns></returns> public virtual bool FindFilm(DIRECTION direction, int posOfGrid, int gridBegin, int[] dat) { if (IsOneMaterial) { return FindFilm_Mean(direction, posOfGrid, gridBegin, dat); } else { return FindFilm_Break(direction, posOfGrid, gridBegin, dat); } } /// <summary> /// 放入grid数据,分析边界 /// </summary> /// <param name="direction"></param> /// <param name="posOfGrid"></param> /// <param name="gridBegin"></param> /// <param name="dat"></param> /// <returns></returns> bool FindFilm_Mean(DIRECTION direction, int posOfGrid, int gridBegin, int[] dat) { //把数据记录下来 getViewReponse = new BorderSearchGetViewReponse() { direction = direction, posOfGrid = posOfGrid, gridBegin = gridBegin, dat = dat }; UpdateTime = DateTime.Now; //这些是AD值 //转为 dat 的序号 int validbegin_grid = Valid.Begin / posOfGrid - gridBegin; int validend_grid = Valid.End / posOfGrid - gridBegin; if (!FindFilm_Threshold(dat, validbegin_grid, validend_grid, out int borderbegin_grid, out int borderend_grid, out bool isBorderBeginOk, out bool isBorderEndOk)) { return false; } //N2 只是用来判断一下膜够不够宽而已,没用 int N2_grid = (N2 * 3 + SensorWidth) / posOfGrid; if ((borderend_grid - borderbegin_grid) < N2_grid)//膜太短 return false; FindFilm_Mean(dat, SensorWidth / posOfGrid, N2 / posOfGrid, ref borderbegin_grid, ref borderend_grid); int idx = (direction == DIRECTION.FORWARD) ? 0 : 1; var border = borders[idx]; //borderbegin_grid, borderend_grid 是实际边界位置, //border.value 是空气与膜的拐点 //必须外扩 //转为 脉冲 border.value.Begin = (borderbegin_grid + gridBegin) * posOfGrid - SensorWidth / 2; border.value.End = (borderend_grid + gridBegin) * posOfGrid + SensorWidth / 2; border.isBeginOk = true; border.isEndOk = true; border.UpdateReal(SensorWidth); if (borders.All(b => b.value.IsValid)) { Border = new RangeStruct((int)borders.Average(b => b.real.Begin), (int)borders.Average(b => b.real.End)); } else if (border.value.IsValid) { Border = border.real.ToStruct(); } getViewReponse.border = border.real; Width = Border.Width; Mid = Border.Mid; return true; } /// <summary> /// 获取膜边界,当根本找不到膜时,返回找边界范围!!! /// 膜的边界,只是斜波的中间而已。 /// </summary> /// <param name="direction"></param> /// <param name="posBegin"></param> /// <param name="posEnd"></param> public void GetBorder(DIRECTION direction, out int posBegin, out int posEnd) { int idx = (direction == DIRECTION.FORWARD) ? 0 : 1; var border = borders[idx]; if (border.value.IsValid) { posBegin = border.real.Begin; posEnd = border.real.End; } else { idx += 1; if (idx > 1) idx = 0; border = borders[idx]; if (border.value.IsValid) { posBegin = border.real.Begin; posEnd = border.real.End; } else { posBegin = Misc.MyBase.NULL_VALUE; posEnd = Misc.MyBase.NULL_VALUE; } } } /// <summary> /// 获取膜边界,当根本找不到膜时,返回IsVaild=false 的Range!!! /// 膜的边界,只是斜波的中间而已。 /// </summary> /// <param name="direction"></param> /// <returns></returns> public Range GetBorder(DIRECTION direction) { int b, e; GetBorder(direction, out b, out e); return new Range() { Begin = b, End = e }; } /// <summary> /// 获取 膜边界 向内缩 N2 + SensorWidth / 2 /// </summary> /// <param name="direction"></param> /// <param name="posBegin"></param> /// <param name="posEnd"></param> public void GetBoltRange(DIRECTION direction, out int posBegin, out int posEnd) { GetBorder(direction, out posBegin, out posEnd); if (Misc.MyBase.ISVALIDATA(posBegin)) { posBegin += (SensorWidth / 2 + N2); posEnd -= (SensorWidth / 2 + N2); } } /// <summary> /// 膜边界 向内缩 N2 + SensorWidth / 2 /// </summary> /// <param name="direction"></param> /// <returns></returns> public Range GetBoltRange(DIRECTION direction) { int b, e; GetBoltRange(direction, out b, out e); return new Range() { Begin = b, End = e }; } /// <summary> /// 获取混合后的横向数据;没有把横向留白剔除;只把纵向留白剔除。 /// 数据单位 mm /// </summary> /// <param name="asyncDelegate"></param> /// <param name="asyncContext"></param> [AsyncCb(typeof(BorderSearchGetViewReponse))] public void GetView(AsyncCBHandler asyncDelegate, object asyncContext) { asyncDelegate?.Invoke(asyncContext, getViewReponse); } public bool Load() { return BorderSearchJsonDb.Load(this, param_path); } public bool Save() { return BorderSearchJsonDb.Save(this, param_path); } public void Apply() { Save(); } #region INotifyPropertyChanged 成员 public event PropertyChangedEventHandler PropertyChanged; protected void NotifyPropertyChanged(string propertyname) { if (PropertyChanged != null) { PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyname)); } } #endregion } public class BorderSearchJsonDb { static Mapper Mapper { get; } = new AutoMapper.Mapper(new MapperConfiguration(c => { c.CreateMap<BorderSearch, BorderSearchJsonDb>().ReverseMap(); })); public static bool Load(BorderSearch src, string filePath) { try { if (File.Exists(filePath)) { string json = File.ReadAllText(filePath); var p = JsonConvert.DeserializeObject<BorderSearchJsonDb>(json); Mapper.Map(p, src); return true; } } catch { //异常,没有json 解码失败 } return false; } public static bool Save(BorderSearch src, string filePath) { var p = Mapper.Map<BorderSearchJsonDb>(src); try { File.WriteAllText(filePath, JsonConvert.SerializeObject(p, Formatting.Indented)); return true; } catch { //异常,没有json 编码失败 } return false; } public bool Enable; public bool IsOneMaterial; public bool IsBreakDetect = true; public RangeStruct Valid = new RangeStruct(200,7000); public bool TempADBySet; public int TempAD = 50000; public int TempRange = 500; public bool IsTempRangeByPercent = true; public double TempRangePercent = 0.02; public int N=20; public int SensorWidth=250; public int N2=400; public int N3=1000; } }