Pārlūkot izejas kodu

Merge branch 'new-ui-infrared' into gitee_dev

ZIM 1 gadu atpakaļ
vecāks
revīzija
5e4a308397
21 mainītis faili ar 1043 papildinājumiem un 187 dzēšanām
  1. 91 0
      Assets/BowArrow/Scenes/GameChallengeScene/校准用的靶子/TargetObject2D.prefab
  2. 7 0
      Assets/BowArrow/Scenes/GameChallengeScene/校准用的靶子/TargetObject2D.prefab.meta
  3. 1 1
      Assets/InfraredProject/WebCamera/Image/1-测试图片.png.meta
  4. BIN
      Assets/InfraredProject/WebCamera/Image/20240923_111420A屏幕原图.png
  5. 123 0
      Assets/InfraredProject/WebCamera/Image/20240923_111420A屏幕原图.png.meta
  6. BIN
      Assets/InfraredProject/WebCamera/Image/20240923_111420B黑白色差.png
  7. 123 0
      Assets/InfraredProject/WebCamera/Image/20240923_111420B黑白色差.png.meta
  8. 1 0
      Assets/InfraredProject/WebCamera/Script/ZIM/DebugOnDemo.cs
  9. 1 1
      Assets/InfraredProject/WebCamera/Script/ZIM/DebugOnDemo.cs.meta
  10. 45 19
      Assets/InfraredProject/WebCamera/Script/ZIM/InfraredLocate/InfraredLocate.cs
  11. 153 51
      Assets/InfraredProject/WebCamera/Script/ZIM/InfraredLocate/InfraredSpot/PixelSpotArea.cs
  12. 1 1
      Assets/InfraredProject/WebCamera/Script/ZIM/InfraredLocate/InfraredSpot/PixelSpotArea.cs.meta
  13. 136 0
      Assets/InfraredProject/WebCamera/Script/ZIM/InfraredLocate/InfraredSpot/PixelSpotArea_DbScan.cs
  14. 11 0
      Assets/InfraredProject/WebCamera/Script/ZIM/InfraredLocate/InfraredSpot/PixelSpotArea_DbScan.cs.meta
  15. 42 0
      Assets/InfraredProject/WebCamera/Script/ZIM/InfraredLocate/LineIdentified.cs
  16. 11 0
      Assets/InfraredProject/WebCamera/Script/ZIM/InfraredLocate/LineIdentified.cs.meta
  17. 276 90
      Assets/InfraredProject/WebCamera/Script/ZIM/InfraredLocate/ScreenIdentification.cs
  18. 9 16
      Assets/InfraredProject/WebCamera/Script/ZIM/ScreenLocate.cs
  19. 1 1
      Assets/InfraredProject/WebCamera/Script/ZIM/o0Extension.meta
  20. 8 4
      Assets/InfraredProject/WebCamera/zimWebCamera.unity
  21. 3 3
      Assets/InfraredProject/o0/zimIdentifyLineLSD.cs

+ 91 - 0
Assets/BowArrow/Scenes/GameChallengeScene/校准用的靶子/TargetObject2D.prefab

@@ -0,0 +1,91 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!1 &6996483751777965607
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 5956873247306950790}
+  - component: {fileID: 8104374185026126055}
+  - component: {fileID: 7201364884107489749}
+  - component: {fileID: 3289135366177489764}
+  m_Layer: 5
+  m_Name: TargetObject2D
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!224 &5956873247306950790
+RectTransform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 6996483751777965607}
+  m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+  m_LocalPosition: {x: 0, y: 0, z: 0}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_ConstrainProportionsScale: 0
+  m_Children: []
+  m_Father: {fileID: 0}
+  m_RootOrder: 0
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+  m_AnchorMin: {x: 0.5, y: 0.5}
+  m_AnchorMax: {x: 0.5, y: 0.5}
+  m_AnchoredPosition: {x: 0, y: 0}
+  m_SizeDelta: {x: 200, y: 200}
+  m_Pivot: {x: 0.5, y: 0.5}
+--- !u!222 &8104374185026126055
+CanvasRenderer:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 6996483751777965607}
+  m_CullTransparentMesh: 1
+--- !u!114 &7201364884107489749
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 6996483751777965607}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  m_Material: {fileID: 0}
+  m_Color: {r: 1, g: 1, b: 1, a: 0.5882353}
+  m_RaycastTarget: 1
+  m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
+  m_Maskable: 1
+  m_OnCullStateChanged:
+    m_PersistentCalls:
+      m_Calls: []
+  m_Sprite: {fileID: 21300000, guid: a6ca034c14c5e46418c3e4d31d3ae246, type: 3}
+  m_Type: 0
+  m_PreserveAspect: 0
+  m_FillCenter: 1
+  m_FillMethod: 4
+  m_FillAmount: 1
+  m_FillClockwise: 1
+  m_FillOrigin: 0
+  m_UseSpriteMesh: 0
+  m_PixelsPerUnitMultiplier: 1
+--- !u!114 &3289135366177489764
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 6996483751777965607}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: fba4c80fe5844664ab053b308fd12a84, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 

+ 7 - 0
Assets/BowArrow/Scenes/GameChallengeScene/校准用的靶子/TargetObject2D.prefab.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 8d260cea079d7c3499396e68e067274f
+PrefabImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

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

@@ -1,5 +1,5 @@
 fileFormatVersion: 2
-guid: 6894a5dc9ea5b9748a6519bf8b298431
+guid: f0f6c5286b3b4524baae12fbf5cfa115
 TextureImporter:
   internalIDToNameTable: []
   externalObjects: {}

BIN
Assets/InfraredProject/WebCamera/Image/20240923_111420A屏幕原图.png


+ 123 - 0
Assets/InfraredProject/WebCamera/Image/20240923_111420A屏幕原图.png.meta

@@ -0,0 +1,123 @@
+fileFormatVersion: 2
+guid: f87cd5e946c72b94f8f96d241df677e4
+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
Assets/InfraredProject/WebCamera/Image/20240923_111420B黑白色差.png


+ 123 - 0
Assets/InfraredProject/WebCamera/Image/20240923_111420B黑白色差.png.meta

@@ -0,0 +1,123 @@
+fileFormatVersion: 2
+guid: 9d8901ef5c5fb5c49b0ba580f7698e8b
+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 - 0
Assets/InfraredProject/WebCamera/Script/ZIM/DebugOnDemo.cs

@@ -13,6 +13,7 @@ public class DebugOnDemo : MonoBehaviour
     {
         yield return null;
         ScreenLocate.Main.DebugOnZIMDemo = true;
+        ScreenLocate.Main.SaveToggle.isOn = true;
     }
 
     void Update()

+ 1 - 1
Assets/InfraredProject/WebCamera/Script/ZIM/DebugOnDemo.cs.meta

@@ -1,5 +1,5 @@
 fileFormatVersion: 2
-guid: f333b3d3462cbec49acf5b4d967f4d95
+guid: 4f2ded6b4a0d6f6449cba281b752cc91
 MonoImporter:
   externalObjects: {}
   serializedVersion: 2

+ 45 - 19
Assets/InfraredProject/WebCamera/Script/ZIM/InfraredLocate/InfraredLocate.cs

@@ -169,21 +169,20 @@ namespace ZIM
                 }
             });
 
