Procházet zdrojové kódy

Merge branch 'ZIM-DEV' into New红外灯算法-DEV

ZIM před 11 měsíci
rodič
revize
031d100a90

binární
Assets/InfraredProject/WebCamera/Image/Debug1.jpg


+ 123 - 0
Assets/InfraredProject/WebCamera/Image/Debug1.jpg.meta

@@ -0,0 +1,123 @@
+fileFormatVersion: 2
+guid: 240a2a431fcaafc41955f8c508eb82a1
+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: 

binární
Assets/InfraredProject/WebCamera/Image/Debug2.jpg


+ 123 - 0
Assets/InfraredProject/WebCamera/Image/Debug2.jpg.meta

@@ -0,0 +1,123 @@
+fileFormatVersion: 2
+guid: cfeda3b3fd30c9743888012459064ca8
+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: 

+ 8 - 0
Assets/InfraredProject/WebCamera/Script/ZIM/InfraredLocate/LineIdentify.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 84106b35ce9944345a6bb51787c57815
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 146 - 0
Assets/InfraredProject/WebCamera/Script/ZIM/InfraredLocate/LineIdentify/LineGuess.cs

@@ -0,0 +1,146 @@
+using o0.Geometry2D.Float;
+using o0.Num;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using UnityEngine;
+
+namespace o0.Project
+{
+    // 大于阈值则猜测是Line,但是这个类不考虑梯度,外部仍需结合梯度评估
+    public class LineGuess
+    {
+        public static float isLineThreshold = 1.0f;
+        // 记录分段的线段数据
+        class SubLineData
+        {
+            public List<float> DataList;    
+            public List<float> DiffList;    // 数据与平均值的差
+
+            // 幅度
+            float diffExtent = -1;
+            float dataExtent = -1;
+
+            public SubLineData(float data, float diff)
+            {
+                DataList = new List<float> { data };
+                DiffList = new List<float> { diff };
+            }
+
+            public void Add(float data, float diff)
+            {
+                DataList.Add(data);
+                DiffList.Add(diff);
+            }
+
+            // 得到差异的幅度
+            public float GetDiffExtent()
+            {
+                if (diffExtent == -1)
+                {
+                    var sum = 0.0f;
+                    foreach (var i in DiffList)
+                        sum += i * i;
+                    return diffExtent = sum / DiffList.Count;
+                }
+                else
+                    return diffExtent;
+            }
+
+            // 得到数据的幅度(和0比)
+            public float GetDataExtent()
+            {
+                if (dataExtent == -1)
+                {
+                    var sum = 0.0f;
+                    foreach (var i in DataList)
+                        sum += i * i;
+                    return dataExtent = sum / DataList.Count;
+                }
+                else
+                    return dataExtent;
+            }
+        }
+
+        public List<Matrix> ScreenLocateMatList;
+        public float GradientLength;
+        public float MinLineLength;
+
+        public LineGuess(List<Matrix> screenLocateMatList, float gradientLength, float minLineLength)
+        {
+            ScreenLocateMatList = screenLocateMatList;
+            GradientLength = gradientLength;
+            MinLineLength = minLineLength;
+        }
+
+        // 猜测是直线返回true
+        public bool GuessIsLine(LineIdentified line) => Guess(line) > isLineThreshold;
+
+        // 越大越可能是直线
+        public float Guess(LineIdentified line)
+        {
+            var dir = (line.Line.B - line.Line.A).Normalized;
+            var vertical = new Vector(-dir.y, dir.x) * (GradientLength / 2);
+            var verticalNormal = vertical.Normalized;
+            int lineABStep = (int)(MinLineLength / 5);
+            var ll = line.Line.Length;
+            var gradData = new List<float>();      // 直线两侧梯度的平均值,垂直于直线变化
+
+            var a0 = line.Line.A - vertical;
+            var gradStep = 1.5f;
+            for (float n = 0; n < GradientLength; n += gradStep)
+            {
+                var aN = a0 + verticalNormal * n;
+                var gradThisLine = new List<float>();
+                for (int i = 0; i <= ll; i += lineABStep)
+                {
+                    var point = aN + dir * i;
+                    gradThisLine.Add(ScreenLocateMatList[line.Batch][(int)point.x, (int)point.y]);
+                }
+                gradData.Add(gradThisLine.Mean());
+            }
+            //Debug.Log(line.Gradient + ", " + line.GradientDegree + ", " + gradData.ToJson());
+            if (gradData.Count <= 3)
+                return 400;
+
+            var gradChangeData = new List<float>();
+            for (int j = 1; j < gradData.Count; j++)
+                gradChangeData.Add(Math.Abs(gradData[j] - gradData[j - 1]));
+            var meanChange = gradChangeData.Mean();
+            var diff = gradChangeData[0] - meanChange;
+
+            var subList = new List<SubLineData>() { new SubLineData(gradChangeData[0], diff) };
+
+            for (int i = 1; i < gradChangeData.Count; i++)
+            {
+                var newDiff = gradChangeData[i] - meanChange;
+                if ((newDiff <= 0 && diff <= 0) || (newDiff > 0 && diff > 0))   // 同号,是一个分段
+                    subList.Last().Add(gradChangeData[i], newDiff);
+                else
+                    subList.Add(new SubLineData(gradChangeData[i], newDiff));
+                diff = newDiff;
+            }
+            int maxDiffIndex = -1;
+            if (subList.Count >= 3) 
+                maxDiffIndex = subList.MaxIndex((a, b) => a.GetDiffExtent().CompareTo(b.GetDiffExtent()));      // 找到分段里和平均数差异最大的分段,计算数据幅度的时候不计入
+
+            var lineGuessExtent = 0.0f;
+            for (int i = 0; i < subList.Count; i++)
+            {
+                if (i != maxDiffIndex)
+                    lineGuessExtent += subList[i].GetDataExtent();
+            }
+            if (maxDiffIndex == -1)
+                lineGuessExtent /= subList.Count;
+            else
+                lineGuessExtent /= subList.Count - 1;
+
+            lineGuessExtent = (float)Math.Sqrt(lineGuessExtent);
+            var countFactor = -0.002f * Math.Abs(subList.Count - 3) + 0.015f;  // 3时最大
+            //Debug.Log("maxDiffIndex: " + maxDiffIndex + ", sub count: " + subList.Count + ", lineGuess: " + (1 - lineGuessExtent + countFactor));
+            return 1 - lineGuessExtent + countFactor;
+        }
+    }
+
+
+}

