Explorar o código

1、增加区分半自动、自动识别 2、修改算法的output图片 3、bug修复

ZIM hai 1 ano
pai
achega
0187efc23a

BIN=BIN
Assets/InfraredProject/WebCamera/Image/测试图片.png


+ 0 - 123
Assets/InfraredProject/WebCamera/Image/测试图片.png.meta

@@ -1,123 +0,0 @@
-fileFormatVersion: 2
-guid: d3a5d442a677a7f4aa8dead09e3904ac
-TextureImporter:
-  internalIDToNameTable: []
-  externalObjects: {}
-  serializedVersion: 12
-  mipmaps:
-    mipMapMode: 0
-    enableMipMap: 0
-    sRGBTexture: 1
-    linearTexture: 0
-    fadeOut: 0
-    borderMipMap: 0
-    mipMapsPreserveCoverage: 0
-    alphaTestReferenceValue: 0.5
-    mipMapFadeDistanceStart: 1
-    mipMapFadeDistanceEnd: 3
-  bumpmap:
-    convertToNormalMap: 0
-    externalNormalMap: 0
-    heightScale: 0.25
-    normalMapFilter: 0
-  isReadable: 1
-  streamingMipmaps: 0
-  streamingMipmapsPriority: 0
-  vTOnly: 0
-  ignoreMasterTextureLimit: 0
-  grayScaleToAlpha: 0
-  generateCubemap: 6
-  cubemapConvolution: 0
-  seamlessCubemap: 0
-  textureFormat: 1
-  maxTextureSize: 2048
-  textureSettings:
-    serializedVersion: 2
-    filterMode: 1
-    aniso: 1
-    mipBias: 0
-    wrapU: 1
-    wrapV: 1
-    wrapW: 0
-  nPOTScale: 0
-  lightmap: 0
-  compressionQuality: 50
-  spriteMode: 1
-  spriteExtrude: 1
-  spriteMeshType: 1
-  alignment: 0
-  spritePivot: {x: 0.5, y: 0.5}
-  spritePixelsToUnits: 100
-  spriteBorder: {x: 0, y: 0, z: 0, w: 0}
-  spriteGenerateFallbackPhysicsShape: 1
-  alphaUsage: 1
-  alphaIsTransparency: 1
-  spriteTessellationDetail: -1
-  textureType: 8
-  textureShape: 1
-  singleChannelComponent: 0
-  flipbookRows: 1
-  flipbookColumns: 1
-  maxTextureSizeSet: 0
-  compressionQualitySet: 0
-  textureFormatSet: 0
-  ignorePngGamma: 0
-  applyGammaDecoding: 0
-  cookieLightType: 0
-  platformSettings:
-  - serializedVersion: 3
-    buildTarget: DefaultTexturePlatform
-    maxTextureSize: 2048
-    resizeAlgorithm: 0
-    textureFormat: -1
-    textureCompression: 1
-    compressionQuality: 50
-    crunchedCompression: 0
-    allowsAlphaSplitting: 0
-    overridden: 0
-    androidETC2FallbackOverride: 0
-    forceMaximumCompressionQuality_BC6H_BC7: 0
-  - serializedVersion: 3
-    buildTarget: Standalone
-    maxTextureSize: 2048
-    resizeAlgorithm: 0
-    textureFormat: -1
-    textureCompression: 1
-    compressionQuality: 50
-    crunchedCompression: 0
-    allowsAlphaSplitting: 0
-    overridden: 0
-    androidETC2FallbackOverride: 0
-    forceMaximumCompressionQuality_BC6H_BC7: 0
-  - serializedVersion: 3
-    buildTarget: Android
-    maxTextureSize: 2048
-    resizeAlgorithm: 0
-    textureFormat: -1
-    textureCompression: 1
-    compressionQuality: 50
-    crunchedCompression: 0
-    allowsAlphaSplitting: 0
-    overridden: 0
-    androidETC2FallbackOverride: 0
-    forceMaximumCompressionQuality_BC6H_BC7: 0
-  spriteSheet:
-    serializedVersion: 2
-    sprites: []
-    outline: []
-    physicsShape: []
-    bones: []
-    spriteID: 5e97eb03825dee720800000000000000
-    internalID: 0
-    vertices: []
-    indices: 
-    edges: []
-    weights: []
-    secondaryTextures: []
-    nameFileIdTable: {}
-  spritePackingTag: 
-  pSDRemoveMatte: 0
-  pSDShowRemoveMatteOption: 0
-  userData: 
-  assetBundleName: 
-  assetBundleVariant: 

+ 1 - 3
Assets/InfraredProject/WebCamera/Script/ZIM/InfraredLocate/InfraredLocate.cs

@@ -124,8 +124,6 @@ namespace ZIM
                 new InfraredSpot(screenIdentification.Screen, InfraredMatch.Match1) };
             }
 
-            ScreenPixelCheaker.ImageSize = new Vector2(screenIdentification.Size.x, screenIdentification.Size.y);
-
             //var watch = new System.Diagnostics.Stopwatch();
             //watch.Start();
             //var times = new List<double>() { 0.0 };
@@ -145,7 +143,7 @@ namespace ZIM
                     int ip = i * samplingScale;
                     int jp = j * samplingScale;
 
-                    if (!screenIdentification.Screen.Active && ScreenPixelCheaker.OutCollider2D(new Vector2(ip, jp)))
+                    if (!screenIdentification.Screen.Active && ScreenPixelCheaker.OutArea2D(new Vector2(ip, jp), screenIdentification.Size))
                         continue;
 
                     var index = mCameraInfo.CoordToIndex(ip, jp);

+ 8 - 2
Assets/InfraredProject/WebCamera/Script/ZIM/InfraredLocate/LineIdentified.cs

@@ -17,21 +17,27 @@ namespace o0.Project
         public float Gradient;
         // 线梯度方向, 0 - 180度
         public float GradientDegree;
+        // 用于绘制的线, 为null就是不绘制
+        public Line DrawLine;
 
-        public LineIdentified(int batch, Line line, float gradient, float gradientDegree)
+        public LineIdentified(int batch, Line line, float gradient, float gradientDegree, bool drawAll = false)
         {
             Batch = batch;
             Line = line;
             Gradient = gradient;
             GradientDegree = gradientDegree;
+            if (drawAll)
+                DrawLine = line;
         }
 
-        public LineIdentified(int batch, (Line line, float gradient, float gradientDegree) lineParams)
+        public LineIdentified(int batch, (Line line, float gradient, float gradientDegree) lineParams, bool drawAll = false)
         {
             Batch = batch;
             Line = lineParams.line;
             Gradient = lineParams.gradient;
             GradientDegree = lineParams.gradientDegree;
+            if (drawAll)
+                DrawLine = lineParams.line;
         }
 
         public void Offset(Vector offset)

+ 261 - 184
Assets/InfraredProject/WebCamera/Script/ZIM/InfraredLocate/ScreenIdentification.cs

@@ -8,6 +8,7 @@ using System.IO;
 using System.Linq;
 using System.Threading.Tasks;
 using UnityEngine;
+using UnityEngine.UIElements;
 using ZIM;
 using ZIM.Unity;
 