-            //Debug.Log("---spotPoint.Count: " + spotPoint.Count);
-            //Debug.Log(samplingScale);
-            if (spotPoint.Count > 200)     // 如果亮点太多,控制亮点数量在200左右
+            if (ScreenLocate.Main.DebugOnZIMDemo)
             {
-                samplingScale = (int)Math.Ceiling(samplingScale * Math.Sqrt(spotPoint.Count / 200.0));
-                return new List<PixelSpotArea>();
-            }
-            else if (samplingScale > 1 && spotPoint.Count < 20) 
-            {
-                samplingScale = Math.Max((int)Math.Ceiling(samplingScale * Math.Sqrt(spotPoint.Count / 80.0)), 1);
-                return new List<PixelSpotArea>();
+                if (spotPoint.Count > 400)     // 如果亮点太多,控制亮点数量在200左右
+                {
+                    samplingScale = (int)Math.Ceiling(samplingScale * Math.Sqrt(spotPoint.Count / 400.0));
+                    return new List<PixelSpotArea>();
+                }
+                else if (samplingScale > 1 && spotPoint.Count < 100)
+                {
+                    samplingScale = Math.Max((int)Math.Ceiling(samplingScale * Math.Sqrt(spotPoint.Count / 200.0)), 1);
+                    return new List<PixelSpotArea>();
+                }
             }
 
-            //Debug.Log("spotPoint Count: " + spotPoint.Count + ", brightPoint Count: " + brightPoint.Count);
-
             //times.Add(watch.ElapsedMilliseconds);
             //UnityEngine.Debug.Log("time1: " + (times[times.Count - 1] - times[times.Count - 2]));
 
@@ -212,8 +211,9 @@ namespace ZIM
                 //times.Add(watch.ElapsedMilliseconds);
                 //UnityEngine.Debug.Log("time2: " + (times[times.Count - 1] - times[times.Count - 2]));
 
-                var db = Dbscan.Run(spotPoint, ZIM.Unity.ZIMMath.LengthManhattan, samplingScale * 2, 3);
-                var spotArea = DbscanToSpotAreas(db, brightPoint);
+                //var db = Dbscan.Run(spotPoint, ZIM.Unity.ZIMMath.LengthManhattan, samplingScale * 2, 3);
+                //var spotArea = DbscanToSpotAreas(db, brightPoint);
+                var spotArea = PixelSpotArea.Cluster(spotPoint, brightPoint, screenIdentification.Screen.UVInScreen);
 
                 if (ScreenLocate.Main.DebugOnZIMDemo)
                     DebugAreas(spotArea);
@@ -271,7 +271,7 @@ namespace ZIM
             return new List<PixelSpotArea>();
         }
 
-        private void DebugAreas(List<PixelSpotArea> areas)
+        private void DebugAreas(List<PixelSpotArea_DbScan> areas)
         {
             Texture2D texture = new Texture2D(ScreenLocate.Main.CameraSize.x, ScreenLocate.Main.CameraSize.y);
             Color[] blackPixels = new Color[texture.width * texture.height];
@@ -297,17 +297,43 @@ namespace ZIM
             ScreenLocate.DebugTexture(6, texture);
         }
 