+ 11 - 0
Assets/InfraredProject/WebCamera/Script/ZIM/InfraredLocate/LineIdentify/LineGuess.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 8501a1c5db387574788194c39a469865
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 6 - 0
Assets/InfraredProject/WebCamera/Script/ZIM/InfraredLocate/LineIdentified.cs → Assets/InfraredProject/WebCamera/Script/ZIM/InfraredLocate/LineIdentify/LineIdentified.cs

@@ -17,8 +17,14 @@ namespace o0.Project
         public float Gradient;
         // 线梯度方向, 0 - 180度
         public float GradientDegree;
+        // 筛线条时, 计算垂直直线的梯度值
+        public float ZIMGradient;
+        // 到屏幕中点的距离
+        public float DistanceToMiddle;
         // 用于绘制的线, 为null就是不绘制
         public Line DrawLine;
+        // 被认为是屏幕的哪条边,-1表示不是屏幕的边,0, 1, 2,3对应 下、右、上、左
+        public int ScreenLineIndex;
 
         public LineIdentified(int batch, Line line, float gradient, float gradientDegree, bool drawAll = false)
         {

+ 0 - 0
Assets/InfraredProject/WebCamera/Script/ZIM/InfraredLocate/LineIdentified.cs.meta → Assets/InfraredProject/WebCamera/Script/ZIM/InfraredLocate/LineIdentify/LineIdentified.cs.meta


+ 148 - 43
Assets/InfraredProject/WebCamera/Script/ZIM/InfraredLocate/ScreenIdentification.cs

@@ -9,6 +9,7 @@ using System.Linq;
 using System.Threading.Tasks;
 using UnityEngine;
 using UnityEngine.UIElements;
+using UnityStandardAssets.Utility;
 using ZIM;
 using ZIM.Unity;
 
@@ -144,6 +145,9 @@ namespace o0.Project
 
             //bStartLocateScreen = false;
 
+            ScreenWhiteTexture = null;
+            ScreenBlackTexture = null;
+
             OnLocateScreenEnter?.Invoke();
         }
         /// <summary>