@@ -17,23 +18,29 @@ namespace o0.Project
     {
         private const string TAG = "ScreenIdentification#";
 
-        //static Rect[][] LocateAreaData = new Rect[][] {
-        //     new Rect[] { new Rect(0f, 0f, 0.3f, 0.3f),     new Rect(0f, 0f, 0.4f, 0.4f),     new Rect(0f, 0f, 0.5f, 0.5f),     new Rect(0f, 0f, 0.6f, 0.6f) },
-        //     new Rect[] { new Rect(0.7f, 0f, 0.3f, 0.3f),   new Rect(0.6f, 0f, 0.4f, 0.4f),   new Rect(0.5f, 0f, 0.5f, 0.5f),   new Rect(0.4f, 0f, 0.6f, 0.6f) },
-        //     new Rect[] { new Rect(0f, 0.7f, 0.3f, 0.3f),   new Rect(0f, 0.6f, 0.4f, 0.4f),   new Rect(0f, 0.5f, 0.5f, 0.5f),   new Rect(0f, 0.4f, 0.6f, 0.6f) },
-        //     new Rect[] { new Rect(0.7f, 0.7f, 0.3f, 0.3f), new Rect(0.6f, 0.6f, 0.4f, 0.4f), new Rect(0.5f, 0.5f, 0.5f, 0.5f), new Rect(0.4f, 0.4f, 0.6f, 0.6f) }
-        //};
+        // LocateAreaData表示每次屏幕的色差变化的区域,可能有多次。通过设置LocateSingleStep可调整为仅识别一次色差
         static Rect[][] LocateAreaData = new Rect[][] {
              new Rect[] { new Rect(0f, 0f, 0.3f, 0.3f),     new Rect(0f, 0f, 0.4f, 0.4f),     new Rect(0f, 0f, 0.5f, 0.5f)     },
              new Rect[] { new Rect(0.7f, 0f, 0.3f, 0.3f),   new Rect(0.6f, 0f, 0.4f, 0.4f),   new Rect(0.5f, 0f, 0.5f, 0.5f)   },
              new Rect[] { new Rect(0f, 0.7f, 0.3f, 0.3f),   new Rect(0f, 0.6f, 0.4f, 0.4f),   new Rect(0f, 0.5f, 0.5f, 0.5f)   },
              new Rect[] { new Rect(0.7f, 0.7f, 0.3f, 0.3f), new Rect(0.6f, 0.6f, 0.4f, 0.4f), new Rect(0.5f, 0.5f, 0.5f, 0.5f) }
         };
-        //static bool LocateDebug = false;
-        static bool LocateDebug = true;
+        //static Rect[][] LocateAreaData = new Rect[][] {
+        //     new Rect[] { new Rect(0f, 0f, 0.3f, 0.3f),     new Rect(0f, 0f, 0.4f, 0.4f),     new Rect(0f, 0f, 0.5f, 0.5f),     new Rect(0f, 0f, 0.6f, 0.6f) },
+        //     new Rect[] { new Rect(0.7f, 0f, 0.3f, 0.3f),   new Rect(0.6f, 0f, 0.4f, 0.4f),   new Rect(0.5f, 0f, 0.5f, 0.5f),   new Rect(0.4f, 0f, 0.6f, 0.6f) },
+        //     new Rect[] { new Rect(0f, 0.7f, 0.3f, 0.3f),   new Rect(0f, 0.6f, 0.4f, 0.4f),   new Rect(0f, 0.5f, 0.5f, 0.5f),   new Rect(0f, 0.4f, 0.6f, 0.6f) },
+        //     new Rect[] { new Rect(0.7f, 0.7f, 0.3f, 0.3f), new Rect(0.6f, 0.6f, 0.4f, 0.4f), new Rect(0.5f, 0.5f, 0.5f, 0.5f), new Rect(0.4f, 0.4f, 0.6f, 0.6f) }
+        //};
+
+        //static bool LocateSingleStep = false;
+        static bool LocateSingleStep = true;
 
         public Geometry2D.Vector<int> Size => ScreenLocate.Main.CameraSize;
 
+        public QuadrilateralInCamera QuadManual;
+        public QuadrilateralInCamera QuadAuto;      // 全自动,可以给用户选择(赋值给Screen.QuadInCamera即生效)
+        public QuadrilateralInCamera QuadSemiAuto;  // 半自动,可以给用户选择(赋值给Screen.QuadInCamera即生效)
+
         public ScreenMap Screen;       // 识别到的屏幕,用于执行透视变换
 
         int capture = 0;
@@ -45,14 +52,14 @@ namespace o0.Project
         Geometry.Vector<float>[] ScreenBlackTexture;
         Geometry.Vector<float>[] ScreenWhiteTexture;
         int locateIndex = -1;
-        List<Rect> locateArea = new List<Rect> {
+        readonly List<Rect> locateArea = new List<Rect> {
             new Rect(0f, 0f, 0.5f, 0.5f), new Rect(0.5f, 0f, 0.5f, 0.5f), new Rect(0f, 0.5f, 0.5f, 0.5f), new Rect(0.5f, 0.5f, 0.5f, 0.5f)
         };      // 屏幕显示白色的区域大小
 
         float areaPercent => locateArea[locateIndex].size.x;         // 当前白色区域的占比
         int areaSelected = -1;                                       // 选择哪个区域,顺序与Quadrilateral对应
-        List<float> sumTemp = new List<float>();
-        List<OrdinalQuadrilateral> quadTemp = new List<OrdinalQuadrilateral>();
+        readonly List<float> sumTemp = new List<float>();
+        readonly List<OrdinalQuadrilateral> quadTemp = new List<OrdinalQuadrilateral>();
 
         //public ScreenIdentification(WebCamTexture texture)
         //{
@@ -61,17 +68,14 @@ namespace o0.Project
         //}
         public static UnityEngine.Color FloatValueToColor(float i)
         {
-            switch (i)
+            return i switch
             {
-                case 1:
-                    return UnityEngine.Color.green;
-                case 2:
-                    return UnityEngine.Color.red;
-                case 3:
-                    return UnityEngine.Color.yellow;
-                default:
-                    return UnityEngine.Color.black;
-            }
+                1 => UnityEngine.Color.green,
+                2 => UnityEngine.Color.red,
+                3 => UnityEngine.Color.yellow,
+                4 => UnityEngine.Color.white,
+                _ => UnityEngine.Color.black,
+            };
         }
 
         public ScreenIdentification()
@@ -87,6 +91,22 @@ namespace o0.Project
         public event Action OnLocateScreenEnd;
         public bool bStartLocateScreen { get; set; } = false;//是否进行捕获
 
+        public bool SelectScreenAfterLocate(ScreenLocate.ScreenIdentificationTag tag)
+        {
+            QuadrilateralInCamera target = tag switch
+            {
+                ScreenLocate.ScreenIdentificationTag.Manual => QuadManual,
+                ScreenLocate.ScreenIdentificationTag.SemiAuto => QuadSemiAuto,
+                ScreenLocate.ScreenIdentificationTag.Auto => QuadAuto,
+                _ => null
+            };
+
+            if (target == null)
+                return false;
+            SetScreenQuad(target);
+            return true;
+        }
+
         // 自动识别开始的入口
         public void LocateScreen(int Capture = 30, int Delay = 30)  //数值单位是frame
         {
@@ -132,7 +152,7 @@ namespace o0.Project
 
         void DebugImage(List<Texture2D> images)
         {
-            QuadrilateralFit(images, 5);
+            QuadrilateralFit(images);
 
             //var watch = new System.Diagnostics.Stopwatch();
             //watch.Start();
@@ -177,8 +197,8 @@ namespace o0.Project
 
         public void NextScreen()
         {
-            // 测试用
-            if (LocateDebug && areaSelected == -1)
+            // 只识别一次色差变化
+            if (LocateSingleStep && areaSelected == -1)
             {
                 LocateAreaData = new Rect[][] { new Rect[] { new Rect(0, 0, 1f, 1f) } };
                 locateIndex = 3;
@@ -202,7 +222,16 @@ namespace o0.Project
             }
         }
 
-        void Reset()
+        // 清除记录的屏幕识别数据(手动、自动等)
+        public void ClearQuadCache()
+        {
+            SetScreenQuad(null);
+            QuadManual = null;
+            QuadSemiAuto = null;
+            QuadAuto = null;
+        }
+
+        public void Reset()
         {
             // bStartLocateScreen = false;
             delay = 0;
@@ -211,7 +240,8 @@ namespace o0.Project
             ScreenBlackTexture = null;
             locateIndex = -1;
             areaSelected = -1;
-            locateArea.RemoveRange(4, LocateAreaData[0].Length);
+            if (locateArea.Count > 4)
+                locateArea.RemoveRange(4, LocateAreaData[0].Length);
             quadTemp.Clear();
             sumTemp.Clear();
         }
@@ -623,21 +653,21 @@ namespace o0.Project
             //ScreenLocate.DebugTexture(4, ScreenLocateTexG);
             //ScreenLocate.DebugTexture(5, ScreenLocateTexB);
 
-
-            //var watch = new System.Diagnostics.Stopwatch();
-            //watch.Start();
-            //var times = new List<double>() { 0.0 };
-
             //var ScreenLocateTexLightedMat = texture.Too0Mat();
         }
 
         /// <param name="lineWidth">识别的最小线段长度</param>
         /// <param name="debugImages">这个参数如果不为null且数量大于0,则执行debug操作</param>
-        void QuadrilateralFit(List<Texture2D> debugImages = null, float lineWidth = 10)
+        void QuadrilateralFit(List<Texture2D> debugImages = null)
         {
+            // 如果有旧的手动数据,刷新一下Size
+            QuadManual?.ReSize(new Vector(Size.x, Size.y), ScreenMap.ViewAspectRatioSetting);
             // 屏幕黑白差值,存放多批次的图像用于识别, 该List数量不能等于 0 
             List<UnityEngine.Color[]> PixelsMultipleBatches = new List<UnityEngine.Color[]>();
 
+            var sw = new System.Diagnostics.Stopwatch();
+            sw.Start();
+
             //读取数据
             if (debugImages != null && debugImages.Count != 0) 
             {
@@ -660,13 +690,19 @@ namespace o0.Project
                 var scale = 1.0f / maxWhite;        // 放大对比度
 
                 var differPixel = new UnityEngine.Color[Size.x * Size.y];
-                Parallel.For(0, Size.x * Size.y, i =>
+                var whitePixel = new UnityEngine.Color[Size.x * Size.y];
+                Parallel.For(0, Size.x, x =>
                 {
-                    var pi = ScreenWhiteTexture[i] - ScreenBlackTexture[i];
-                    differPixel[i] = new UnityEngine.Color(pi.x, pi.y, pi.z) * scale;
+                    for (int y = 0; y < Size.y; y++) 
+                    {
+                        var i = y * Size.x + x;
+                        var d = ScreenWhiteTexture[i] - ScreenBlackTexture[i];
+                        differPixel[i] = new UnityEngine.Color(d.x, d.y, d.z) * scale;
+                        whitePixel[i] = new UnityEngine.Color(ScreenWhiteTexture[i].x, ScreenWhiteTexture[i].y, ScreenWhiteTexture[i].z) * scale;
+                    }
                 });
                 PixelsMultipleBatches.Add(differPixel);
-                PixelsMultipleBatches.Add(ScreenWhiteTexture.Select((i) => new UnityEngine.Color(i.x, i.y, i.z) * scale).ToArray());
+                PixelsMultipleBatches.Add(whitePixel);
             }
 
             int conSize = (int)Math.Ceiling(0.007f * Size.y) * 2 + 1;
@@ -674,7 +710,7 @@ namespace o0.Project
 
             float minLength = conSize * 7.7f;
             minLength = locateIndex == -1 ? minLength : minLength * areaPercent;    // minLength需要按比例缩小
-            string log = $"[ScreenLocate Auto] Size: ({Size.x},{Size.y}), 卷积核Size: {conSize}, 最小线段长度: {minLength}";
+            string log = $"[Log][ScreenLocate Auto] Size: ({Size.x},{Size.y}), 卷积核Size: {conSize}, 最小线段长度: {minLength}";
 
             var allLines = new List<LineIdentified>();
             List<Texture2D> LocateTexTemp = new List<Texture2D>();
@@ -690,90 +726,129 @@ namespace o0.Project
             }
             Texture2D ScreenLocateTexture = LocateTexTemp[0];       // for output
 
-            // 过滤得到四边形的四条边, ScreenLocateMatList[0]默认是屏幕的黑白色差
-            var quadLines = FilterLines(ScreenLocateMatList, allLines, GetAvgPoint(ScreenLocateMatList[0]),
-                out Line[] oldLines, out List<Line> possibleLines,
-                Screen, conSize, conSize, minLength);
-
+            // 估算屏幕中点,如果已有手动定位数据,根据现有数据取平均即可,否则从色差计算,ScreenLocateMatList[0]默认是屏幕的黑白色差
+            Vector AvgPoint = QuadManual != null ? QuadManual.Quad.Centroid : GetAvgPoint(ScreenLocateMatList[0]);
+            // 过滤得到四边形的四条边,
+            var (quadLinesSemiAuto, quadLinesAuto) = FilterLines(ScreenLocateMatList, allLines, AvgPoint,
+                out LineIdentified[] manualLines, out List<LineIdentified> possibleLines,
+                conSize, conSize, minLength);
 
-            // 将 allLines 输出一张图片
-            var allLinesMap = new Matrix(Size, Tiling: true);
-            foreach (var l in allLines)
+            #region 全自动识别的结果
+            List<LineIdentified> LineIdentifiedAuto = new List<LineIdentified>();               // 线段顺序: 下、右、上、左
+            for (int i = 0; i < 4; i++)
             {
-                if (l.Line != null)
-                    o0Extension.DrawLine(allLinesMap, l.Line, (x, y) => 3, new Geometry2D.Float.Vector(0, 2), true);
+                if (quadLinesAuto[i] != null)
+                    LineIdentifiedAuto.Add(quadLinesAuto[i]);
             }
-            var allLinesTex = allLinesMap.ToTexRGBA(FloatValueToColor);
-            ScreenLocate.DebugTexture(1, allLinesTex);
-
-
+            if (LineIdentifiedAuto.Count == 4)            // 判断识别的线段能否拼成屏幕,能拼成则记录
+            {
+                var a = LineIdentifiedAuto[0].Line.Intersect(LineIdentifiedAuto[3].Line, false).Value;
+                var b = LineIdentifiedAuto[0].Line.Intersect(LineIdentifiedAuto[1].Line, false).Value;
+                var c = LineIdentifiedAuto[2].Line.Intersect(LineIdentifiedAuto[3].Line, false).Value;
+                var d = LineIdentifiedAuto[1].Line.Intersect(LineIdentifiedAuto[2].Line, false).Value;
+                QuadAuto = new QuadrilateralInCamera(a, b, c, d, new Vector(Size.x, Size.y));
+                if (!QuadAuto.IsQuadComplete())
+                    QuadAuto = null;
+            }
+            #endregion
 
-            // 将识别到的边画出来,并判断能否拼成屏幕,能拼成则设置ScreenMap
-            // 线段顺序: 下、右、上、左
-            List<Line> LineIdentified = new List<Line>();
+            #region 半自动识别
+            List<LineIdentified> LineIdentifiedSemiAuto = new List<LineIdentified>();               // 线段顺序: 下、右、上、左
+            bool[] newLines = new bool[4] { true, true, true, true };
             for (int i = 0; i < 4; i++)
             {
-                if (quadLines[i] != null)
-                    LineIdentified.Add(quadLines[i]);
-                else if (oldLines != null)
-                    LineIdentified.Add(oldLines[i]);
+                if (quadLinesSemiAuto[i] != null)
+                    LineIdentifiedSemiAuto.Add(quadLinesSemiAuto[i]);
+                else if (manualLines != null)
+                {
+                    LineIdentifiedSemiAuto.Add(manualLines[i]);
+                    newLines[i] = false;
+                }
             }
 
-            var drawScreenMap = new Matrix(Size, Tiling: true);
-            foreach (var l in LineIdentified)
-                o0Extension.DrawLine(drawScreenMap, l, (x, y) => 1, new Geometry2D.Float.Vector(0, lineWidth));
-            Texture2D ScreenQuadTex = drawScreenMap.ToTex();      // out ScreenQuadTex
-
-            QuadrilateralInCamera screenQuad = null;
-            if (LineIdentified.Count == 4)
+            if (LineIdentifiedSemiAuto.Count == 4)            // 判断识别的线段能否拼成屏幕,能拼成则记录
             {
-                var a = LineIdentified[0].Intersect(LineIdentified[3], false).Value;
-                var b = LineIdentified[0].Intersect(LineIdentified[1], false).Value;
-                var c = LineIdentified[2].Intersect(LineIdentified[3], false).Value;
-                var d = LineIdentified[1].Intersect(LineIdentified[2], false).Value;
-                screenQuad = new QuadrilateralInCamera(a, b, c, d, new Vector(Size.x, Size.y));
-                if (!screenQuad.IsQuadComplete())
-                    screenQuad = null;
+                var a = LineIdentifiedSemiAuto[0].Line.Intersect(LineIdentifiedSemiAuto[3].Line, false).Value;
+                var b = LineIdentifiedSemiAuto[0].Line.Intersect(LineIdentifiedSemiAuto[1].Line, false).Value;
+                var c = LineIdentifiedSemiAuto[2].Line.Intersect(LineIdentifiedSemiAuto[3].Line, false).Value;
+                var d = LineIdentifiedSemiAuto[1].Line.Intersect(LineIdentifiedSemiAuto[2].Line, false).Value;
+                QuadSemiAuto = new QuadrilateralInCamera(a, b, c, d, new Vector(Size.x, Size.y));
+                if (!QuadSemiAuto.IsQuadComplete())
+                    QuadSemiAuto = null;
             }
-            if (screenQuad == null && Screen.QuadInCamera != null)     // 如果可能,回退到上一个screen
+            #endregion
+
+            // 优先应用半自动的结果(也可以在外部手动设置)
+            if (QuadSemiAuto == null && QuadAuto == null && Screen.QuadInCamera != null)     // 如果可能,回退到上一个screen
             {
                 Debug.Log("<color=aqua>[ScreenIdentification] 本次识别失败,回退到上次的识别结果</color>");
                 quadTemp.Add(Screen.QuadInCamera.Quad);
             }
-            else if (screenQuad != null)
+            else if (QuadSemiAuto != null)
+            {
+                Debug.Log("<color=aqua>[ScreenIdentification] 识别到四边形</color>");
+                quadTemp.Add(QuadSemiAuto.Quad);
+            }else if (QuadAuto != null)
             {
                 Debug.Log("<color=aqua>[ScreenIdentification] 识别到四边形</color>");
-                quadTemp.Add(screenQuad.Quad);
+                quadTemp.Add(QuadAuto.Quad);
             }
 
+            #region 绘制 output texture
+            // 绘制半自动
+            var ScreenQuadMap = new Matrix(Size, Tiling: true);     // 识别的到的屏幕四边形(半自动和自动在一张图上) 
+            foreach (var i in LineIdentifiedSemiAuto.Index())
+            {
+                if (newLines[i])
+                    o0Extension.DrawLine(ScreenQuadMap, LineIdentifiedSemiAuto[i].DrawLine, (x, y) => 2, new Geometry2D.Float.Vector(0, 10));
+                else
+                    o0Extension.DrawLine(ScreenQuadMap, LineIdentifiedSemiAuto[i].DrawLine, (x, y) => 1, new Geometry2D.Float.Vector(0, 6), true);
+            }
 
+            // 绘制全自动
+            foreach (var i in LineIdentifiedAuto.Index())
+                o0Extension.DrawLine(ScreenQuadMap, LineIdentifiedAuto[i].DrawLine, (x, y) => 4, new Geometry2D.Float.Vector(0, 4), true);
+
+            Texture2D ScreenQuad = ScreenQuadMap.ToTexRGBA(FloatValueToColor);
+            Texture2D ScreenQuadWithScreen = ScreenQuad.Overlay(ScreenLocateTexture);   // 叠加屏幕色差图
+
+            // 绘制allLines
+            var allLinesMap = new Matrix(Size, Tiling: true);
+            foreach (var l in allLines)
+            {
+                if (l.DrawLine != null)
+                    o0Extension.DrawLine(allLinesMap, l.DrawLine, (x, y) => 3, new Geometry2D.Float.Vector(0, 2), true);
+            }
+            var allLinesTex = allLinesMap.ToTexRGBA(FloatValueToColor);
+            ScreenLocate.DebugTexture(1, allLinesTex);
 
             // 还需要输出一张识别结果图,包含干扰线段
-            var LSDLineMap = new Matrix(Size, Tiling: true);
+            var ChoosableLineMap = new Matrix(Size, Tiling: true);
             foreach (var l in possibleLines)
             {
-                if (l != null && !quadLines.Contains(l))
-                    o0Extension.DrawLine(LSDLineMap, l, (x, y) => 3, new Geometry2D.Float.Vector(0, 2), true); // 其他的备选线段
+                if (l != null && !quadLinesSemiAuto.Contains(l) && !manualLines.Contains(l))
+                    o0Extension.DrawLine(ChoosableLineMap, l.DrawLine, (x, y) => 3, new Geometry2D.Float.Vector(0, 2), true); // 其他的备选线段
             }
-            foreach (var l in quadLines)
+            foreach (var l in LineIdentifiedSemiAuto)
             {
                 if (l != null)
-                    o0Extension.DrawLine(LSDLineMap, l, (x, y) => 2, new Geometry2D.Float.Vector(0, 4)); // 这次识别到的线段
+                    o0Extension.DrawLine(ChoosableLineMap, l.DrawLine, (x, y) => 2, new Geometry2D.Float.Vector(0, 5)); // 识别的结果
             }
-            if (oldLines != null)
+            if (manualLines != null)
             {
-                foreach (var l in oldLines)
-                    o0Extension.DrawLine(LSDLineMap, l, (x, y) => 1, new Geometry2D.Float.Vector(0, 2), true);     // 旧的屏幕线段(例如上次手动识别的)
+                foreach (var l in manualLines)
+                    o0Extension.DrawLine(ChoosableLineMap, l.DrawLine, (x, y) => 1, new Geometry2D.Float.Vector(0, 2), true);     // 旧的屏幕线段(例如上次手动识别的)
             }
-            Texture2D ChoosableLineTex = LSDLineMap.ToTexRGBA(FloatValueToColor);
-
+            Texture2D ChoosableLineTex = ChoosableLineMap.ToTexRGBA(FloatValueToColor);
+            #endregion
 
+            log += $"\r\n屏幕四边形_手动识别{QuadManual != null}\r\n屏幕四边形_半自动识别{QuadSemiAuto != null}\r\n屏幕四边形_全自动识别{QuadAuto != null}";
             Debug.Log(log);
             // 是否将图片保存到本地
             if (ScreenLocate.Main.SaveToggle.isOn && ScreenLocate.Main.DebugOnZIMDemo)
             {
                 var FileDirectory = $"Debug_屏幕定位/";
-                SaveImages(FileDirectory, log, ScreenLocateTexture, allLinesTex, ChoosableLineTex, ScreenQuadTex);
+                SaveImages(FileDirectory, log, ScreenLocateTexture, allLinesTex, ChoosableLineTex, ScreenQuad);
             }
 
             //times.Add(watch.ElapsedMilliseconds);
@@ -791,9 +866,8 @@ namespace o0.Project
 
             {
                 ScreenLocate.DebugTexture(2, ScreenLocateTexture);
-                ScreenLocate.DebugTexture(3, ScreenQuadTex);
-                // 融合线段和原图
-                ScreenLocate.DebugTexture(4, ScreenLocateTexture.Merge(ScreenQuadTex));
+                ScreenLocate.DebugTexture(3, ScreenQuad);
+                ScreenLocate.DebugTexture(4, ScreenQuadWithScreen);
                 ScreenLocate.DebugTexture(5, ChoosableLineTex);
             }
             foreach (var i in LocateTexTemp)
@@ -840,17 +914,42 @@ namespace o0.Project
             return l.Count;
         }
 
-        // 返回四边形的四条边,List长度一定是4 (如果没有识别到就是null),且线段顺序是: 下、右、上、左
-        List<Line> FilterLines(List<Matrix> screenLocateMatList, List<LineIdentified> allLines, Vector avgPoint,
-            out Line[] oldLines, out List<Line> possibleLines, 
-            ScreenMap screen, float conSize, float gradientLength, float minLength = 100)
+        // 返回四边形的四条边(半自动、全自动),List长度一定是4 (如果没有识别到就是null),且线段顺序是: 下、右、上、左
+        (List<LineIdentified>, List<LineIdentified>) FilterLines(List<Matrix> screenLocateMatList, List<LineIdentified> allLines, Vector avgPoint,
+            out LineIdentified[] manualLines, out List<LineIdentified> possibleLines, float conSize, float gradientLength, float minLength = 100)
         {
             //Debug.Log("[IdentifyLineLSD] lines.Count: " + lines.Count);
-            // LSD计算得到的矩阵尺寸较小(因为卷积),这里必须进行位移
             var offset = new Vector((conSize - 1) / 2, (conSize - 1) / 2);
+            // LSD计算得到的矩阵尺寸较小(因为卷积),这里必须进行位移
             for (int i = 0; i < allLines.Count; i++)
                 allLines[i].Offset(offset);
 
+            // 筛掉椭圆框外的线段(超出一半会筛掉)
+            var innerLines = new List<LineIdentified>();
+            for (int i = 0; i < allLines.Count; i++)
+            {
+                List<Vector> InArea = new List<Vector>();
+                var dir = (allLines[i].Line.B - allLines[i].Line.A) / 4;
+                var points = new Vector[5] { allLines[i].Line.A, allLines[i].Line.A + dir, allLines[i].Line.A + dir * 2f, allLines[i].Line.A + dir * 3f, allLines[i].Line.B };  // A点、中间的点、B点
+                for (int pI = 0; pI < points.Length; pI++)
+                {
+                    if (!ScreenLocate.Main.ScreenPixelCheaker.OutArea2D(points[pI], Size))
+                        InArea.Add(points[pI]);
+                }
+
+                if (InArea.Count < 2)   // 少于2个点在内部
+                    continue;
+                else if (InArea.Count < points.Length)  // 不完全在内部
+                    allLines[i].DrawLine = new Line(InArea.First(), InArea.Last());        // 将部分线条设置为drawline,用于下一步的绘制
+                else   // 线段全部在椭圆内
+                    allLines[i].DrawLine = allLines[i].Line;
+
+                innerLines.Add(allLines[i]);
+            }
+
+            // 角度阈值,用来判断线段的梯度方向是否指向屏幕中心(avgPoint)
+            var avaAngleHalf = 75f;
+
             // 沿直线计算综合梯度(梯度乘以长度系数,再乘以距离系数), distanceRatio是实际距离除以最大距离
             float estimateGradient(LineIdentified line, float distanceRatio)
             {
@@ -867,143 +966,121 @@ namespace o0.Project
                     lg.Add(screenLocateMatList[line.Batch][(int)ga.x, (int)ga.y] - screenLocateMatList[line.Batch][(int)gb.x, (int)gb.y]);
                 }
 
-                float e = (float)Math.Pow(Math.Ceiling(line.Line.Length / minLength), 0.2f);       // 长度系数,筛选时梯度更大、长度更长的线段更优
+                float e = (float)Math.Sqrt(Math.Ceiling(line.Line.Length / minLength));       // 长度系数,筛选时梯度更大、长度更长的线段更优
                 float d = (3 - distanceRatio) / 2;            // 距离系数,距离越近,系数越大
                 return e * d * Math.Abs(lg.Mean());
             }
+            // 根据线段梯度的角度,判断是不是屏幕的边,out index代表是哪条边(顺序是: 下、右、上、左)
+            bool isScreenLine(LineIdentified line, out int index)
+            {
+                var a = (avgPoint - (line.Line.A + line.Line.B) / 2).DegreeToXAxis();
+                //Debug.Log(a + ", " + gradient + ", " + sum);
+                index = -1;
+                if (Math.Abs(a - line.GradientDegree) < avaAngleHalf || Math.Abs(a - 360 - line.GradientDegree) < avaAngleHalf || Math.Abs(a + 360 - line.GradientDegree) < avaAngleHalf)
+                {
+                    if (line.GradientDegree > 45 && line.GradientDegree < 135)     // 下
+                        index = 0;
+                    else if (line.GradientDegree > 135 && line.GradientDegree < 225) // 右
+                        index = 1;
+                    else if (line.GradientDegree > 225 && line.GradientDegree < 315)  // 上
+                        index = 2;
+                    else
+                        index = 3;
+                    return true;
+                }
+                return false;
+            }
+
 
-            // 下、右、上、左
-            var quadLines = new List<(float, Line)>[4] { new List<(float, Line)>(), new List<(float, Line)>(), new List<(float, Line)>(), new List<(float, Line)>() };
-            possibleLines = new List<Line>();
-            oldLines = null;
+            // 下、右、上、左, 半自动和自动
+            var quadLinesSemiAuto = new List<(float, LineIdentified)>[4] { new List<(float, LineIdentified)>(), new List<(float, LineIdentified)>(), new List<(float, LineIdentified)>(), new List<(float, LineIdentified)>() };
+            var quadLinesAuto = new List<(float, LineIdentified)>[4] { new List<(float, LineIdentified)>(), new List<(float, LineIdentified)>(), new List<(float, LineIdentified)>(), new List<(float, LineIdentified)>() };
+            possibleLines = new List<LineIdentified>();
+            manualLines = null;
 
-            // 如果已有定位数据,根据现有数据筛选线条
-            if (screen.QuadInCamera != null)
+            // 如果已有手动定位数据,根据现有数据筛选线条(半自动)
+            if (QuadManual != null)
             {
                 Debug.Log("[IdentifyLineLSD] 根据已有定位数据做筛选");
-                screen.RefreshCameraSize(new Vector2(Size.x, Size.y));
-
                 var calibration = ScreenLocate.Main.ReDoLocateCalibrationRatio * Size.y;
-                oldLines = screen.QuadInCamera.GetLines();
+                manualLines = QuadManual.GetLines().Select((i) => new LineIdentified(0, i, 0, 0, true)).ToArray();
 
-                var pedals = oldLines.Select((i) => o0Extension.PointPedal(i, avgPoint, out _)).ToArray();     // 当前定位的垂足,下、右、上、左
+                var pedals = manualLines.Select((i) => o0Extension.PointPedal(i.Line, avgPoint, out _)).ToArray();     // 当前定位的垂足,下、右、上、左
 
-                foreach (var i in allLines)
+                foreach (var line in innerLines)
                 {
-                    float minDistance = float.MaxValue;
-                    int index = -1;
-                    foreach (var j in pedals.Index())
-                    {
-                        var d = (o0Extension.PointPedal(i.Line, avgPoint, out _) - pedals[j]).Length;
-                        if (d < minDistance)
-                        {
-                            minDistance = d;
-                            index = j;
-                        }
-                    }
-                    //Debug.Log(minDistance +", -----------"+ calibration);
-                    if (minDistance < calibration)      // 垂足的距离足够近
+                    // 筛选条件:1-梯度方向匹配,2-垂足的距离足够近, 3-新的线段的中点,到旧线段的垂足,要在旧线段内
+                    if (isScreenLine(line, out int index))
                     {
-                        // 另外满足,新的线段的中点,到旧线段的垂足,要在旧线段内
-                        var middleToOldLine = o0Extension.PointPedal(oldLines[index], (i.Line.A + i.Line.B) / 2, out bool inLineSegment);
-                        if (inLineSegment)
+                        var distanceToOld = (o0Extension.PointPedal(line.Line, avgPoint, out _) - pedals[index]).Length;
+                        if (distanceToOld < calibration)
                         {
-                            quadLines[index].Add((estimateGradient(i, minDistance / calibration), i.Line));
-                            possibleLines.Add(i.Line);
+                            var middleToOldLine = o0Extension.PointPedal(manualLines[index].Line, (line.Line.A + line.Line.B) / 2, out bool inLineSegment);
+                            if (inLineSegment)
+                            {
+                                quadLinesSemiAuto[index].Add((estimateGradient(line, distanceToOld / calibration), line));
+                                possibleLines.Add(line);
+                            }
                         }
                     }
                 }
             }
-            else
+
+            // 全自动
+            foreach (var line in allLines)
             {
-                var avaAngleHalf = 75f;
-                foreach (var line in allLines)
+                if (isScreenLine(line , out int index))
                 {
-                    possibleLines.Add(line.Line);
-
-                    var a = (avgPoint - (line.Line.A + line.Line.B) / 2).DegreeToXAxis();
-                    //Debug.Log(a + ", " + gradient + ", " + sum);
-                    int index = -1;
-                    if (Math.Abs(a - line.GradientDegree) < avaAngleHalf || Math.Abs(a - 360 - line.GradientDegree) < avaAngleHalf || Math.Abs(a + 360 - line.GradientDegree) < avaAngleHalf)
+                    if (line.Batch < 1)     // 全自动只处理第一张图,默认是色差图
                     {
-                        if (line.GradientDegree > 45 && line.GradientDegree < 135)     // 下
-                            index = 0;
-                        else if (line.GradientDegree > 135 && line.GradientDegree < 225) // 右
-                            index = 1;
-                        else if (line.GradientDegree > 225 && line.GradientDegree < 315)  // 上
-                            index = 2;
-                        else
-                            index = 3;
-
-                        //var g = Math.Abs(lg.Mean());
-                        //Debug.Log(gradient + ", " + g);
-
-                        //List<float> lp1 = new List<float>(), lp2 = new List<float>();    // 线两侧的值
-                        //for (float i = 0; i <= ll; i += step)
-                        //{
-                        //    var point = line.A + dir * i;
-                        //    var ga = point + vertical;
-                        //    var gb = point - vertical;
-                        //    lp1.Add(screenLocateMat[(int)ga.x, (int)ga.y]);
-                        //    lp2.Add(screenLocateMat[(int)gb.x, (int)gb.y]);
-                        //}
-
-                        //var avg1 = lp1.Mean();
-                        //var avg2 = lp2.Mean();
-                        //var v1 = lp1.Variance();
-                        //var v2 = lp2.Variance();
-
-                        //var lineGradient = Math.Abs(avg1 - avg2) / (v1 + v2 + 0.2f);       // 方差越小,梯度的价值越高
-                        ////var g = Math.Abs(lg.Mean());
-                        ////Debug.Log(gradient + ", " + g);
-                        //Debug.Log(v1 + ", " + v2 + ", " + lineGradient);
-
-                        //quadLines[index].Add((lineGradient, line));
-
-                        quadLines[index].Add((estimateGradient(line, 1), line.Line));
+                        quadLinesAuto[index].Add((estimateGradient(line, 1), line));
                     }
                 }
             }
 
-            var result = new Line[4];
+            var resultSemiAuto = new LineIdentified[4];
+            var resultAuto = new LineIdentified[4];
             for (int i = 0; i < 4; i++)
             {
-                if (quadLines[i].Count > 0)
-                    result[i] = quadLines[i].Max((a, b) => a.Item1.CompareTo(b.Item1)).Item2;
+                if (quadLinesSemiAuto[i].Count > 0)
+                    resultSemiAuto[i] = quadLinesSemiAuto[i].Max((a, b) => a.Item1.CompareTo(b.Item1)).Item2;
+                if (quadLinesAuto[i].Count > 0)
+                    resultAuto[i] = quadLinesAuto[i].Max((a, b) => a.Item1.CompareTo(b.Item1)).Item2;
             }
-            return result.ToList();
+            return (resultSemiAuto.ToList(), resultAuto.ToList());
         }
 
-        void SaveImages(string FileDirectory, string log, Texture2D ScreenLocateTex, Texture2D allLinesTex, Texture2D ChoosableLineTex, Texture2D ScreenQuadTex)
+        void SaveImages(string FileDirectory, string log, 
+            Texture2D ScreenLocateTex, Texture2D allLinesTex, Texture2D ChoosableLineTex, Texture2D ScreenQuadTex)
         {
             if (!Directory.Exists(FileDirectory))
                 Directory.CreateDirectory(FileDirectory);
             var time = DateTime.Now.ToString("yyyyMMdd_HHmmss");
 
-            var pngData = (ScreenLocate.Main.outputTexture2D[7] as Texture2D)?.EncodeToPNG();
+            var pngData = ScreenLocate.Main.OutputTextures[7]?.EncodeToPNG();
             if (pngData != null)
                 File.WriteAllBytes($"{FileDirectory}{time}A屏幕原图.png", pngData);
-            var pngData1 = ScreenLocateTex.EncodeToPNG();
+            var pngData1 = ScreenLocateTex?.EncodeToPNG();
             if (pngData1 != null)
                 File.WriteAllBytes($"{FileDirectory}{time}B黑白色差.png", pngData1);
-            var pngData2 = allLinesTex.EncodeToPNG();
+            var pngData2 = allLinesTex?.EncodeToPNG();
             if (pngData2 != null)
-                File.WriteAllBytes($"{FileDirectory}{time}C全部识别线段.png", pngData2);
-            var pngData3 = ChoosableLineTex.EncodeToPNG();
+                File.WriteAllBytes($"{FileDirectory}{time}C全部识别线段_半自动.png", pngData2);
+            var pngData3 = ChoosableLineTex?.EncodeToPNG();
             if (pngData3 != null)
-                File.WriteAllBytes($"{FileDirectory}{time}D备选线段.png", pngData3);
-            var pngData4 = ScreenQuadTex.EncodeToPNG();
+                File.WriteAllBytes($"{FileDirectory}{time}D备选线段_半自动.png", pngData3);
+            var pngData4 = ScreenQuadTex?.EncodeToPNG();
             if (pngData4 != null)
                 File.WriteAllBytes($"{FileDirectory}{time}E识别结果.png", pngData4);
 
 
             Debug.Log($"<color=aqua>({time}) 屏幕识别图片保存至:程序根目录/{FileDirectory}</color>");
-            log += 
+            log +=
                 $"\r\n屏幕原图保存{pngData != null}, " +
                 $"\r\n黑白色差保存{pngData1 != null}, " +
-                $"\r\n全部识别线段保存{pngData2 != null}, " +
-                $"\r\n备选线段保存{pngData3 != null}, " +
-                $"\r\n识别结果保存{pngData4 != null}, ";
+                $"\r\n全部识别线段(半自动)保存{pngData2 != null}, " +
+                $"\r\n备选线段(半自动)保存{pngData3 != null}, " +
+                $"\r\n识别结果保存{pngData4 != null}";
             File.WriteAllText($"{FileDirectory}{time}屏幕自动定位_日志.log", log);
         }
 

+ 4 - 0
Assets/InfraredProject/WebCamera/Script/ZIM/Other/OrdinalQuadrilateral.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using o0.Geometry2D.Float;
+using Unity.VisualScripting;
 
 namespace ZIM
 {
@@ -33,6 +34,9 @@ namespace ZIM
 
         public static OrdinalQuadrilateral Identity { get; } = new OrdinalQuadrilateral(new Vector(0, 0), new Vector(1, 0), new Vector(0, 1), new Vector(1, 1));
 
+        /// <summary>四边形的质心(顶点平均值)</summary>
+        public Vector Centroid => new Vector(A.x + B.x + C.x + D.x, A.y + B.y + C.y + D.y) / 4f;
+
         public override Vector this[int index]
         {
             get

+ 8 - 0
Assets/InfraredProject/WebCamera/Script/ZIM/Other/QuadrilateralInCamera.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Linq;
 using o0.Geometry2D;
 using o0.Geometry2D.Float;
 using UnityEngine;
@@ -33,6 +34,13 @@ namespace ZIM.Unity
             Quad = new OrdinalQuadrilateral(enumable);
         }
 
+        // 固定的顶点顺序: 左下,右下,左上,右上
+        public QuadrilateralInCamera(IEnumerable<Vector2> enumable, Vector cameraSize)
+        {
+            CameraSize = cameraSize;
+            Quad = new OrdinalQuadrilateral(enumable.Select((i=>i.o0Vector())));
+        }
+
         public QuadrilateralInCamera(OrdinalQuadrilateral quad, Vector cameraSize)
         {
             CameraSize = cameraSize;

+ 61 - 42
Assets/InfraredProject/WebCamera/Script/ZIM/ScreenLocate.cs

@@ -39,6 +39,13 @@ public partial class ScreenLocate : MonoBehaviour
         Android
     }
     Platform mPlatform = Platform.Android;
+    public enum ScreenIdentificationTag
+    {
+        // 屏幕定位的方式,手动、半自动、自动
+        Manual,
+        SemiAuto,
+        Auto
+    }
 
     // 2个灯,顺序根据红外灯的大小 由大到小, 坐标通过 InfraredSpot.ScreenUV 和 InfraredSpot.CameraLocation 获得
     public InfraredSpot[] InfraredSpots
@@ -61,6 +68,17 @@ public partial class ScreenLocate : MonoBehaviour
     public InfraredSpot[] infraredSpotBuffer;
     public string GetInfraredCount() { return infraredCount.ToString(); }
 
+    /// <summary>
+    /// 定位之后,可能有3种结果(手动、半自动、自动),从中选择一种作为最终识别到的屏幕。
+    /// 如果选择的是null,即没有识别到屏幕,则返回false,否则返回true
+    /// </summary>
+    public bool SelectScreenAfterLocate(ScreenIdentificationTag tag) => ScreenIdentification.SelectScreenAfterLocate(tag);
+
+    /// <summary>
+    /// 获取算法执行过程中输出的纹理,0原图,1半自动识别到的全部线段,2屏幕黑白色差,3识别结果,4屏幕色差叠加识别结果,5半自动时的备选线段
+    /// </summary>
+    public Texture2D[] OutputTextures => outputTexture2D;
+
     /// <summary>
     /// CameraLocation 的偏移量
     /// </summary>
@@ -99,15 +117,15 @@ public partial class ScreenLocate : MonoBehaviour
 
     public bool ShowScreenQuad = false;
 
-    // output的图像
-    // 图0是摄像机原图,图1是屏幕识别的全部可选线段,图2是识别出的屏幕画面,图3是识别出的屏幕四条边,图4是图2和图3的叠加,图5显示3种不同颜色的算法识别线段
+    // 显示在demo上的rawImage
     public List<RawImage> outputRawImages;
-    [NonSerialized] public Texture[] outputTexture2D = new Texture[8];
+    readonly Texture2D[] outputTexture2D = new Texture2D[8];
     public RawImage FullScreenImage;
 
     public PixelCheaker ScreenPixelCheaker;
     public InfraredSpotSettings InfraredSpotSettings;
 
+    // 全局记录当前算法中的CameraSize,红外识别和屏幕识别都会使用到
     public o0.Geometry2D.Vector<int> CameraSize { get; set; }
 
     public List<Texture2D> DebugScreenImages = new List<Texture2D>();
@@ -118,9 +136,9 @@ public partial class ScreenLocate : MonoBehaviour
     //是否单点显示
     public bool bSinglePoint = true;//默认单点识别
 
-    [NonSerialized] public float ReDoLocateCalibrationRatio = 0.08f;  // 重复定位时校准的距离比例,例如先手动定位,再自动定位,会以手动的结果来校准
+    [NonSerialized] public float ReDoLocateCalibrationRatio = 0.12f;  // 半自动定位时校准的距离比例,以手动的结果来校准,离手动太远的线段会被舍弃
 
-    [NonSerialized] public InfraredCount infraredCount = InfraredCount.Single;
+    [NonSerialized] public InfraredCount infraredCount = InfraredCount.Single;  // 识别红外灯的数量,1个或者2个
 
     bool bIdentifyRed = true;//默认设备红色
 
@@ -151,15 +169,17 @@ public partial class ScreenLocate : MonoBehaviour
     public float pcContrast { get; set; } = 0.0f;
     #endregion
 
-
+    // 红外灯识别算法
     InfraredLocate infraredLocate;
+    // 屏幕识别算法
+    o0.Project.ScreenIdentification screenIdentification;
+    public o0.Project.ScreenIdentification ScreenIdentification => screenIdentification;
+
     RectTransform canvas;
     Mode mode;
-    List<(Vector2 pos, GameObject go)> pointManual = new List<(Vector2, GameObject)>();
-
+    //List<(Vector2 pos, GameObject go)> pointManual = new List<(Vector2, GameObject)>();
     //o0.Project.WebCam o0WebCam = null;
-    o0.Project.ScreenIdentification screenIdentification;
-    public o0.Project.ScreenIdentification ScreenIdentification => screenIdentification;
+
 
     /// <summary>
     /// 正在识别的状态,自动识别时候记录
@@ -186,7 +206,7 @@ public partial class ScreenLocate : MonoBehaviour
         }
     }
 