-        private List<PixelSpotArea> DbscanToSpotAreas(DbscanResult<Vector2> db, List<Vector2> brightPoint)
+        private void DebugAreas(List<PixelSpotArea> areas)
+        {
+            Texture2D texture = new Texture2D(ScreenLocate.Main.CameraSize.x, ScreenLocate.Main.CameraSize.y);
+            Color[] blackPixels = new Color[texture.width * texture.height];
+            for (int i = 0; i < blackPixels.Length; i++)
+                blackPixels[i] = Color.black;
+            texture.SetPixels(blackPixels);
+
+            Color[] colors = new Color[4] { Color.yellow, Color.white, Color.green, Color.blue };
+            int index = 0;
+            foreach (var i in areas)
+            {
+                foreach (var p in i.Pixels0)
+                    texture.SetPixel((int)p.x, (int)p.y, colors[index]);
+                index++;
+                foreach (var p in i.Pixels1)
+                    texture.SetPixel((int)p.x, (int)p.y, colors[index]);
+                index++;
+                if (index >= 4)
+                    break;
+            }
+
+            texture.Apply();
+            ScreenLocate.DebugTexture(6, texture);
+        }
+
+        private List<PixelSpotArea_DbScan> DbscanToSpotAreas(DbscanResult<Vector2> db, List<Vector2> brightPoint)
         {
             if (db.Clusters.Count == 0)
-                return new List<PixelSpotArea>();
+                return new List<PixelSpotArea_DbScan>();
 
             // 用dbscan的结果创建SpotArea,先用高亮区算一遍半径
-            var clusters = new List<PixelSpotArea>();
+            var clusters = new List<PixelSpotArea_DbScan>();
             var areaIncludeRadius = new List<(Vector2, float)>();
             foreach (var i in db.Clusters.Values)
             {
-                var area = new PixelSpotArea(i);
+                var area = new PixelSpotArea_DbScan(i);
                 areaIncludeRadius.Add((area.Centroid, area.Radius));
                 clusters.Add(area);
             }

+ 153 - 51
Assets/InfraredProject/WebCamera/Script/ZIM/InfraredLocate/InfraredSpot/PixelSpotArea.cs

@@ -6,21 +6,84 @@ using System.Linq;
 using System.Threading.Tasks;
 using UnityEngine;
 using ZIM.Unity;
-using static ScreenLocate;
 using Color = UnityEngine.Color;
 
 namespace ZIM
 {
-    public interface IPixel<T>
-    {
-        // 像素坐标点
-        T Point { get;}
-    }
-
     // 亮区的点用来定位(计算center),泛光区域的点用来计算radius
     public class PixelSpotArea
     {
-        // 中心
+        //public static float gridRatio0 = 6f / 1280;
+        //public static float gridRatio1 = 20f / 720;
+
+        public static int gridLength0 = 2;      // 亮区
+        public static int gridLength1 = 10;     // 边缘泛光
+
+        public static (int x, int y) GetGrid0(Vector2 v)
+        {
+            var m = (int)(v.x / gridLength0);
+            var n = (int)(v.y / gridLength0);
+            return (m, n);
+        }
+
+        public static (int x, int y) GetGrid1(Vector2 v)
+        {
+            var m = (int)(v.x / gridLength1);
+            var n = (int)(v.y / gridLength1);
+            return (m, n);
+        }
+
+        // 亮点聚类到区域
+        public static List<PixelSpotArea> Cluster(List<Vector2> spotPoint, List<Vector2> brightPoint, Func<Vector2, bool> PointFilter = null)
+        {
+            var spotArea = new List<PixelSpotArea>();
+            for (int i = 0; i < spotPoint.Count; i++)
+            {
+                var p = spotPoint[i];
+                if (PointFilter != null && !PointFilter(p))        // 筛选是否在屏幕内
+                    continue;
+
+                var grid = PixelSpotArea.GetGrid0(p);
+                var join = new SortedList<int, PixelSpotArea>();
+                for (int j = 0; j < spotArea.Count; j++)
+                {
+                    var area = spotArea[j];
+                    if (area.Include0(grid))
+                        join.Add(j, area);
+                }
+                if (join.Count == 0)
+                    spotArea.Add(new PixelSpotArea(p, grid, PixelSpotArea.GetGrid1(p)));
+                else if (join.Count == 1)
+                    join.First().Value.Join(p, grid, PixelSpotArea.GetGrid1(p));
+                else
+                {
+                    var combine = new PixelSpotArea(join.Values);
+                    combine.Join(p, grid, PixelSpotArea.GetGrid1(p));
+                    spotArea.Add(combine);
+                    for (int j = join.Count - 1; j >= 0; j--)
+                        spotArea.RemoveAt(join.ElementAt(j).Key);
+                }
+            }
+
+            if (spotArea.Count != 0)
+            {
+                // brightPoint合并到区域中
+                for (int i = 0; i < brightPoint.Count; i++)
+                {
+                    var p = brightPoint[i];
+                    var grid = PixelSpotArea.GetGrid1(p);
+                    for (int j = 0; j < spotArea.Count; j++)
+                    {
+                        var area = spotArea[j];
+                        if (area.Include1(grid))
+                            area.Add(p);
+                    }
+                }
+            }
+
+            return spotArea;
+        }
+
         Vector2 centroid = default;       // 该项目里center不可能等于0,0
         public Vector2 Centroid
         {
@@ -30,12 +93,12 @@ namespace ZIM
                     return centroid;
 
                 foreach (var p in Pixels0)
-                    centroid += p.Point;
+                {
+                    centroid += p;
+                }
                 return centroid /= Pixels0.Count;
             }
         }
-
-        // 亮区的半径,计算含泛光点
         float radius = 0;
         public float Radius
         {
@@ -48,63 +111,102 @@ namespace ZIM
                 if (radius != 0)
                     return radius;
 
-                return radius = CalculateRadius();
-            }
-        }
-        public float CalculateRadius()
-        {
-            //var radiusDic = new Dictionary<int, float>() { { 0, 0 }, { 45, 0 }, { 90, 0 }, { 135, 0 }, { 180, 0 }, { 225, 0 }, { 270, 0 }, { 315, 0 } };
-            var radiusDic = new float[8];
-            foreach (var p in Pixels0)
-            {
-                var dir = p.Point - Centroid;
-                var radius = dir.magnitude;
-                var degreeI = radius < 0.00001f ? 0 : (int)(dir.DegreeToXAxis() / 45);
-                if (degreeI > 7) degreeI = 0;       // 防止正好360度
+                //var radiusDic = new Dictionary<int, float>() { { 0, 0 }, { 45, 0 }, { 90, 0 }, { 135, 0 }, { 180, 0 }, { 225, 0 }, { 270, 0 }, { 315, 0 } };
+                var radiusDic = new float[8];
+                foreach (var p in Pixels0)
+                {
+                    var dir = p - Centroid;
+                    var radius = dir.magnitude;
+                    var degreeI = radius < 0.00001f ? 0 : (int)(dir.DegreeToXAxis() / 45);
+                    if (degreeI > 7) degreeI = 0;       // 防止正好360度
 
-                if (radius > radiusDic[degreeI])
-                    radiusDic[degreeI] = radius;
-            }
+                    if (radius > radiusDic[degreeI])
+                        radiusDic[degreeI] = radius;
+                }
 
-            foreach (var p in Pixels1)
-            {
-                var dir = p - Centroid;
-                var radius = dir.magnitude;
-                var degreeI = radius < 0.00001f ? 0 : (int)(dir.DegreeToXAxis() / 45);
-                if (degreeI > 7) degreeI = 0;
+                foreach (var p in Pixels1)
+                {
+                    var dir = p - Centroid;
+                    var radius = dir.magnitude;
+                    var degreeI = radius < 0.00001f ? 0 : (int)(dir.DegreeToXAxis() / 45);
+                    if (degreeI > 7) degreeI = 0;
 
-                if (radius > radiusDic[degreeI])
-                    radiusDic[degreeI] = radius;
+                    if (radius > radiusDic[degreeI])
+                        radiusDic[degreeI] = radius;
+                }
+                return radius = radiusDic.Mean();
             }
-            return radiusDic.Mean();
         }
 
-        // 中心亮点
-        public List<IPixel<Vector2>> Pixels0 = new List<IPixel<Vector2>>();
-        // 泛光
-        public List<Vector2> Pixels1 = new List<Vector2>();
+        public List<Vector2> Pixels0;
+        public List<Vector2> Pixels1;
+
+        HashSet<(int, int)> grids0;
+        HashSet<(int, int)> grids1;
 
-        public PixelSpotArea(IEnumerable<IPixel<Vector2>> p)        // kmeans中用随机点初始化,作为中心
+        public PixelSpotArea(Vector2 location, (int, int) grid0, (int, int) grid1)
         {
-            Pixels0.AddRange(p);
+            Pixels0 = new List<Vector2>(1000);       // 预估的初始容量
+            Pixels1 = new List<Vector2>(5000);
+            this.grids0 = new HashSet<(int, int)>();        // hashset如果设置了太大的容量会降低效率
+            this.grids1 = new HashSet<(int, int)>();
+            Join(location, grid0, grid1);
         }
 
-        // 中心亮点
-        public void Add0(IPixel<Vector2> p)
+        public PixelSpotArea(IList<PixelSpotArea> areas)
         {
-            Pixels0.Add(p);
+            Pixels0 = new List<Vector2>(1000);
+            Pixels1 = new List<Vector2>(5000);
+            grids0 = new HashSet<(int, int)>();
+            grids1 = new HashSet<(int, int)>();
+            foreach (var a in areas)
+            {
+                Pixels0.AddRange(a.Pixels0);
+                grids0.AddRange(a.grids0);
+                grids1.AddRange(a.grids1);
+                //Center += a.Center * a.SpotPointCount;
+                //SpotPointCount += a.SpotPointCount;
+            }
+            //Center /= SpotPointCount;
         }
 
-        // 泛光
-        public void Add1(Vector2 point)
+        public bool Include0((int, int) grid) => grids0.Contains(grid);
+        public bool Include1((int, int) grid) => grids1.Contains(grid);
+
+        public void Join(Vector2 point) => Join(point, GetGrid0(point), GetGrid1(point));
+        public void Join(Vector2 point, (int x, int y) grid0, (int x, int y) grid1)
         {
-            Pixels1.Add(point);
+            Pixels0.Add(point);
+            //SpotPointCount++;
+            //Center += (point - Center) / SpotPointCount;
+            grids0.AddRange(new (int, int)[]
+            {
+                    (grid0.x + -1, grid0.y +  0) , (grid0.x + 1 , grid0.y + 0),
+                    (grid0.x + 1,  grid0.y + -1) , (grid0.x + -1, grid0.y + 1),
+                    (grid0.x + 0,  grid0.y + -1) , (grid0.x + 0 , grid0.y + 1),
+                    (grid0.x + -1, grid0.y + -1) , (grid0.x + 1 , grid0.y + 1),
+                    grid0
+            });
+            grids1.AddRange(new (int, int)[]
+            {
+                    (grid1.x + -1, grid1.y +  0) , (grid1.x + 1 , grid1.y + 0),
+                    (grid1.x + 1,  grid1.y + -1) , (grid1.x + -1, grid1.y + 1),
+                    (grid1.x + 0,  grid1.y + -1) , (grid1.x + 0 , grid1.y + 1),
+                    (grid1.x + -1, grid1.y + -1) , (grid1.x + 1 , grid1.y + 1),
+                    grid1
+            });
         }
 
-        public void Clear()
+        // 不再join之后,才可以用add添加周围泛光
+        public void Add(Vector2 point)
         {
-            Pixels0.Clear();
-            Pixels1.Clear();
+            //var radius = (point - Center).LengthManhattan();
+            //if (radius > MaxRadius)
+            //    MaxRadius = radius;
+            Pixels1.Add(point);
+            //var radius = (point - Center).LengthManhattan();
+            //if (radius > MaxRadius)
+            //    MaxRadius = radius;
         }
 
         public float TotalBrightness(Color[] pixels, Func<int, int, int> Vector2ToIndex, int outer_size = 3)

+ 1 - 1
Assets/InfraredProject/WebCamera/Script/ZIM/InfraredLocate/InfraredSpot/PixelSpotArea.cs.meta

@@ -1,5 +1,5 @@
 fileFormatVersion: 2
-guid: 14094e24894f86145bf54897536727c8
+guid: 593e81f9469ce1d489c5b091cd129347
 MonoImporter:
   externalObjects: {}
   serializedVersion: 2

+ 136 - 0
Assets/InfraredProject/WebCamera/Script/ZIM/InfraredLocate/InfraredSpot/PixelSpotArea_DbScan.cs

@@ -0,0 +1,136 @@
+using o0;
+using o0.Num;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using UnityEngine;
+using ZIM.Unity;
+using static ScreenLocate;
+using Color = UnityEngine.Color;
+
+namespace ZIM
+{
+    public interface IPixel<T>
+    {
+        // 像素坐标点
+        T Point { get;}
+    }
+
+    // 亮区的点用来定位(计算center),泛光区域的点用来计算radius
+    public class PixelSpotArea_DbScan
+    {
+        // 中心
+        Vector2 centroid = default;       // 该项目里center不可能等于0,0
+        public Vector2 Centroid
+        {
+            get
+            {
+                if (centroid != default)
+                    return centroid;
+
+                foreach (var p in Pixels0)
+                    centroid += p.Point;
+                return centroid /= Pixels0.Count;
+            }
+        }
+
+        // 亮区的半径,计算含泛光点
+        float radius = 0;
+        public float Radius
+        {
+            set
+            {
+                radius = value;
+            }
+            get
+            {
+                if (radius != 0)
+                    return radius;
+
+                return radius = CalculateRadius();
+            }
+        }
+        public float CalculateRadius()
+        {
+            //var radiusDic = new Dictionary<int, float>() { { 0, 0 }, { 45, 0 }, { 90, 0 }, { 135, 0 }, { 180, 0 }, { 225, 0 }, { 270, 0 }, { 315, 0 } };
+            var radiusDic = new float[8];
+            foreach (var p in Pixels0)
+            {
+                var dir = p.Point - Centroid;
+                var radius = dir.magnitude;
+                var degreeI = radius < 0.00001f ? 0 : (int)(dir.DegreeToXAxis() / 45);
+                if (degreeI > 7) degreeI = 0;       // 防止正好360度
+
+                if (radius > radiusDic[degreeI])
+                    radiusDic[degreeI] = radius;
+            }
+
+            foreach (var p in Pixels1)
+            {
+                var dir = p - Centroid;
+                var radius = dir.magnitude;
+                var degreeI = radius < 0.00001f ? 0 : (int)(dir.DegreeToXAxis() / 45);
+                if (degreeI > 7) degreeI = 0;
+
+                if (radius > radiusDic[degreeI])
+                    radiusDic[degreeI] = radius;
+            }
+            return radiusDic.Mean();
+        }
+
+        // 中心亮点
+        public List<IPixel<Vector2>> Pixels0 = new List<IPixel<Vector2>>();
+        // 泛光
+        public List<Vector2> Pixels1 = new List<Vector2>();
+
+        public PixelSpotArea_DbScan(IEnumerable<IPixel<Vector2>> p)        // kmeans中用随机点初始化,作为中心
+        {
+            Pixels0.AddRange(p);
+        }
+
+        // 中心亮点
+        public void Add0(IPixel<Vector2> p)
+        {
+            Pixels0.Add(p);
+        }
+
+        // 泛光
+        public void Add1(Vector2 point)
+        {
+            Pixels1.Add(point);
+        }
+
+        public void Clear()
+        {
+            Pixels0.Clear();
+            Pixels1.Clear();
+        }
+
+        public float TotalBrightness(Color[] pixels, Func<int, int, int> Vector2ToIndex, int outer_size = 3)
+        {
+            (int x, int y) rectMin = ((int)(Centroid.x - Radius - outer_size), (int)(Centroid.y - Radius - outer_size));
+            (int x, int y) rectMax = ((int)(Centroid.x + Radius + outer_size), (int)(Centroid.y + Radius + outer_size));
+
+            var total = 0f;
+            Parallel.For(rectMin.x, rectMax.x, (i) =>
+            {
+                var t = 0;
+                for (int j = rectMin.y; j < rectMax.y; j++)
+                {
+                    var index = Vector2ToIndex(i, j);
+                    var b = pixels[index].Brightness(64);
+                    t += b;
+                }
+                lock (InfraredLocate.locker)
+                {
+                    total += t;
+                }
+            });
+            //total /= (rectMax.y - rectMin.y) * (rectMax.x - rectMin.x);
+
+            return total;
+        }
+    }
+
+}

+ 11 - 0
Assets/InfraredProject/WebCamera/Script/ZIM/InfraredLocate/InfraredSpot/PixelSpotArea_DbScan.cs.meta

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

+ 42 - 0
Assets/InfraredProject/WebCamera/Script/ZIM/InfraredLocate/LineIdentified.cs

@@ -0,0 +1,42 @@
+using o0.Geometry2D.Float;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace o0.Project
+{
+    public class LineIdentified
+    {
+        // 属于的识别批次
+        public int Batch;
+        // 线段
+        public Line Line;
+        // 线梯度
+        public float Gradient;
+        // 线梯度方向, 0 - 180度
+        public float GradientDegree;
+
+        public LineIdentified(int batch, Line line, float gradient, float gradientDegree)
+        {
+            Batch = batch;
+            Line = line;
+            Gradient = gradient;
+            GradientDegree = gradientDegree;
+        }
+
+        public LineIdentified(int batch, (Line line, float gradient, float gradientDegree) lineParams)
+        {
+            Batch = batch;
+            Line = lineParams.line;
+            Gradient = lineParams.gradient;
+            GradientDegree = lineParams.gradientDegree;
+        }
+
+        public void Offset(Vector offset)
+        {
+            Line += offset;
+        }
+    }
+}

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

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

+ 276 - 90
Assets/InfraredProject/WebCamera/Script/ZIM/InfraredLocate/ScreenIdentification.cs

@@ -1,13 +1,13 @@
 #define ENABLE_LOG
 
 using o0.Geometry2D.Float;
+using o0.Num;
 using System;
-using System.Collections;
 using System.Collections.Generic;
 using System.IO;
+using System.Linq;
 using System.Threading.Tasks;
 using UnityEngine;
-using UnityStandardAssets.ImageEffects;
 using ZIM;
 using ZIM.Unity;
 
@@ -90,11 +90,11 @@ namespace o0.Project
         // 自动识别开始的入口
         public void LocateScreen(int Capture = 45, int Delay = 45)  //数值单位是frame
         {
-            if (ScreenLocate.Main.DebugScreenImage != null && ScreenLocate.Main.DebugOnZIMDemo)     // 这段仅用于测试图片
+            if (ScreenLocate.Main.DebugScreenImages.Count != 0 && ScreenLocate.Main.DebugOnZIMDemo)     // 这段仅用于测试图片
             {
-                ScreenLocate.Main.CameraSize = new Geometry2D.Vector<int>(ScreenLocate.Main.DebugScreenImage.width, ScreenLocate.Main.DebugScreenImage.height);
-                DebugImage(ScreenLocate.Main.DebugScreenImage);
-                Screen.QuadInCamera = new QuadrilateralInCamera(quadTemp[0], new Vector(ScreenLocate.Main.DebugScreenImage.width, ScreenLocate.Main.DebugScreenImage.height));
+                ScreenLocate.Main.CameraSize = new Geometry2D.Vector<int>(ScreenLocate.Main.DebugScreenImages[0].width, ScreenLocate.Main.DebugScreenImages[0].height);
+                DebugImage(ScreenLocate.Main.DebugScreenImages);
+                Screen.QuadInCamera = new QuadrilateralInCamera(quadTemp[0], new Vector(ScreenLocate.Main.DebugScreenImages[0].width, ScreenLocate.Main.DebugScreenImages[0].height));
                 ScreenLocate.SetScreen(null);
                 ScreenLocate.Main.ShowScreen(ScreenLocate.Main.ScreenQuad, Screen.QuadInCamera);
                 delay = 0;
@@ -105,6 +105,7 @@ namespace o0.Project
                 areaSelected = -1;
                 quadTemp.Clear();
                 sumTemp.Clear();
+                ScreenLocate.Main.DebugScreenImages.Clear();
                 return;
             }
 
@@ -129,14 +130,9 @@ namespace o0.Project
             return capture != 0 && delay != 0;
         }
 
-        void DebugImage(Texture2D image)
+        void DebugImage(List<Texture2D> images)
         {
-            QuadrilateralFit(out Texture2D LocateLightedRedTex,out Texture2D ChoosableLineTex, out Texture2D ScreenQuadTex, 5, image);
-            ScreenLocate.DebugTexture(2, LocateLightedRedTex);
-            ScreenLocate.DebugTexture(3, ScreenQuadTex);
-            // 融合线段和原图
-            ScreenLocate.DebugTexture(4, image.Merge(ScreenQuadTex));
-            ScreenLocate.DebugTexture(5, ChoosableLineTex);
+            QuadrilateralFit(images, 5);
 
             //var watch = new System.Diagnostics.Stopwatch();
             //watch.Start();
@@ -147,32 +143,32 @@ namespace o0.Project
             if (quadTemp.Count > 0)
             {
                 var quad = quadTemp[0];
-                ScreenLocate.Main.ShowScreen(ScreenLocate.Main.outputRawImages[4].transform.GetChild(0) as RectTransform, 
-                    new QuadrilateralInCamera(quad, image.Size().o0Vector()));
+                ScreenLocate.Main.ShowScreen(ScreenLocate.Main.outputRawImages[4].transform.GetChild(0) as RectTransform,
+                    new QuadrilateralInCamera(quad, images[0].Size().o0Vector()));
 
                 // 透视变换
-                var srcWidth = LocateLightedRedTex.width;
-                var transformWidth = (int)((quad.B.x - quad.A.x + quad.D.x - quad.C.x) / 2);
-                var transformHeight = (int)((quad.C.y - quad.A.y + quad.D.y - quad.B.y) / 2);
-                var transformTex = new Texture2D(transformWidth, transformHeight);
-                var pt = new ZIMPerspectiveTransform(new OrdinalQuadrilateral(new Vector(0, 0), new Vector(transformWidth, 0), new Vector(0, transformHeight), new Vector(transformWidth, transformHeight)), quad);
-                var dstPixel = new UnityEngine.Color[transformWidth * transformHeight];
-                var srcPixel = LocateLightedRedTex.GetPixels();
-                Parallel.For(0, transformWidth, (x) =>
-                {
-                    for (int y = 0; y < transformHeight; y++)
-                    {
-                        var index = y * transformWidth + x;
-                        var sampleCoord = pt.TransformRound(x, y);
-                        dstPixel[index] = srcPixel[sampleCoord.y * srcWidth + sampleCoord.x];
-                    }
-                });
-                transformTex.SetPixels(dstPixel);
-                transformTex.Apply();
-                //ScreenLocate.DebugTexture(1, transformTex);
-#if (!NDEBUG && DEBUG && ENABLE_LOG)
-                Console.WriteLine($"{TAG} ScreenLocate.DebugTexture 1:{transformTex.GetNativeTexturePtr()}");
-#endif
+//                var srcWidth = LocateLightedRedTex.width;
+//                var transformWidth = (int)((quad.B.x - quad.A.x + quad.D.x - quad.C.x) / 2);
+//                var transformHeight = (int)((quad.C.y - quad.A.y + quad.D.y - quad.B.y) / 2);
+//                var transformTex = new Texture2D(transformWidth, transformHeight);
+//                var pt = new ZIMPerspectiveTransform(new OrdinalQuadrilateral(new Vector(0, 0), new Vector(transformWidth, 0), new Vector(0, transformHeight), new Vector(transformWidth, transformHeight)), quad);
+//                var dstPixel = new UnityEngine.Color[transformWidth * transformHeight];
+//                var srcPixel = LocateLightedRedTex.GetPixels();
+//                Parallel.For(0, transformWidth, (x) =>
+//                {
+//                    for (int y = 0; y < transformHeight; y++)
+//                    {
+//                        var index = y * transformWidth + x;
+//                        var sampleCoord = pt.TransformRound(x, y);
+//                        dstPixel[index] = srcPixel[sampleCoord.y * srcWidth + sampleCoord.x];
+//                    }
+//                });
+//                transformTex.SetPixels(dstPixel);
+//                transformTex.Apply();
+//                //ScreenLocate.DebugTexture(1, transformTex);
+//#if (!NDEBUG && DEBUG && ENABLE_LOG)
+//                Console.WriteLine($"{TAG} ScreenLocate.DebugTexture 1:{transformTex.GetNativeTexturePtr()}");
+//#endif
             }
 
             //times.Add(watch.ElapsedMilliseconds);
@@ -208,7 +204,7 @@ namespace o0.Project
 
         void Reset()
         {
-           // bStartLocateScreen = false;
+            // bStartLocateScreen = false;
             delay = 0;
             capture = 0;
             ScreenWhiteTexture = null;
@@ -276,17 +272,12 @@ namespace o0.Project
             }
             else if (locateIndex >= 4 && locateIndex < locateArea.Count - 1)
             {
-                QuadrilateralFit(out _, out _, out _);
+                QuadrilateralFit();
                 ScreenWhiteTexture = null;
             }
             else
             {
-                QuadrilateralFit(out Texture2D LocateLightedRedTex,out Texture2D ChoosableLineTex, out Texture2D ScreenQuadTex);
-                ScreenLocate.DebugTexture(2, LocateLightedRedTex);
-                ScreenLocate.DebugTexture(3, ScreenQuadTex);
-                // 融合线段和原图
-                ScreenLocate.DebugTexture(4, LocateLightedRedTex.Merge(ScreenQuadTex));
-                ScreenLocate.DebugTexture(5, ChoosableLineTex);
+                QuadrilateralFit();
 
                 if (quadTemp.Count != LocateAreaData[0].Length)
                 {
@@ -612,38 +603,22 @@ namespace o0.Project
             return sum;
         }
 
-        void QuadrilateralFit(out Texture2D LocateLightedRedTex,out Texture2D ChoosableLineTex, out Texture2D ScreenQuadTex, float lineWidth = 10, Texture2D debugImage = null)
+        // 转换成屏幕定位所需的纹理图像
+        Texture2D ToLocateTex(UnityEngine.Color[] pixels)
         {
-            UnityEngine.Color[] differPixel = new UnityEngine.Color[Size.x * Size.y];
-
-            //读取数据
-            if (debugImage != null)
-            {
-                Debug.Log($"<color=aqua>Debug {debugImage.name}</color>");
-                differPixel = debugImage.GetPixels();
-            }
-            else    // 获得屏幕差值
-            {
-                Parallel.For(0, Size.x * Size.y, i =>
-                {
-                    var pi = ScreenWhiteTexture[i] - ScreenBlackTexture[i];
-                    differPixel[i] = new UnityEngine.Color(pi.x, pi.y, pi.z);
-                });
-            }
-
             var ScreenLocateTex = new Texture2D(Size.x, Size.y);
-            ScreenLocateTex.SetPixels(differPixel);
+            ScreenLocateTex.SetPixels(pixels);
             ScreenLocateTex.Apply();
             //ScreenLocate.DebugTexture(2, ScreenLocateTex);
-            var ScreenLocateTexLighted = ScreenLocateTex.AutoLight(10);
+            return ScreenLocateTex.AutoLight(10);
 
             //ScreenLocate.DebugTexture(2, ScreenLocateTexLighted);
 
-            var ScreenLocateTexR = ScreenLocateTexLighted.ToRGB(ColorChannel.Red);
-            var ScreenLocateTexG = ScreenLocateTexLighted.ToRGB(ColorChannel.Green);
-            var ScreenLocateTexB = ScreenLocateTexLighted.ToRGB(ColorChannel.Blue);
+            //var ScreenLocateTexR = ToLocateTex.ToRGB(ColorChannel.Red);
+            //var ScreenLocateTexG = ToLocateTex.ToRGB(ColorChannel.Green);
+            //var ScreenLocateTexB = ToLocateTex.ToRGB(ColorChannel.Blue);
 
-            LocateLightedRedTex = ScreenLocateTexR;
+            //LocateLightedRedTex = ScreenLocateTexR;
             //ScreenLocate.DebugTexture(2, ScreenLocateTexR);
             //ScreenLocate.DebugTexture(4, ScreenLocateTexG);
             //ScreenLocate.DebugTexture(5, ScreenLocateTexB);
@@ -653,33 +628,74 @@ namespace o0.Project
             //watch.Start();
             //var times = new List<double>() { 0.0 };
 
+            //var ScreenLocateTexLightedMat = texture.Too0Mat();
+        }
 
+        /// <param name="ScreenLocateTexture">用于算法检测线段的图片</param>
+        /// <param name="ChoosableLineTex">输出备选线段</param>
+        /// <param name="ScreenQuadTex">输出最终结果</param>
+        /// <param name="lineWidth">识别的最小线段长度</param>
+        /// <param name="debugImage">这个参数如果不为null,则执行debug操作</param>
+        void QuadrilateralFit(List<Texture2D> debugImages = null, float lineWidth = 10)
+        {
+            // 屏幕黑白差值,存放多批次的图像用于识别, 该List数量不能等于 0 
+            List<UnityEngine.Color[]> PixelsMultipleBatches = new List<UnityEngine.Color[]>();
 
-            var ScreenLocateTexLightedMat = ScreenLocateTexLighted.Too0Mat();
-            //var ScreenLocateTexLightedMat = texture.Too0Mat();
+            //读取数据
+            if (debugImages != null && debugImages.Count != 0) 
+            {
+                foreach (var i in debugImages)
+                {
+                    Debug.Log($"<color=aqua>Debug {i.name}</color>");
+                    PixelsMultipleBatches.Add(i.GetPixels());
+                }
+
+            }
+            else    // 获得屏幕差值
+            {
+                PixelsMultipleBatches.Add(ScreenWhiteTexture.Select((i) => new UnityEngine.Color(i.x, i.y, i.z)).ToArray());
+                var differPixel = new UnityEngine.Color[Size.x * Size.y];
+                Parallel.For(0, Size.x * Size.y, i =>
+                {
+                    var pi = ScreenWhiteTexture[i] - ScreenBlackTexture[i];
+                    differPixel[i] = new UnityEngine.Color(pi.x, pi.y, pi.z);
+                });
+                PixelsMultipleBatches.Add(differPixel);
+            }
 
-            //var (edge, edgeDir) = ScreenLocateTexLightedMat.IdentifyEdge();
             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需要按比例缩小
-            Debug.Log($"[ScreenIdentification] Size: ({Size.x},{Size.y}), 卷积核Size: {conSize}, 最小线段长度: {minLength}");
-            var (edge, edgeDir) = ScreenLocateTexLightedMat.zimIdentifyEdgeGradientAny(conSize);
+            string log = $"[ScreenLocate Auto] Size: ({Size.x},{Size.y}), 卷积核Size: {conSize}, 最小线段长度: {minLength}";
+
+            var allLines = new List<LineIdentified>();
+            Texture2D ScreenLocateTexture = null;
+            List<Matrix> ScreenLocateMatList = new List<Matrix>();
+            foreach (var batch in PixelsMultipleBatches.Index())
+            {
+                ScreenLocateTexture = ToLocateTex(PixelsMultipleBatches[batch]);
+                var ScreenLocateMat = ScreenLocateTexture.Too0Mat();        // 用于获取Lines的Matrix
+                var lineCount = ZIMIdentifyQuadLSD(ref allLines, batch, ScreenLocateMat.zimIdentifyEdgeGradientAny(conSize));
+                log += $"\r\n识别图片{batch}, 识别到的线段数量为: {lineCount}";
+                ScreenLocateMatList.Add(ScreenLocateMat);
+            }
+
 
-            var quadLines = ScreenLocateTexLightedMat.ZIMIdentifyQuadLSD(
-                edge, edgeDir, 
-                out Line[] oldLines, out List<Line> possibleLines,out List<(Line, float, float)> allLines, 
-                Screen, conSize, conSize, minLength);
 
+            // 过滤得到四边形的四条边
+            var quadLines = FilterLines(ScreenLocateMatList, allLines, GetAvgPoint(ScreenLocateMatList[0]),
+                out Line[] oldLines, out List<Line> possibleLines,
+                Screen, conSize, conSize, minLength);
 
 
             // 将 allLines 输出一张图片
-            var allLinesMap = new Matrix(ScreenLocateTexLightedMat.Size, Tiling: true);
+            var allLinesMap = new Matrix(Size, Tiling: true);
             foreach (var l in allLines)
             {
-                if (l.Item1 != null)
-                    o0Extension.DrawLine(allLinesMap, l.Item1, (x, y) => 3, new Geometry2D.Float.Vector(0, 2), true);
+                if (l.Line != null)
+                    o0Extension.DrawLine(allLinesMap, l.Line, (x, y) => 3, new Geometry2D.Float.Vector(0, 2), true);
             }
             var allLinesTex = allLinesMap.ToTexRGBA(FloatValueToColor);
             ScreenLocate.DebugTexture(1, allLinesTex);
@@ -697,10 +713,10 @@ namespace o0.Project
                     LineIdentified.Add(oldLines[i]);
             }
 
-            var drawScreenMap = new Matrix(ScreenLocateTexLightedMat.Size, Tiling: true);
+            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));
-            ScreenQuadTex = drawScreenMap.ToTex();      // out ScreenQuadTex
+            Texture2D ScreenQuadTex = drawScreenMap.ToTex();      // out ScreenQuadTex
 
             QuadrilateralInCamera screenQuad = null;
             if (LineIdentified.Count == 4)
@@ -727,7 +743,7 @@ namespace o0.Project
 
 
             // 还需要输出一张识别结果图,包含干扰线段
-            var LSDLineMap = new Matrix(ScreenLocateTexLightedMat.Size, Tiling: true);
+            var LSDLineMap = new Matrix(Size, Tiling: true);
             foreach (var l in possibleLines)
             {
                 if (l != null && !quadLines.Contains(l))
@@ -743,17 +759,15 @@ namespace o0.Project
                 foreach (var l in oldLines)
                     o0Extension.DrawLine(LSDLineMap, l, (x, y) => 1, new Geometry2D.Float.Vector(0, 2), true);     // 旧的屏幕线段(例如上次手动识别的)
             }
-            ChoosableLineTex = LSDLineMap.ToTexRGBA(FloatValueToColor);
-
+            Texture2D ChoosableLineTex = LSDLineMap.ToTexRGBA(FloatValueToColor);
 
 
+            Debug.Log(log);
             // 是否将图片保存到本地
             if (ScreenLocate.Main.SaveToggle.isOn && ScreenLocate.Main.DebugOnZIMDemo)
             {
                 var FileDirectory = $"Debug_屏幕定位/";
-                SaveImages(FileDirectory,
-                    $"[ScreenLocate Auto] Size: ({Size.x},{Size.y}), 卷积核Size: {conSize}, 最小线段长度: {minLength}",
-                    ScreenLocateTex, allLinesTex, ScreenQuadTex);
+                SaveImages(FileDirectory, log, ScreenLocateTexture, allLinesTex, ScreenQuadTex);
             }
 
             //times.Add(watch.ElapsedMilliseconds);
@@ -769,7 +783,179 @@ namespace o0.Project
                 //ScreenLocate.DebugTexture(5, cvLines);
             }
 