@@ -667,15 +671,18 @@ namespace o0.Project
             // 屏幕黑白差值,存放多批次的图像用于识别, 该List数量不能等于 0 
             List<UnityEngine.Color[]> PixelsMultipleBatches = new List<UnityEngine.Color[]>();
 
-            var sw = new System.Diagnostics.Stopwatch();
-            sw.Start();
+            //var sw = new System.Diagnostics.Stopwatch();
+            //sw.Start();
 
             //读取数据
             if (debugImages != null && debugImages.Count != 0)
             {
+                var dSize = debugImages.First().Size();
                 foreach (var i in debugImages)
                 {
                     Debug.Log($"<color=aqua>Debug {i.name}</color>");
+                    if (i.Size() != dSize)
+                        throw new InvalidOperationException("Multiple Debug textures have different sizes");
                     PixelsMultipleBatches.Add(i.GetPixels());
                 }
 
@@ -698,8 +705,8 @@ namespace o0.Project
                     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;
+                        var d = ScreenWhiteTexture[i] * scale - ScreenBlackTexture[i];
+                        differPixel[i] = new UnityEngine.Color(d.x, d.y, d.z);
                         whitePixel[i] = new UnityEngine.Color(ScreenWhiteTexture[i].x, ScreenWhiteTexture[i].y, ScreenWhiteTexture[i].z) * scale;
                     }
                 });
@@ -710,8 +717,8 @@ namespace o0.Project
             int conSize = (int)Math.Ceiling(0.007f * Size.y) * 2 + 1;
             conSize = Math.Max(conSize, 7);     // 设置最小为7
 
-            float minLength = conSize * 7.7f;
-            minLength = locateIndex == -1 ? minLength : minLength * areaPercent;    // minLength需要按比例缩小
+            float minLength = conSize * 6f;
+            minLength = locateIndex == -1 ? minLength : minLength * areaPercent;    // minLength需要按areaPercent比例缩小
             string log = $"[Log][ScreenLocate Auto] Size: ({Size.x},{Size.y}), 卷积核Size: {conSize}, 最小线段长度: {minLength}";
 
             var allLines = new List<LineIdentified>();
@@ -722,20 +729,47 @@ namespace o0.Project
                 var locateTex = ToLocateTex(PixelsMultipleBatches[batch]);
                 LocateTexTemp.Add(locateTex);
                 var ScreenLocateMat = locateTex.Too0Mat();        // 用于获取Lines的Matrix
-                var lineCount = ZIMIdentifyQuadLSD(ref allLines, batch, ScreenLocateMat.zimIdentifyEdgeGradientAny(conSize), minLength);
+                var lineCount = ZIMIdentifyQuadLSD(
+                    ref allLines,
+                    batch,
+                    ScreenLocateMat.zimIdentifyEdgeGradientAny(conSize),
+                    minLength,
+                    new Vector(minLength * 0.4f, conSize * 1.6f));
                 log += $"\r\n识别图片{batch}, 识别到的线段数量为: {lineCount}";
                 ScreenLocateMatList.Add(ScreenLocateMat);
             }
             Texture2D ScreenLocateTexture = LocateTexTemp[0];       // for output
 
+            // LSD计算得到的矩阵尺寸较小(因为卷积),这里必须进行位移
+            // 新增:根据阈值筛去梯度太低的线段
+            float minGradient = 0.09f;
+            var offset = new Vector((conSize - 1) / 2, (conSize - 1) / 2);
+            var tempList = new List<LineIdentified>();
+            for (int i = 0; i < allLines.Count; i++)
+            {
+                var l = allLines[i];
+                if (l.Gradient > minGradient * l.Line.Length) 
+                {
+                    l.Offset(offset);
+                    tempList.Add(l);
+                }
+            }
+            allLines = tempList;
+            log += $"\r\n根据梯度阈值筛选,最终线段数量为: {allLines.Count}";
+
             // 如果有手动数据,刷新一下Size
             QuadManual?.ReSize(new Vector(Size.x, Size.y), ScreenMap.ViewAspectRatioSetting);
             // 估算屏幕中点,如果已有手动定位数据,根据现有数据取平均即可,否则从色差计算,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);