-    static public void DebugTexture(int index, Texture texture)
+    static public void DebugTexture(int index, Texture2D texture)
     {
         LateDestory(Main.outputTexture2D[index]);
 
@@ -240,24 +260,23 @@ public partial class ScreenLocate : MonoBehaviour
         }
     }
 
-    public void ReSizeTexture(int width, int height)
-    {
-        Debug.Log("Cur mUVCTexture Size: [" + mUVCTexture.width + "," + mUVCTexture.height + "]");
-        return;
-        if (mUVCTexture.width < width || mUVCTexture.height < height)       // 如果当前分辨率太小,则重新new一个texture
-        {
-            Texture2D tex = new Texture2D(
-                            width, height,
-                            TextureFormat.ARGB32,
-                            false, /* mipmap */
-                            true /* linear */);
-            tex.filterMode = FilterMode.Point;
-            tex.Apply();
-            mUVCTexture = tex;
-            mUVCCameraInfo.previewTexture = tex;
-            var nativeTexPtr = mUVCCameraInfo.previewTexture.GetNativeTexturePtr();
-        }
-    }
+    //public void ReSizeTexture(int width, int height)
+    //{
+    //    Debug.Log("Cur mUVCTexture Size: [" + mUVCTexture.width + "," + mUVCTexture.height + "]");
+    //    if (mUVCTexture.width < width || mUVCTexture.height < height)       // 如果当前分辨率太小,则重新new一个texture
+    //    {
+    //        Texture2D tex = new Texture2D(
+    //                        width, height,
+    //                        TextureFormat.ARGB32,
+    //                        false, /* mipmap */
+    //                        true /* linear */);
+    //        tex.filterMode = FilterMode.Point;
+    //        tex.Apply();
+    //        mUVCTexture = tex;
+    //        mUVCCameraInfo.previewTexture = tex;
+    //        var nativeTexPtr = mUVCCameraInfo.previewTexture.GetNativeTexturePtr();
+    //    }
+    //}
 
     void Awake()
     {
@@ -429,7 +448,7 @@ public partial class ScreenLocate : MonoBehaviour
             }
          
             if (DebugOnZIMDemo)