-            UnityEngine.Object.Destroy(ScreenLocateTex);
+            {
+                ScreenLocate.DebugTexture(2, ScreenLocateTexture);
+                ScreenLocate.DebugTexture(3, ScreenQuadTex);
+                // 融合线段和原图
+                ScreenLocate.DebugTexture(4, ScreenLocateTexture.Merge(ScreenQuadTex));
+                ScreenLocate.DebugTexture(5, ChoosableLineTex);
+            }
+        }
+
+        Vector GetAvgPoint(Matrix screenLocateMat)
+        {
+            // 加权平均
+            Vector[] avgPointsColumn = new Vector[screenLocateMat.Size.x];
+            float[] valueSumsColumn = new float[screenLocateMat.Size.x];
+            Parallel.For(0, screenLocateMat.Size.x, i =>
+            {
+                for (int j = 0; j < screenLocateMat.Size.y; j++)
+                {
+                    var value = screenLocateMat[i, j];
+                    valueSumsColumn[i] += value;
+                    avgPointsColumn[i] += new Vector(i, j) * value;
+                }
+            });
+
+            Vector avgPoint = Vector.Zero;
+            var valueSum = 0f;
+            for (int i = 0; i < screenLocateMat.Size.x; i++)
+            {
+                avgPoint += avgPointsColumn[i];
+                valueSum += valueSumsColumn[i];
+            }
+            avgPoint /= valueSum;
+            return avgPoint;
+        }
+
+        // 返回查找到的线段数量,0是查找失败
+        int ZIMIdentifyQuadLSD(ref List<LineIdentified> allLines, int batch, (Matrix edgeMat, Matrix edgeDirMat) edgeGradient,
+            float minLength = 100)
+        {
+            var l = edgeGradient.edgeMat.IdentifyLineLSD(edgeGradient.edgeDirMat, minLength, 20, LineCaptureSize: new Vector(0, 5));
+            if (l == null || l.Count == 0)
+                return 0;
+            allLines.AddRange(l.Select((i) => new LineIdentified(batch, i)));
+            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)
+        {
+            //Debug.Log("[IdentifyLineLSD] lines.Count: " + lines.Count);
+            // LSD计算得到的矩阵尺寸较小(因为卷积),这里必须进行位移
+            var offset = new Vector((conSize - 1) / 2, (conSize - 1) / 2);
+            for (int i = 0; i < allLines.Count; i++)
+                allLines[i].Offset(offset);
+
+            // 沿直线计算综合梯度(梯度乘以长度系数,再乘以距离系数), distanceRatio是实际距离除以最大距离
+            float estimateGradient(LineIdentified line, float distanceRatio)
+            {
+                var dir = (line.Line.B - line.Line.A).Normalized;
+                var vertical = new Vector(-dir.y, dir.x) * (gradientLength / 2);
+                var step = 2;
+                var ll = line.Line.Length;
+                var lg = new List<float>();
+                for (int i = 0; i <= ll; i += step)
+                {
+                    var point = line.Line.A + dir * i;
+                    var ga = point + vertical;
+                    var gb = point - vertical;
+                    lg.Add(screenLocateMatList[line.Batch][(int)ga.x, (int)ga.y] - screenLocateMatList[line.Batch][(int)gb.x, (int)gb.y]);
+                }
+
+                float e = (float)Math.Sqrt(Math.Max(1, line.Line.Length / minLength / 3));       // 长度系数,筛选时梯度更大、长度更长的线段更优
+                float d = (3 - distanceRatio) / 2;            // 距离系数,距离越近,系数越大
+                return e * d * Math.Abs(lg.Mean());
+            }
+
+            // 下、右、上、左
+            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;
+
+            // 如果已有定位数据,根据现有数据筛选线条
+            if (screen.QuadInCamera != null)
+            {
+                Debug.Log("[IdentifyLineLSD] 根据已有定位数据做筛选");
+                screen.RefreshCameraSize(new Vector2(Size.x, Size.y));
+
+                var calibration = ScreenLocate.Main.ReDoLocateCalibrationRatio * Size.y;
+                oldLines = screen.QuadInCamera.GetLines();
+
+                var pedals = oldLines.Select((i) => o0Extension.PointPedal(i, avgPoint)).ToArray();     // 当前定位的垂足,下、右、上、左
+
+                foreach (var i in allLines)
+                {
+                    float minDistance = float.MaxValue;
+                    int index = -1;
+                    foreach (var j in pedals.Index())
+                    {
+                        var d = (o0Extension.PointPedal(i.Line, avgPoint) - pedals[j]).Length;
+                        if (d < minDistance)
+                        {
+                            minDistance = d;
+                            index = j;
+                        }
+                    }
+                    //Debug.Log(minDistance +", -----------"+ calibration);
+                    if (minDistance < calibration)      // 垂足的距离足够近
+                    {
+                        quadLines[index].Add((estimateGradient(i, minDistance / calibration), i.Line));
+                        possibleLines.Add(i.Line);
+                    }
+                }
+            }
+            else
+            {
+                var avaAngleHalf = 75f;
+                foreach (var line in allLines)
+                {
+                    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.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));
+                    }
+                }
+            }
+
+            var result = new Line[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;
+            }
+            return result.ToList();
         }
 
         void SaveImages(string FileDirectory, string log, Texture2D ScreenLocateTex, Texture2D allLinesTex, Texture2D ScreenQuadTex)