+            var (quadLinesSemiAuto, quadLinesAuto) = FilterLines(
+                ScreenLocateMatList, 
+                allLines, 
+                AvgPoint,
+                out LineIdentified[] manualLines, 
+                out List<LineIdentified> possibleLines,
+                conSize, 
+                minLength);
 
             #region 全自动识别的结果
             List<LineIdentified> LineIdentifiedAuto = new List<LineIdentified>();               // 线段顺序: 下、右、上、左
@@ -808,7 +842,7 @@ namespace o0.Project
                     o0Extension.DrawLine(ScreenQuadMap, LineIdentifiedSemiAuto[i].DrawLine, (x, y) => 5, new Geometry2D.Float.Vector(0, 10));
                 else
                     o0Extension.DrawLine(ScreenQuadMap, LineIdentifiedSemiAuto[i].DrawLine, (x, y) => 3, new Geometry2D.Float.Vector(0, 6), true);
-            }
+            } 
 
             // 绘制全自动
             foreach (var i in LineIdentifiedAuto.Index())
@@ -910,9 +944,9 @@ namespace o0.Project
 
         // 返回查找到的线段数量,0是查找失败
         int ZIMIdentifyQuadLSD(ref List<LineIdentified> allLines, int batch, (Matrix edgeMat, Matrix edgeDirMat) edgeGradient,
-            float minLength = 100)
+            float minLength, Vector LineCaptureSize)
         {
-            var l = edgeGradient.edgeMat.IdentifyLineLSD(edgeGradient.edgeDirMat, minLength, 50, LineCaptureSize: new Vector(10, 6));
+            var l = edgeGradient.edgeMat.IdentifyLineLSD(edgeGradient.edgeDirMat, minLength, 25, LineCaptureSize);
             if (l == null || l.Count == 0)
                 return 0;
             allLines.AddRange(l.Select((i) => new LineIdentified(batch, i)));
@@ -921,14 +955,8 @@ namespace o0.Project
 
         // 返回四边形的四条边(半自动、全自动),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)
+            out LineIdentified[] manualLines, out List<LineIdentified> possibleLines, float gradientLength, float minLength = 100)
         {
-            //Debug.Log("[IdentifyLineLSD] lines.Count: " + lines.Count);
-            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++)
@@ -955,12 +983,12 @@ namespace o0.Project
             // 角度阈值,用来判断线段的梯度方向是否指向屏幕中心(avgPoint)
             var avaAngleHalf = 75f;
 
-            // 沿直线计算综合梯度(梯度乘以长度系数,再乘以距离系数), distanceRatio是实际距离除以最大距离
-            float estimateGradient(LineIdentified line, float distanceRatio)
+            #region 内部函数
+            float ScreenGrad(LineIdentified line)
             {
                 var dir = (line.Line.B - line.Line.A).Normalized;
                 var vertical = new Vector(-dir.y, dir.x) * (gradientLength / 2);
-                var step = 2;
+                int step = (int)(minLength / 5);
                 var ll = line.Line.Length;
                 var lg = new List<float>();
                 for (int i = 0; i <= ll; i += step)
@@ -970,17 +998,24 @@ namespace o0.Project
                     var gb = point - vertical;
                     lg.Add(screenLocateMatList[line.Batch][(int)ga.x, (int)ga.y] - screenLocateMatList[line.Batch][(int)gb.x, (int)gb.y]);
                 }
-
+                return Math.Abs(lg.Mean());
+            }
+            // 沿直线计算综合梯度(梯度乘以长度系数,再乘以距离系数), distanceRatio是实际距离除以最大距离
+            float estimateGradient(LineIdentified line, float distanceRatio)
+            {
+                var gM = ScreenGrad(line);
                 float e = (float)Math.Sqrt(Math.Ceiling(line.Line.Length / minLength));       // 长度系数,筛选时梯度更大、长度更长的线段更优
-                float d = (3 - distanceRatio) / 2;            // 距离系数,距离越近,系数越大
-                return e * d * Math.Abs(lg.Mean());
+                float d = (5 - distanceRatio) / 4;             // 距离系数,距离越近,系数越大
+
+                line.ZIMGradient = e * d * gM;      // 记录一下综合梯度,全自动新增的功能会二次使用
+                return line.ZIMGradient;
             }