-                Main.ShowScreen(Main.ScreenQuad, screenIdentification.Screen.QuadInCamera);
+                Main.ShowScreen(screenIdentification.Screen.QuadInCamera);
 
         }
 
@@ -905,18 +924,18 @@ public partial class ScreenLocate : MonoBehaviour
         }
         Info.text = "已识别到屏幕";
 
-        if (ScreenQuadObject && ScreenQuadObject.childCount >= 4)
-        {
-            ScreenQuadObject.gameObject.SetActive(true);
-            for (int i = 0; i < 4; i++)
-            {
-                if (DebugOnZIMDemo)
-                {
-                    RectTransform t = ScreenQuadObject.GetChild(i) as RectTransform;
-                    t.anchoredPosition = screen.Quad[i].pixelToLocalPosition_AnchorCenter(screen.CameraSize, ScreenQuadObject.rect);
-                }
-            }
-        }
+        //if (ScreenQuadObject && ScreenQuadObject.childCount >= 4)
+        //{
+        //    ScreenQuadObject.gameObject.SetActive(true);
+        //    for (int i = 0; i < 4; i++)
+        //    {
+        //        if (DebugOnZIMDemo)
+        //        {
+        //            RectTransform t = ScreenQuadObject.GetChild(i) as RectTransform;
+        //            t.anchoredPosition = screen.Quad[i].pixelToLocalPosition_AnchorCenter(screen.CameraSize, ScreenQuadObject.rect);
+        //        }
+        //    }
+        //}
         quadUnityVectorList = screen.GetUnityVertexNormalizedList();      // 记录四个点
         if (!ContainsNaN(quadUnityVectorList))
         {

+ 29 - 1
Assets/InfraredProject/WebCamera/Script/ZIM/ZIMUnity/Extension.cs

@@ -65,7 +65,12 @@ namespace ZIM.Unity
 
         public static float Brightness(this Color c)
         {
-            return 0.59f * c.r + 0.3f * c.r + 0.11f * c.r;    // 红色为主
+            return 0.59f * c.r + 0.3f * c.g + 0.11f * c.b;    // 红色为主
+        }
+
+        public static float Lightness(this Color c)
+        {
+            return Mathf.Max(c.r, c.g, c.b);
         }
 
         // 转换为整数再计算
@@ -78,6 +83,7 @@ namespace ZIM.Unity
             return (int)(0.59f * r + 0.3f * g + 0.11f * b);    // 红色为主
         }
 