+ 9 - 16
Assets/InfraredProject/WebCamera/Script/ZIM/ScreenLocate.cs

@@ -110,7 +110,7 @@ public partial class ScreenLocate : MonoBehaviour
 
     public o0.Geometry2D.Vector<int> CameraSize { get; set; }
 
-    public Texture2D DebugScreenImage;
+    public List<Texture2D> DebugScreenImages = new List<Texture2D>();
     public bool DebugOnZIMDemo = false;
 
     // private SynchronizationContext mainContext;
@@ -174,7 +174,6 @@ public partial class ScreenLocate : MonoBehaviour
 
     static public void AutoLightPixels(Color[] pixels, int width, int height)
     {
-        if (Main.DebugOnZIMDemo) return;
         var newTex = pixels.zimAutoLightSimple(width, height);
         DebugTexture(7, newTex);
         try
@@ -186,10 +185,8 @@ public partial class ScreenLocate : MonoBehaviour
 
     static public void DebugTexture(int index, Texture texture)
     {
-        if (Main.DebugOnZIMDemo) return;
         LateDestory(Main.outputTexture2D[index]);
 
-        if (Main.outputTexture2D != null) 
         Main.outputTexture2D[index] = texture;
         try
         {
@@ -287,19 +284,16 @@ public partial class ScreenLocate : MonoBehaviour
 
         mode = Mode.InfraredLocate;
 
-        if (DebugScreenImage && DebugOnZIMDemo)
+        if (DebugScreenImages.Count != 0 && DebugOnZIMDemo)
         {
             screenIdentification = new o0.Project.ScreenIdentification();
             screenIdentification.LocateScreen();
-			
-			FullScreenToggle.onValueChanged.AddListener((i) =>
-	        {
-	            Screen.fullScreen = i;
-	        });
         }
 
- 
-
+        FullScreenToggle.onValueChanged.AddListener((i) =>
+        {
+            Screen.fullScreen = i;
+        });
 
         infraredCount = InfraredCount.Single;
 
@@ -438,7 +432,6 @@ public partial class ScreenLocate : MonoBehaviour
                 Debug.LogError("[ScreenLocate] RefreshCameraSize 屏幕size改变,存在NaN值,重新校准:" + PrintVector2List(quadUnityVectorList));
             }
          
-
             if (DebugOnZIMDemo)
                 Main.ShowScreen(Main.ScreenQuad, screenIdentification.Screen.QuadInCamera);
 
@@ -727,11 +720,11 @@ public partial class ScreenLocate : MonoBehaviour
 
     public void BtnScreenLocate()
     {
-        if (DebugScreenImage)
+        if (DebugScreenImages.Count != 0)
         {
             screenIdentification = new o0.Project.ScreenIdentification();
-            CameraSize = new o0.Geometry2D.Vector<int>(DebugScreenImage.width, DebugScreenImage.height);
-            WebCamIsReady(DebugScreenImage);
+            CameraSize = new o0.Geometry2D.Vector<int>(DebugScreenImages[0].width, DebugScreenImages[0].height);
+            WebCamIsReady(DebugScreenImages[0]);
 
             CreateUVCTexture2DIfNeeded();
 

+ 1 - 1
Assets/InfraredProject/WebCamera/Script/ZIM/o0Extension.meta

@@ -1,5 +1,5 @@
 fileFormatVersion: 2
-guid: 736eddcce8713dd4ab207b5de6fe919e
+guid: 9fc1e66a04f7d744aa313eb43d231d9b
 folderAsset: yes
 DefaultImporter:
   externalObjects: {}

+ 8 - 4
Assets/InfraredProject/WebCamera/zimWebCamera.unity

@@ -137,6 +137,10 @@ PrefabInstance:
       propertyPath: m_Name
       value: PixelCheaker
       objectReference: {fileID: 0}
+    - target: {fileID: 2705055563425357306, guid: ab30c51d9dbef7d4db660e2ae2075902, type: 3}
+      propertyPath: m_IsActive
+      value: 1
+      objectReference: {fileID: 0}
     - target: {fileID: 2705055563425357307, guid: ab30c51d9dbef7d4db660e2ae2075902, type: 3}
       propertyPath: m_Pivot.x
       value: 0.5
@@ -1545,7 +1549,7 @@ MonoBehaviour:
   onValueChanged:
     m_PersistentCalls:
       m_Calls: []
-  m_IsOn: 1
+  m_IsOn: 0
 --- !u!1 &614532336
 GameObject:
   m_ObjectHideFlags: 0
@@ -4855,8 +4859,8 @@ MonoBehaviour:
   m_Name: 
   m_EditorClassIdentifier: 
   cameraIndex: 2
-  width: 320
-  height: 240
+  width: 1280
+  height: 720
   fps: 30
   rawImage: {fileID: 2101632897}
   rawImageSize: {fileID: 681002959}
@@ -4899,7 +4903,7 @@ MonoBehaviour:
   FullScreenImage: {fileID: 1697954115}
   ScreenPixelCheaker: {fileID: 36100230}
   InfraredSpotSettings: {fileID: 11400000, guid: ca2f3b215f8d9d64caed905436a89b86, type: 2}
-  DebugScreenImage: {fileID: 0}
+  DebugScreenImages: []
   DebugOnZIMDemo: 1
   bSinglePoint: 1
   m_UITime: {fileID: 849339071}

+ 3 - 3
Assets/InfraredProject/o0/zimIdentifyLineLSD.cs

@@ -90,10 +90,10 @@ namespace o0.Project
             return (new Vector(B1, xToYFunc(B1)), Sum1);
         }
 
-        // 返回四边形的四条边,List长度一定是4 (如果没有识别到就是null),且线段顺序是: 下、右、上、左
+        // Old, 弃用的 ------------
         public static List<Line> ZIMIdentifyQuadLSD(this Matrix screenLocateMat, Matrix edgeMat, Matrix edgeDirMat,
             out Line[] oldLines, out List<Line> possibleLines, out List<(Line, float, float)> allLines, ScreenMap screen, 
-            float conSize, float gradientLength, float minLength = 100)
+            float gradientLength, float minLength = 100)
         {
             // 加权平均
             Vector[] avgPointsColumn = new Vector[screenLocateMat.Size.x];
@@ -121,7 +121,7 @@ namespace o0.Project
 
             //Debug.Log("[IdentifyLineLSD] lines.Count: " + lines.Count);
             // LSD计算得到的矩阵尺寸较小(因为卷积),这里必须进行位移
-            var offset = new Vector((conSize - 1) / 2, (conSize - 1) / 2);
+            var offset = new Vector((screenLocateMat.Size.x - edgeMat.Size.x) / 2, (screenLocateMat.Size.y - edgeMat.Size.y) / 2);
             for (int i = 0; i < allLines.Count; i++)
                 allLines[i] = (allLines[i].Item1 + offset, allLines[i].Item2, allLines[i].Item3);