-            // 根据线段梯度的角度,判断是不是屏幕的边,out index代表是哪条边(顺序是: 下、右、上、左)
-            bool isScreenLine(LineIdentified line, out int index)
+            // 根据线段梯度的角度,判断是不是屏幕的边,index代表是哪条边(顺序是: 下、右、上、左)
+            void GetScreenLineIndex(LineIdentified line)
             {
                 var a = (avgPoint - (line.Line.A + line.Line.B) / 2).DegreeToXAxis();
                 //Debug.Log(a + ", " + gradient + ", " + sum);
-                index = -1;
+                var 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)     // 下
@@ -991,11 +1026,14 @@ namespace o0.Project
                         index = 2;
                     else
                         index = 3;
-                    return true;
                 }
-                return false;
+                line.ScreenLineIndex = index;
             }
+            #endregion
 
+            // 根据梯度方向,判断是哪条边
+            foreach (var l in innerLines)
+                GetScreenLineIndex(l);
 
             // 下、右、上、左, 半自动和自动
             var quadLinesSemiAuto = new List<(float, LineIdentified)>[4] { new List<(float, LineIdentified)>(), new List<(float, LineIdentified)>(), new List<(float, LineIdentified)>(), new List<(float, LineIdentified)>() };
@@ -1015,17 +1053,17 @@ namespace o0.Project
                 foreach (var line in innerLines)
                 {
                     // 筛选条件:1-梯度方向匹配,2-垂足的距离足够近, 3-线段的AB点均在旧线段外部, 4-新的线段的中点,到旧线段的垂足,要在旧线段内
-                    if (isScreenLine(line, out int index))
+                    if (line.ScreenLineIndex >= 0) 
                     {
-                        var distanceToOld = (o0Extension.PointPedal(line.Line, avgPoint, out _) - avgPointPedal[index]).Length;
+                        var distanceToOld = (o0Extension.PointPedal(line.Line, avgPoint, out _) - avgPointPedal[line.ScreenLineIndex]).Length;
                         if (distanceToOld < calibration &&
-                            manualLines[index].Line.LineCrossWithPoint(line.Line.A) * avgPointCross[index] <= 0 &&
-                            manualLines[index].Line.LineCrossWithPoint(line.Line.B) * avgPointCross[index] <= 0)
+                            manualLines[line.ScreenLineIndex].Line.LineCrossWithPoint(line.Line.A) * avgPointCross[line.ScreenLineIndex] <= 0 &&
+                            manualLines[line.ScreenLineIndex].Line.LineCrossWithPoint(line.Line.B) * avgPointCross[line.ScreenLineIndex] <= 0)
                         {
-                            var middleToOldLine = o0Extension.PointPedal(manualLines[index].Line, (line.Line.A + line.Line.B) / 2, out bool inLineSegment);
+                            var middleToOldLine = o0Extension.PointPedal(manualLines[line.ScreenLineIndex].Line, (line.Line.A + line.Line.B) / 2, out bool inLineSegment);
                             if (inLineSegment)
                             {
-                                quadLinesSemiAuto[index].Add((estimateGradient(line, distanceToOld / calibration), line));
+                                quadLinesSemiAuto[line.ScreenLineIndex].Add((estimateGradient(line, distanceToOld / calibration), line));
                                 possibleLines.Add(line);
                             }
                         }
@@ -1034,29 +1072,96 @@ namespace o0.Project
             }
 
             // 全自动
-            foreach (var line in allLines)
+            foreach (var line in innerLines)
             {
-                if (isScreenLine(line, out int index))
+                if (line.ScreenLineIndex >= 0 && line.Batch < 1)    // 全自动只处理第一张图,默认是色差图
                 {
-                    if (line.Batch < 1)     // 全自动只处理第一张图,默认是色差图
-                    {
-                        quadLinesAuto[index].Add((estimateGradient(line, 1), line));
-                    }
+                    quadLinesAuto[line.ScreenLineIndex].Add((estimateGradient(line, 1), line));
                 }
             }
 
+            // 获得半自动和全自动的结果
             var resultSemiAuto = new LineIdentified[4];
             var resultAuto = new LineIdentified[4];
+            var resultAutoPedal = new Vector[4];       // 用于找全自动的平行线
             for (int i = 0; i < 4; i++)
             {
                 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;
+                    if (resultAuto[i] != null)
+                        resultAutoPedal[i] = o0Extension.PointPedal(resultAuto[i].Line, avgPoint, out _);
+                }
+            }
+
+            // 新增功能(全自动,为了解决黑边问题):根据 resultAuto 再找平行线,判断是否替换(1-在 resultAuto 内部,2-接近平行,3-LineGuess判断是直线)
+            // 半自动不增加这个功能,直接通过增加到手动数据的距离阈值来解决黑边问题
+            var interSelectable = new List<LineIdentified>();
+            foreach (var line in innerLines)
+            {
+                if (line.ScreenLineIndex >= 0 && line.Batch < 1)
+                {
+                    if (line != resultAuto[line.ScreenLineIndex])
+                    {
+                        var pedal = o0Extension.PointPedal(line.Line, avgPoint, out _);
+                        var a0 = pedal - avgPoint;
+                        var a0L = a0.Length;
+                        line.DistanceToMiddle = a0L;
+                        var a1 = resultAutoPedal[line.ScreenLineIndex] - avgPoint;
+                        var a1L = a1.Length;
+                        if (a0L < a1L)
+                        {
+                            var dotN = a0.Dot(a1) / a0L / a1L;
+                            if (Math.Abs(dotN - 1) < 0.001) // 接近平行即可
+                                interSelectable.Add(line);
+                        }
+                    }
+                }
+            }
+            var (interA, interB) = FindInterLinePair(new LineGuess(screenLocateMatList, gradientLength * 2, minLength), interSelectable, 12);
+            if (interA != null && interB != null)   // 替换上一步筛选的结果中的一对边,得到最终的结果
+            {
+                resultAuto[interA.ScreenLineIndex] = interA;
+                resultAuto[interB.ScreenLineIndex] = interB;
             }
+
             return (resultSemiAuto.ToList(), resultAuto.ToList());
         }
 
+        (LineIdentified a, LineIdentified b) FindInterLinePair(LineGuess lineGuess, List<LineIdentified> interSelectable, int maxCountToSelect = 12)
+        {
+            Debug.Log("[ScreenIdentification] selectable inter line count: " + interSelectable.Count);
+            interSelectable.Sort((a, b) => b.ZIMGradient.CompareTo(a.ZIMGradient));
+            int count = 0;
+            LineIdentified[] selected = new LineIdentified[4];
+            foreach (var line in interSelectable)
+            {
+                if (lineGuess.GuessIsLine(line))        // 评价是不是Line, 并且找到离中心点最近的
+                {
+                    Debug.Log("[ScreenIdentification] guess is line: (index)" + line.ScreenLineIndex);
+                    if (selected[line.ScreenLineIndex] == null || selected[line.ScreenLineIndex].DistanceToMiddle > line.DistanceToMiddle)
+                        selected[line.ScreenLineIndex] = line;
+                }
+                if (count++ >= maxCountToSelect)
+                    break;
+            }
+            if (selected[0] != null && selected[1] != null && selected[2] != null && selected[3] != null)
+            {
+                if (selected[0].ZIMGradient + selected[2].ZIMGradient > selected[1].ZIMGradient + selected[3].ZIMGradient)
+                    return (selected[0], selected[2]);
+                else
+                    return (selected[1], selected[3]);
+            }
+            else if (selected[0] != null && selected[2] != null)
+                return (selected[0], selected[2]);
+            else if (selected[1] != null && selected[3] != null)
+                return (selected[1], selected[3]);
+            else
+                return (null, null);
+        }
+
         void SaveImages(string FileDirectory, string log,
             Texture2D ScreenLocateTex, Texture2D allLinesTex, Texture2D ChoosableLineTex, Texture2D ScreenQuadTex)
         {