+        // a 融合b,颜色直接相加
         public static Texture2D Merge(this Texture2D a, Texture2D b)
         {
             if (a.width != b.width || a.height != b.height)
@@ -93,6 +99,28 @@ namespace ZIM.Unity
             return texAdd;
         }
 
+        // a 覆盖 b,会剔除a的黑色背景
+        public static Texture2D Overlay(this Texture2D a, Texture2D b)
+        {
+            if (a.width != b.width || a.height != b.height)
+                throw new InvalidOperationException("尺寸不同无法叠加texture");
+
+            var pixel0 = a.GetPixels();
+            var pixel1 = b.GetPixels();
+            for (int i = 0; i < pixel0.Length; i++)
+            {
+                float newA = (float)(Math.Clamp((pixel0[i].Lightness() - 0.2) / 0.6, 0, 1) * pixel0[i].a);
+                pixel0[i] = pixel0[i].RGBMutiply(newA) + pixel1[i] * (1 - newA);
+            }
+            var texAdd = new Texture2D(a.width, a.height);
+            texAdd.SetPixels(pixel0);
+            texAdd.Apply();
+            return texAdd;
+        }
+
+        // 长度平方,0-1
+        public static float LengthSquare(this Color c) => (c.r * c.r + c.g * c.g + c.b * c.b) / 3;
+
         // 像素坐标映射到Unity局部坐标
         public static Vector2 pixelToLocalPosition_AnchorCenter(this Vector2 pixel, Vector2 size, Rect dstRect)
         {

+ 4 - 2
Assets/InfraredProject/WebCamera/Script/ZIM/o0Extension/o0Extension.cs

@@ -7,6 +7,8 @@ namespace o0.Project
     {
         public static void DrawLine(Matrix matrix, Line line, Func<int, int, float> setValue, Geometry2D.Float.Vector size = default, bool dashedLine = false)
         {
+            if (line == null || matrix == null)
+                return;
             if (size == default)
                 size = new Geometry2D.Float.Vector(0, 1);
             if (size.x != 0)
@@ -16,14 +18,14 @@ namespace o0.Project
                 line = new Geometry2D.Float.Line(line.A - offset, line.B + offset);
             }
 
-            int drawCount = 0;
             void _draw(int x, int y)
             {
                 try
                 {
                     if (dashedLine)
                     {
-                        if (drawCount++ % 80 < 50)
+                        var d = (new Vector(x, y) - line.A).Length;
+                        if (d % 75 < 35)
                             matrix[x, y] = setValue(x, y);
                     }
                     else

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 717 - 52
Assets/InfraredProject/WebCamera/zimWebCamera.unity


+ 27 - 17
Assets/SmartBow/Resources/SmartBow/Prefabs/ZIM/PixelCheaker.cs

@@ -1,4 +1,5 @@
-using System;
+using o0.Geometry2D;
+using System;
 using System.Collections;
 using System.Collections.Generic;
 using Unity.VisualScripting;
@@ -11,29 +12,38 @@ public class PixelCheaker : MonoBehaviour
     public Image Bordermage;            // 椭圆的图片
     public RectTransform BorderArea;    // 椭圆的大小区域,调整这个gameobject的scale作为椭圆的区域
 
-    private Vector2 _imageSize;
-    public Vector2 ImageSize
+    public void Start()
     {
-        get => _imageSize;
-        set
-        {
-            if (_imageSize != value)
-            {
-                _imageSize = value;
-                ellipseRadius = BorderArea.localScale / 2f;
-            }
-        }
+        ellipseRadius = BorderArea.localScale / 2f;
     }
 
-    private Vector2 ellipseRadius; 
+    private Vector2 ellipseRadius;
 
-    public bool OutCollider2D(Vector2 p)
+    public bool OutArea2D(Vector2 pixelPosition, Vector<int> imageSize, out float f) => OutArea2D(new Vector2(pixelPosition.x / imageSize.x, pixelPosition.y / imageSize.y), out f);
+    public bool OutArea2D(Vector2 pixelPosition, Vector2 imageSize, out float f) => OutArea2D(pixelPosition / imageSize, out f);
+    public bool OutArea2D(o0.Geometry2D.Float.Vector pixelPosition, Vector<int> imageSize, out float f) => OutArea2D(new Vector2(pixelPosition.x / imageSize.x, pixelPosition.y / imageSize.y), out f);
+    public bool OutArea2D(Vector2 pixelPosition, Vector<int> imageSize) => OutArea2D(new Vector2(pixelPosition.x / imageSize.x, pixelPosition.y / imageSize.y));
+    public bool OutArea2D(Vector2 pixelPosition, Vector2 imageSize) => OutArea2D(pixelPosition / imageSize);
+    public bool OutArea2D(o0.Geometry2D.Float.Vector pixelPosition, Vector<int> imageSize) => OutArea2D(new Vector2(pixelPosition.x / imageSize.x, pixelPosition.y / imageSize.y));
+
+    // out 椭圆的距离系数,越大表示离椭圆越远
+    private bool OutArea2D(Vector2 pixelUV, out float f)
     {
         Vector2 center = new Vector2(0.5f, 0.5f);           // 椭圆中心
+        float normalizedX = (pixelUV.x - center.x) / ellipseRadius.x;
+        float normalizedY = (pixelUV.y - center.y) / ellipseRadius.y;
 
-        p /= ImageSize;
-        float normalizedX = (p.x - center.x) / ellipseRadius.x;
-        float normalizedY = (p.y - center.y) / ellipseRadius.y;
+        float value = (normalizedX * normalizedX) + (normalizedY * normalizedY);    // 使用椭圆方程判断
+        f = (float)Math.Sqrt(value);
+        return value > 1.0f;
+    }
+
+    // 无out的版本
+    private bool OutArea2D(Vector2 pixelUV)
+    {
+        Vector2 center = new Vector2(0.5f, 0.5f);           // 椭圆中心
+        float normalizedX = (pixelUV.x - center.x) / ellipseRadius.x;
+        float normalizedY = (pixelUV.y - center.y) / ellipseRadius.y;
 
         float value = (normalizedX * normalizedX) + (normalizedY * normalizedY);    // 使用椭圆方程判断
         return value > 1.0f;

+ 41 - 11
Assets/SmartBow/Scripts/Views/InfraredViewParts/InfraredScreenPositioningView.cs

@@ -10,6 +10,7 @@ using Color = UnityEngine.Color;
 using InfraredManager;
 using Org.BouncyCastle.Asn1.Crmf;
 using ZIM.Unity;
+using o0.Project;
 
 public class LinePosition
 {
@@ -72,6 +73,8 @@ public class InfraredScreenPositioningView : JCUnityLib.ViewBase
     [SerializeField] Color normalTextColor = Color.black;
     [SerializeField] Color highlightedTextColor = Color.white;
 
+    bool doLocateAuto;
+
     int DefaultResolutionIndex;
     private void Awake()
     {
@@ -81,6 +84,7 @@ public class InfraredScreenPositioningView : JCUnityLib.ViewBase
 
     void Start()
     {
+        doLocateAuto = false;
 
         textTip1.SetActive(true);
         //设置btnAuto 高亮
@@ -230,6 +234,7 @@ public class InfraredScreenPositioningView : JCUnityLib.ViewBase
     public void OnClick_Auto()
     {
         bAuto = true;
+        doLocateAuto = true;
 
         textTip1.SetActive(false);
         textTip2.SetActive(true);
@@ -257,6 +262,7 @@ public class InfraredScreenPositioningView : JCUnityLib.ViewBase
     public void onHandMovement()
     {
         bAuto = false;
+        doLocateAuto = false;
         draggableParent.gameObject.SetActive(true);
         pointsParent.gameObject.SetActive(false);
 
@@ -416,6 +422,7 @@ public class InfraredScreenPositioningView : JCUnityLib.ViewBase
         if (enterFromZimWebCamera)
         {
             ConfirmScreenLocateManualTest();
+
             if (oldLinePosition.Count > 1) // 确保列表不为空
             {
                 LinePosition lastElement = oldLinePosition[oldLinePosition.Count - 1];
@@ -497,7 +504,12 @@ public class InfraredScreenPositioningView : JCUnityLib.ViewBase
         SetLinePos();
 
         btnRecordInfrared.Reset();
-        ScreenLocate.Main.ScreenIdentification.SetScreenQuad(null);
+        ScreenLocate.Main.ScreenIdentification.ClearQuadCache();
+        if (enterFromZimWebCamera)
+        {
+            ScreenLocate.Main.ScreenQuad.gameObject.SetActive(false);
+            ScreenLocate.Main.UILineGenerator.Points = new Vector2[0];
+        }
     }
 
     #endregion
@@ -579,8 +591,9 @@ public class InfraredScreenPositioningView : JCUnityLib.ViewBase
         _screenLocate.QuitScreenLocateManual(_locatePointList);
         //FindObjectOfType<InfraredDemo>().SetLocatePointsToCameraRender(_locatePointList, _texWidth, _texHeight);
 
-        setPointsLocation(_locatePointList,GameObject.Find("WebCameraView/CameraImage0/ScreenQuad"));
         setPointsLocation(_locatePointList,pointsParent.gameObject,!bAuto);
+        if (!doLocateAuto)  // 设置手动定位数据
+            setPointsManual(_locatePointList, GameObject.Find("WebCameraView/CameraImage0/ScreenQuad"));
 
         //同步数据
         ScreenLocate.quadUnityVectorList.Clear();
@@ -591,22 +604,15 @@ public class InfraredScreenPositioningView : JCUnityLib.ViewBase
         ScreenLocate.quadUnityVectorList.Add(new Vector2(_locatePointList[2].x, _locatePointList[2].y));
         ScreenLocate.SaveScreenLocateVectorList();
 
-        var lo = new Vector2(-0.5f, -0.5f);
-        ScreenLocate.Main.UILineGenerator.Points = new Vector2[4] {
-            2 * (_locatePointList[0] + lo),
-            2 * (_locatePointList[1] + lo),
-            2 * (_locatePointList[2] + lo),
-            2 * (_locatePointList[3] + lo) };
     }
 
-    void setPointsLocation(List<Vector2> targetList ,GameObject pointsTF2,bool active = true) {
-
+    void setPointsLocation(List<Vector2> targetList, GameObject pointsTF2, bool active = true)
+    {
         //GameObject pointsTF2 = GameObject.Find("WebCameraView/CameraImage0/ScreenQuad");
         pointsTF2.SetActive(active);
 
         if (pointsTF2.transform.childCount == targetList.Count)
         {
-            Vector2 texSize = new Vector2(_texWidth, _texHeight);
             for (int i = 0; i < pointsTF2.transform.childCount; i++)
             {
                 Transform pointTF = pointsTF2.transform.GetChild(i);
@@ -615,8 +621,32 @@ public class InfraredScreenPositioningView : JCUnityLib.ViewBase
                 pointTF.gameObject.SetActive(true);
             }
         }
+        
     }
 
+    void setPointsManual(List<Vector2> targetList, GameObject pointsTF2, bool active = true)
+    {
+        setPointsLocation(targetList, pointsTF2, active);
+
+        var lo = new Vector2(-0.5f, -0.5f);
+        ScreenLocate.Main.UILineGenerator.Points = new Vector2[4] {
+                2 * (targetList[0] + lo),
+                2 * (targetList[1] + lo),
+                2 * (targetList[2] + lo),
+                2 * (targetList[3] + lo) };
+
+        // 记录手动数据
+        var size = ScreenLocate.Main.getUVCCameraInfoSize;
+        var quad = new QuadrilateralInCamera(
+                new o0.Geometry2D.Float.Vector(targetList[0].x * size.x, targetList[0].y * size.y),
+                new o0.Geometry2D.Float.Vector(targetList[1].x * size.x, targetList[1].y * size.y),
+                new o0.Geometry2D.Float.Vector(targetList[3].x * size.x, targetList[3].y * size.y),
+                new o0.Geometry2D.Float.Vector(targetList[2].x * size.x, targetList[2].y * size.y),
+                size.o0Vector()
+            );
+        ScreenLocate.Main.ScreenIdentification.QuadManual = quad;
+        Debug.Log("<color=aqua>[ScreenLocate] 记录手动数据</color>");
+    }
 
     void SaveLocalPos()
     {

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio