ScreenIdentification.cs 48 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090
  1. #define ENABLE_LOG
  2. using o0.Geometry2D.Float;
  3. using o0.Num;
  4. using System;
  5. using System.Collections.Generic;
  6. using System.IO;
  7. using System.Linq;
  8. using System.Threading.Tasks;
  9. using UnityEngine;
  10. using UnityEngine.UIElements;
  11. using ZIM;
  12. using ZIM.Unity;
  13. namespace o0.Project
  14. {
  15. public partial class ScreenIdentification
  16. {
  17. private const string TAG = "ScreenIdentification#";
  18. // LocateAreaData表示每次屏幕的色差变化的区域,可能有多次。通过设置LocateSingleStep可调整为仅识别一次色差
  19. static Rect[][] LocateAreaData = new Rect[][] {
  20. new Rect[] { new Rect(0f, 0f, 0.3f, 0.3f), new Rect(0f, 0f, 0.4f, 0.4f), new Rect(0f, 0f, 0.5f, 0.5f) },
  21. new Rect[] { new Rect(0.7f, 0f, 0.3f, 0.3f), new Rect(0.6f, 0f, 0.4f, 0.4f), new Rect(0.5f, 0f, 0.5f, 0.5f) },
  22. new Rect[] { new Rect(0f, 0.7f, 0.3f, 0.3f), new Rect(0f, 0.6f, 0.4f, 0.4f), new Rect(0f, 0.5f, 0.5f, 0.5f) },
  23. new Rect[] { new Rect(0.7f, 0.7f, 0.3f, 0.3f), new Rect(0.6f, 0.6f, 0.4f, 0.4f), new Rect(0.5f, 0.5f, 0.5f, 0.5f) }
  24. };
  25. //static Rect[][] LocateAreaData = new Rect[][] {
  26. // new Rect[] { new Rect(0f, 0f, 0.3f, 0.3f), new Rect(0f, 0f, 0.4f, 0.4f), new Rect(0f, 0f, 0.5f, 0.5f), new Rect(0f, 0f, 0.6f, 0.6f) },
  27. // new Rect[] { new Rect(0.7f, 0f, 0.3f, 0.3f), new Rect(0.6f, 0f, 0.4f, 0.4f), new Rect(0.5f, 0f, 0.5f, 0.5f), new Rect(0.4f, 0f, 0.6f, 0.6f) },
  28. // new Rect[] { new Rect(0f, 0.7f, 0.3f, 0.3f), new Rect(0f, 0.6f, 0.4f, 0.4f), new Rect(0f, 0.5f, 0.5f, 0.5f), new Rect(0f, 0.4f, 0.6f, 0.6f) },
  29. // new Rect[] { new Rect(0.7f, 0.7f, 0.3f, 0.3f), new Rect(0.6f, 0.6f, 0.4f, 0.4f), new Rect(0.5f, 0.5f, 0.5f, 0.5f), new Rect(0.4f, 0.4f, 0.6f, 0.6f) }
  30. //};
  31. //static bool LocateSingleStep = false;
  32. static bool LocateSingleStep = true;
  33. public Geometry2D.Vector<int> Size => ScreenLocate.Main.CameraSize;
  34. public QuadrilateralInCamera QuadManual;
  35. public QuadrilateralInCamera QuadAuto; // 全自动,可以给用户选择(赋值给Screen.QuadInCamera即生效)
  36. public QuadrilateralInCamera QuadSemiAuto; // 半自动,可以给用户选择(赋值给Screen.QuadInCamera即生效)
  37. public ScreenMap Screen; // 识别到的屏幕,用于执行透视变换
  38. int capture = 0;
  39. int delay = 0;
  40. int maxCapture;
  41. int maxDelay;
  42. Geometry.Vector<float>[] ScreenBlackTexture;
  43. Geometry.Vector<float>[] ScreenWhiteTexture;
  44. int locateIndex = -1;
  45. readonly List<Rect> locateArea = new List<Rect> {
  46. new Rect(0f, 0f, 0.5f, 0.5f), new Rect(0.5f, 0f, 0.5f, 0.5f), new Rect(0f, 0.5f, 0.5f, 0.5f), new Rect(0.5f, 0.5f, 0.5f, 0.5f)
  47. }; // 屏幕显示白色的区域大小
  48. float areaPercent => locateArea[locateIndex].size.x; // 当前白色区域的占比
  49. int areaSelected = -1; // 选择哪个区域,顺序与Quadrilateral对应
  50. readonly List<float> sumTemp = new List<float>();
  51. readonly List<OrdinalQuadrilateral> quadTemp = new List<OrdinalQuadrilateral>();
  52. //public ScreenIdentification(WebCamTexture texture)
  53. //{
  54. // Size = new Geometry2D.Vector<int>(texture.width, texture.height);
  55. // Screen = new ScreenMap();
  56. //}
  57. public static UnityEngine.Color FloatValueToColor(float i)
  58. {
  59. return i switch
  60. {
  61. 1 => UnityEngine.Color.green,
  62. 2 => UnityEngine.Color.red,
  63. 3 => UnityEngine.Color.yellow,
  64. 4 => UnityEngine.Color.white,
  65. _ => UnityEngine.Color.black,
  66. };
  67. }
  68. public ScreenIdentification()
  69. {
  70. Screen = new ScreenMap();
  71. OnLocateScreenEnter += () => Application.targetFrameRate = 30; // 固定识别的帧率,确保摄像机拍到正确的画面
  72. OnLocateScreenEnd += () => Application.targetFrameRate = 60;
  73. }
  74. public void SetScreenQuad(QuadrilateralInCamera quad) => Screen.QuadInCamera = quad;
  75. public event Action OnLocateScreenEnter;
  76. public event Action OnLocateScreenEnd;
  77. public bool bStartLocateScreen { get; set; } = false;//是否进行捕获
  78. public bool SelectScreenAfterLocate(ScreenLocate.ScreenIdentificationTag tag)
  79. {
  80. QuadrilateralInCamera target = tag switch
  81. {
  82. ScreenLocate.ScreenIdentificationTag.Manual => QuadManual,
  83. ScreenLocate.ScreenIdentificationTag.SemiAuto => QuadSemiAuto,
  84. ScreenLocate.ScreenIdentificationTag.Auto => QuadAuto,
  85. _ => null
  86. };
  87. if (target == null)
  88. return false;
  89. SetScreenQuad(target);
  90. return true;
  91. }
  92. // 自动识别开始的入口
  93. public void LocateScreen(int Capture = 30, int Delay = 30) //数值单位是frame
  94. {
  95. if (ScreenLocate.Main.DebugScreenImages.Count != 0 && ScreenLocate.Main.DebugOnZIMDemo) // 这段仅用于测试图片
  96. {
  97. ScreenLocate.Main.CameraSize = new Geometry2D.Vector<int>(ScreenLocate.Main.DebugScreenImages[0].width, ScreenLocate.Main.DebugScreenImages[0].height);
  98. DebugImage(ScreenLocate.Main.DebugScreenImages);
  99. Screen.QuadInCamera = new QuadrilateralInCamera(quadTemp[0], new Vector(ScreenLocate.Main.DebugScreenImages[0].width, ScreenLocate.Main.DebugScreenImages[0].height));
  100. ScreenLocate.SetScreen(null);
  101. ScreenLocate.Main.ShowScreen(ScreenLocate.Main.ScreenQuad, Screen.QuadInCamera);
  102. delay = 0;
  103. capture = 0;
  104. ScreenWhiteTexture = null;
  105. ScreenBlackTexture = null;
  106. locateIndex = -1;
  107. areaSelected = -1;
  108. quadTemp.Clear();
  109. sumTemp.Clear();
  110. ScreenLocate.Main.DebugScreenImages.Clear();
  111. return;
  112. }
  113. delay = Math.Max(Delay, 5);
  114. capture = Math.Max(Capture, 5);
  115. maxDelay = Delay;
  116. maxCapture = Capture;
  117. ScreenLocate.SetScreen(new Rect(0f, 0f, 1f, 1f), UnityEngine.Color.black);
  118. //ScreenLocate.SetScreen(new Rect(0f, 0f, 0.6f, 0.6f), UnityEngine.Color.white);
  119. //bStartLocateScreen = false;
  120. OnLocateScreenEnter?.Invoke();
  121. }
  122. /// <summary>
  123. /// 开始进行捕获
  124. /// 初始化了两个数据 capture 和 delay
  125. /// </summary>
  126. /// <returns></returns>
  127. public bool isInitLocateScreen() {
  128. return capture != 0 && delay != 0;
  129. }
  130. void DebugImage(List<Texture2D> images)
  131. {
  132. QuadrilateralFit(images);
  133. //var watch = new System.Diagnostics.Stopwatch();
  134. //watch.Start();
  135. //var times = new List<double>() { 0.0 };
  136. #if (!NDEBUG && DEBUG && ENABLE_LOG)
  137. Console.WriteLine($"{TAG} quadTemp.Count:{quadTemp.Count}");
  138. #endif
  139. if (quadTemp.Count > 0)
  140. {
  141. var quad = quadTemp[0];
  142. ScreenLocate.Main.ShowScreen(ScreenLocate.Main.outputRawImages[4].transform.GetChild(0) as RectTransform,
  143. new QuadrilateralInCamera(quad, images[0].Size().o0Vector()));
  144. // 透视变换
  145. // var srcWidth = LocateLightedRedTex.width;
  146. // var transformWidth = (int)((quad.B.x - quad.A.x + quad.D.x - quad.C.x) / 2);
  147. // var transformHeight = (int)((quad.C.y - quad.A.y + quad.D.y - quad.B.y) / 2);
  148. // var transformTex = new Texture2D(transformWidth, transformHeight);
  149. // var pt = new ZIMPerspectiveTransform(new OrdinalQuadrilateral(new Vector(0, 0), new Vector(transformWidth, 0), new Vector(0, transformHeight), new Vector(transformWidth, transformHeight)), quad);
  150. // var dstPixel = new UnityEngine.Color[transformWidth * transformHeight];
  151. // var srcPixel = LocateLightedRedTex.GetPixels();
  152. // Parallel.For(0, transformWidth, (x) =>
  153. // {
  154. // for (int y = 0; y < transformHeight; y++)
  155. // {
  156. // var index = y * transformWidth + x;
  157. // var sampleCoord = pt.TransformRound(x, y);
  158. // dstPixel[index] = srcPixel[sampleCoord.y * srcWidth + sampleCoord.x];
  159. // }
  160. // });
  161. // transformTex.SetPixels(dstPixel);
  162. // transformTex.Apply();
  163. // //ScreenLocate.DebugTexture(1, transformTex);
  164. //#if (!NDEBUG && DEBUG && ENABLE_LOG)
  165. // Console.WriteLine($"{TAG} ScreenLocate.DebugTexture 1:{transformTex.GetNativeTexturePtr()}");
  166. //#endif
  167. }
  168. //times.Add(watch.ElapsedMilliseconds);
  169. //UnityEngine.Debug.Log("time: " + (times[times.Count - 1] - times[times.Count - 2]));
  170. }
  171. public void NextScreen()
  172. {
  173. // 只识别一次色差变化
  174. if (LocateSingleStep && areaSelected == -1)
  175. {
  176. LocateAreaData = new Rect[][] { new Rect[] { new Rect(0, 0, 1f, 1f) } };
  177. locateIndex = 3;
  178. areaSelected = 0;
  179. locateArea.AddRange(LocateAreaData[0]);
  180. }
  181. // index从-1开始
  182. locateIndex++;
  183. if (locateIndex < locateArea.Count) // 依次点亮屏幕区域
  184. {
  185. ScreenLocate.SetScreen(locateArea[locateIndex], UnityEngine.Color.white);
  186. delay = maxDelay;
  187. capture = maxCapture;
  188. }
  189. else // 退出屏幕黑白控制
  190. {
  191. ScreenLocate.SetScreen(null);
  192. ScreenLocate.Main.ShowScreen(ScreenLocate.Main.ScreenQuad, Screen.QuadInCamera);
  193. Reset();
  194. }
  195. }
  196. // 清除记录的屏幕识别数据(手动、自动等)
  197. public void ClearQuadCache()
  198. {
  199. SetScreenQuad(null);
  200. QuadManual = null;
  201. QuadSemiAuto = null;
  202. QuadAuto = null;
  203. }
  204. public void Reset()
  205. {
  206. // bStartLocateScreen = false;
  207. delay = 0;
  208. capture = 0;
  209. ScreenWhiteTexture = null;
  210. ScreenBlackTexture = null;
  211. locateIndex = -1;
  212. areaSelected = -1;
  213. if (locateArea.Count > 4)
  214. locateArea.RemoveRange(4, LocateAreaData[0].Length);
  215. quadTemp.Clear();
  216. sumTemp.Clear();
  217. }
  218. public void CaptureBlack(Texture2D cam)
  219. {
  220. if (ScreenBlackTexture == null)
  221. ScreenBlackTexture = new Geometry.Vector<float>[Size.x * Size.y];
  222. var pixel = cam.GetPixels();
  223. Parallel.For(0, Size.x * Size.y, i =>
  224. {
  225. var ip = pixel[i];
  226. ScreenBlackTexture[i] += new Geometry.Vector<float>(ip.r / maxCapture, ip.g / maxCapture, ip.b / maxCapture);
  227. });
  228. }
  229. public void CaptureWhite(Texture2D cam)
  230. {
  231. if (ScreenWhiteTexture == null)
  232. ScreenWhiteTexture = new Geometry.Vector<float>[Size.x * Size.y];
  233. var pixel = cam.GetPixels();
  234. Parallel.For(0, Size.x * Size.y, i =>
  235. {
  236. var ip = pixel[i];
  237. ScreenWhiteTexture[i] += new Geometry.Vector<float>(ip.r / maxCapture, ip.g / maxCapture, ip.b / maxCapture);
  238. });
  239. }
  240. public void CaptureStay(Texture2D cam)
  241. {
  242. if (locateIndex == -1) // 屏幕黑色
  243. {
  244. CaptureBlack(cam);
  245. }
  246. else // 屏幕部分为白色
  247. {
  248. CaptureWhite(cam);
  249. }
  250. }
  251. public void CaptureEnd()
  252. {
  253. //Debug.Log("locateIndex: " + locateIndex + ", quad: " + quadTemp.Count);
  254. if (locateIndex == -1)
  255. return;
  256. if (locateIndex < 4)
  257. {
  258. sumTemp.Add(GetBrightness());
  259. ScreenWhiteTexture = null;
  260. // 选择亮度差最大的区域
  261. if (locateIndex == 3)
  262. {
  263. areaSelected = sumTemp.MaxIndex();
  264. locateArea.AddRange(LocateAreaData[areaSelected]);
  265. }
  266. }
  267. else if (locateIndex >= 4 && locateIndex < locateArea.Count - 1)
  268. {
  269. QuadrilateralFit();
  270. ScreenWhiteTexture = null;
  271. }
  272. else
  273. {
  274. QuadrilateralFit();
  275. if (quadTemp.Count != LocateAreaData[0].Length)
  276. {
  277. Debug.Log($"<color=yellow>[ScreenIdentification] 拟合四边形失败, quadTemp.Count: {quadTemp.Count}</color>");
  278. PopupMgr.ins.ShowTipTop(TextAutoLanguage2.GetTextByKey("FittingFailed"));
  279. }
  280. else if (quadTemp.Count == 1)
  281. {
  282. Screen.QuadInCamera = new QuadrilateralInCamera(quadTemp[0], new Vector(Size.x, Size.y));
  283. Debug.Log($"<color=aqua>[ScreenIdentification] 拟合成功,Quad: {Screen.QuadInCamera.QuadString}____{Screen.QuadInCamera.SizeString}</color>");
  284. }
  285. else
  286. {
  287. // Debug.Log($"拟合四边形 2 , quadTemp.Count: {quadTemp.Count}");
  288. // 线性拟合
  289. var xValue = new List<float>() { 0 };
  290. var predicts = new List<Vector>();
  291. foreach (var i in LocateAreaData[0])
  292. xValue.Add(i.size.x);
  293. Vector baseVertex = Vector.Zero; // x==0 时的点
  294. {
  295. foreach (var q in quadTemp)
  296. {
  297. baseVertex += q[areaSelected];
  298. }
  299. baseVertex /= quadTemp.Count;
  300. }
  301. double rs = 0.0;
  302. for (int i = 0; i < 4; i++)
  303. {
  304. if (i == areaSelected)
  305. {
  306. predicts.Add(baseVertex);
  307. }
  308. else
  309. {
  310. var yValue = new List<Vector>() { baseVertex };
  311. foreach (var q in quadTemp)
  312. {
  313. yValue.Add(q[i]);
  314. }
  315. var lr = LinerRegression1D.Fit(2, xValue.ToArray(), yValue.ToArray());
  316. rs += lr.RSquared / 3;
  317. predicts.Add(lr.Predict<Vector>(1));
  318. }
  319. }
  320. Screen.QuadInCamera = new QuadrilateralInCamera(predicts, new Vector(Size.x, Size.y));
  321. Debug.Log($"<color=aqua>[ScreenIdentification] 拟合成功,RSquared: {rs}, Quad: {Screen.QuadInCamera.QuadString}____{Screen.QuadInCamera.SizeString}</color>");
  322. //if (rs < 0.8) Screen.Quad = null;
  323. }
  324. OnLocateScreenEnd?.Invoke();
  325. }
  326. }
  327. public bool Update(Texture2D cam)
  328. {
  329. //if (!bStartLocateScreen) return false;
  330. if (delay != 0)
  331. {
  332. //ScreenLocate.Main.CreateUVCTexture2DFocusSizeIfNeeded(1280, 720);
  333. delay--;
  334. if (delay == 0)
  335. {
  336. ScreenLocate.Main.CameraSize = new Geometry2D.Vector<int>(cam.width, cam.height); // 记录当前的分辨率
  337. Debug.Log("[ScreenIdentification] 采样纹理,分辨率: [" + Size.x + ", " + Size.y + "]");
  338. }
  339. return true;
  340. }
  341. if (capture != 0)
  342. {
  343. //ScreenLocate.Main.CreateUVCTexture2DFocusSizeIfNeeded(1280, 720);
  344. CaptureStay(cam);
  345. capture--;
  346. if (capture == 0)
  347. {
  348. CaptureEnd();
  349. NextScreen();
  350. }
  351. return true;
  352. }
  353. return false;
  354. #region Old
  355. /*
  356. if (delay != 0)
  357. {
  358. delay--;
  359. return true;
  360. }
  361. if (capture != 0)
  362. {
  363. capture--;
  364. if (ScreenBlackTexture == null)
  365. ScreenBlackTexture = new Geometry.Vector<float>[Size.x * Size.y];
  366. var pixel = cam.GetPixels();
  367. Parallel.For(0, Size.x * Size.y, i =>
  368. {
  369. var ip = pixel[i];
  370. ScreenBlackTexture[i] += new Geometry.Vector<float>(ip.r, ip.g, ip.b);
  371. });
  372. if (capture == 0)
  373. ScreenLocate.SetScreen(UnityEngine.Color.black);
  374. return true;
  375. }
  376. if (delay != 0)
  377. {
  378. delay--;
  379. return true;
  380. }
  381. if (capture != 0)
  382. {
  383. capture--;
  384. if (ScreenWhiteTexture == null)
  385. ScreenWhiteTexture = new Geometry.Vector<float>[Size.x * Size.y];
  386. var pixel = cam.GetPixels();
  387. Parallel.For(0, Size.x * Size.y, i =>
  388. {
  389. var ip = pixel[i];
  390. ScreenWhiteTexture[i] += new Geometry.Vector<float>(ip.r, ip.g, ip.b);
  391. });
  392. if (capture == 0)
  393. ScreenLocate.SetScreen(UnityEngine.Color.black);
  394. return true;
  395. }
  396. if (delay != 0)
  397. {
  398. delay--;
  399. return true;
  400. }
  401. if (capture != 0)
  402. {
  403. capture--;
  404. var pixel = cam.GetPixels();
  405. Parallel.For(0, Size.x * Size.y, i =>
  406. {
  407. var ip = pixel[i];
  408. ScreenWhiteTexture[i] -= new Geometry.Vector<float>(ip.r, ip.g, ip.b);
  409. });
  410. if (capture == 0)
  411. {
  412. ScreenLocate.SetScreen(null);
  413. UnityEngine.Color[] newPixel = new UnityEngine.Color[Size.x * Size.y];
  414. Parallel.For(0, Size.x * Size.y, i => {
  415. var pi = ScreenWhiteTexture[i] /= capture;
  416. newPixel[i] = new UnityEngine.Color(pi.x, pi.y, pi.z);
  417. });
  418. //读取数据
  419. //{
  420. // var fileName = "3.bin";
  421. // ScreenLocateTexture = $"2023 04 16 厦门测试数据/{fileName}".FileReadByte<Vector<float>[]>();
  422. // Debug.Log($"Read {fileName}");
  423. // Parallel.For(0, Size.x * Size.y, i =>
  424. // {
  425. // var pi = ScreenLocateTexture[i];
  426. // newPixel[i] = new UnityEngine.Color(pi.x, pi.y, pi.z);
  427. // });
  428. //}
  429. var ScreenLocateTex = new Texture2D(Size.x, Size.y);
  430. ScreenLocateTex.SetPixels(newPixel);
  431. ScreenLocateTex.Apply();
  432. //ScreenLocate.DebugTexture(2, ScreenLocateTex);
  433. var ScreenLocateTexLighted = ScreenLocateTex.AutoLight(10);
  434. //ScreenLocate.DebugTexture(2, ScreenLocateTexLighted);
  435. //var FileSavePath = Application.persistentDataPath + "/ScreenLocateTexture.bin";
  436. bool Save = ScreenLocate.Main.SaveToggle.isOn;
  437. string time;
  438. if (Save)
  439. {
  440. time = DateTime.Now.ToString("yyyyMMdd_HHmmss");
  441. var FileSavePath = $"屏幕定位数据{time}.bin";
  442. FileSavePath.FileWriteByte(ScreenWhiteTexture);
  443. var bytes = ScreenLocateTexLighted.EncodeToPNG();
  444. File.WriteAllBytes($"屏幕定位数据{time}.png", bytes);
  445. Debug.Log("ScreenLocateTexture Saved To: " + FileSavePath);
  446. }
  447. var ScreenLocateTexR = ScreenLocateTexLighted.ToRGB(ColorChannel.Red);
  448. var ScreenLocateTexG = ScreenLocateTexLighted.ToRGB(ColorChannel.Green);
  449. var ScreenLocateTexB = ScreenLocateTexLighted.ToRGB(ColorChannel.Blue);
  450. ScreenLocate.DebugTexture(2, ScreenLocateTexR);
  451. //ScreenLocate.DebugTexture(4, ScreenLocateTexG);
  452. //ScreenLocate.DebugTexture(5, ScreenLocateTexB);
  453. var watch = new System.Diagnostics.Stopwatch();
  454. watch.Start();
  455. var times = new List<double>() { 0.0 };
  456. var ScreenLocateTexLightedMat = ScreenLocateTexLighted.Too0Mat();
  457. //var ScreenLocateTexLightedMat = texture.Too0Mat();
  458. //var (edge, edgeDir) = ScreenLocateTexLightedMat.IdentifyEdge();
  459. var (edge, edgeDir) = ScreenLocateTexLightedMat.zimIdentifyEdgeGradientAny(15);
  460. //ScreenLocate.DebugTexture(4, ScreenLocateTexLighted.Too0Mat().IdentifyEdgeGradient().ToTex());
  461. //ScreenLocate.DebugTexture(4, edge.ToTex());
  462. var quadLines = ScreenLocateTexLightedMat.IdentifyQuadLSD(edge, edgeDir, out List<Line> lightLines, 30);
  463. var drawLineMap = new MatrixF2D(edge..Size.x, edge.Size.y);
  464. int lineCount = 0;
  465. foreach (var l in quadLines)
  466. {
  467. if (l != null)
  468. {
  469. o0Extension.DrawLine(drawLineMap.DrawLine(l, (x, y) => 1, new Geometry2D.Float.Vector(0, 10));
  470. lineCount++;
  471. }
  472. }
  473. if (lineCount == 4)
  474. {
  475. var a = quadLines[0].Intersect(quadLines[3], false).Value;
  476. var b = quadLines[0].Intersect(quadLines[1], false).Value;
  477. var c = quadLines[2].Intersect(quadLines[3], false).Value;
  478. var d = quadLines[1].Intersect(quadLines[2], false).Value;
  479. Quad = new Quadrilateral(a, b, c, d);
  480. if (!Quad.IsInScreen(ScreenLocate.Main.WebCamera.Size))
  481. Quad = null;
  482. }
  483. ScreenLocate.Main.ShowScreen(Quad);
  484. //var lines = edge.IdentifyLineLSD(edgeDir, 100);
  485. ////var lines = ScreenLocateTexLightedMat.IdentifyLineLSD();
  486. //var drawLineMap = new MatrixF2D(edge..Size.x, edge.Size.y);
  487. //var returnMaxLines = lines.Sub(0, 10);
  488. //foreach (var (line, sum, gradient) in returnMaxLines)
  489. // o0Extension.DrawLine(drawLineMap.DrawLine(line, (x, y) => 1, new Geometry2D.Float.Vector(0, 10));
  490. ScreenLocate.DebugTexture(3, drawLineMap.ToTex());
  491. //{
  492. // var bytes = drawLineMap.ToTex().EncodeToPNG();
  493. // File.WriteAllBytes($"屏幕定位数据DrawLineMap.png", bytes);
  494. //}
  495. times.Add(watch.ElapsedMilliseconds);
  496. UnityEngine.Debug.Log("time: " + (times[times.Count - 1] - times[times.Count - 2]));
  497. //ScreenLocate.DebugTexture(5, edge.IdentifyLine(edgeDir).ToTex());
  498. //ScreenLocate.DebugTexture(4, ScreenLocateTexLighted.Too0Mat().IdentifyEdgeGradientX().ToTex());
  499. //ScreenLocate.DebugTexture(5, ScreenLocateTexLighted.Too0Mat().IdentifyEdgeGradientY().ToTex());
  500. //var convolutionLighted2 = ScreenLocateTexLighted.Too0Mat().IdentifyEdgeVariance().ToTex();
  501. // opecncv处理
  502. // zim
  503. {
  504. //var cvLines = edge.cvHoughLinesP();
  505. //ScreenLocate.DebugTexture(5, cvLines);
  506. //var myLines = Hough.Transform(edgeMat);
  507. //var cvLines = edge.cvLine(myLines);
  508. //ScreenLocate.DebugTexture(5, cvLines);
  509. }
  510. UnityEngine.Object.Destroy(ScreenLocateTex);
  511. //ScreenLocate.DebugTexture(4, convolutionLighted2);
  512. }
  513. return true;
  514. }
  515. /*
  516. var avg = new Geometry4D.Vector<float>();
  517. var pixel = texture.GetPixels();
  518. foreach(var i in pixel.Index())
  519. {
  520. var iP = pixel[i];
  521. avg += new Geometry4D.Vector<float>(iP.r, iP.g, iP.b, iP.a);
  522. }
  523. avg /= pixel.Count();
  524. /*
  525. var (texLightedR, texLightedG, texLightedB) = ToRGB(newTex);
  526. ScreenLocate.DebugTexture(3, texLightedR);
  527. ScreenLocate.DebugTexture(4, texLightedG);
  528. ScreenLocate.DebugTexture(5, texLightedB);
  529. //Debug.Log(avg);
  530. return false;
  531. /**/
  532. #endregion
  533. }
  534. float GetBrightness()
  535. {
  536. UnityEngine.Color[] differPixel = new UnityEngine.Color[Size.x * Size.y];
  537. Parallel.For(0, Size.x * Size.y, i =>
  538. {
  539. var pi = ScreenWhiteTexture[i] - ScreenBlackTexture[i];
  540. differPixel[i] = new UnityEngine.Color(pi.x, pi.y, pi.z);
  541. });
  542. var sum = 0f;
  543. foreach (var i in differPixel)
  544. {
  545. sum += i.Brightness();
  546. }
  547. sum /= differPixel.Length;
  548. //Debug.Log(sum);
  549. return sum;
  550. }
  551. // 转换成屏幕定位所需的纹理图像
  552. Texture2D ToLocateTex(UnityEngine.Color[] pixels)
  553. {
  554. var ScreenLocateTex = new Texture2D(Size.x, Size.y);
  555. ScreenLocateTex.SetPixels(pixels);
  556. ScreenLocateTex.Apply();
  557. //ScreenLocate.DebugTexture(2, ScreenLocateTex);
  558. return ScreenLocateTex.AutoLight(10);
  559. //ScreenLocate.DebugTexture(2, ScreenLocateTexLighted);
  560. //var ScreenLocateTexR = ToLocateTex.ToRGB(ColorChannel.Red);
  561. //var ScreenLocateTexG = ToLocateTex.ToRGB(ColorChannel.Green);
  562. //var ScreenLocateTexB = ToLocateTex.ToRGB(ColorChannel.Blue);
  563. //LocateLightedRedTex = ScreenLocateTexR;
  564. //ScreenLocate.DebugTexture(2, ScreenLocateTexR);
  565. //ScreenLocate.DebugTexture(4, ScreenLocateTexG);
  566. //ScreenLocate.DebugTexture(5, ScreenLocateTexB);
  567. //var ScreenLocateTexLightedMat = texture.Too0Mat();
  568. }
  569. /// <param name="lineWidth">识别的最小线段长度</param>
  570. /// <param name="debugImages">这个参数如果不为null且数量大于0,则执行debug操作</param>
  571. void QuadrilateralFit(List<Texture2D> debugImages = null)
  572. {
  573. // 如果有旧的手动数据,刷新一下Size
  574. QuadManual?.ReSize(new Vector(Size.x, Size.y), ScreenMap.ViewAspectRatioSetting);
  575. // 屏幕黑白差值,存放多批次的图像用于识别, 该List数量不能等于 0
  576. List<UnityEngine.Color[]> PixelsMultipleBatches = new List<UnityEngine.Color[]>();
  577. var sw = new System.Diagnostics.Stopwatch();
  578. sw.Start();
  579. //读取数据
  580. if (debugImages != null && debugImages.Count != 0)
  581. {
  582. foreach (var i in debugImages)
  583. {
  584. Debug.Log($"<color=aqua>Debug {i.name}</color>");
  585. PixelsMultipleBatches.Add(i.GetPixels());
  586. }
  587. }
  588. else // 获得屏幕差值
  589. {
  590. var maxWhite = 0f;
  591. foreach (var i in ScreenWhiteTexture)
  592. {
  593. var m = i.x > i.y ? (i.x > i.z ? i.x : i.z) : (i.y > i.z ? i.y : i.z);
  594. if (maxWhite < m)
  595. maxWhite = m;
  596. }
  597. var scale = 1.0f / maxWhite; // 放大对比度
  598. var differPixel = new UnityEngine.Color[Size.x * Size.y];
  599. var whitePixel = new UnityEngine.Color[Size.x * Size.y];
  600. Parallel.For(0, Size.x, x =>
  601. {
  602. for (int y = 0; y < Size.y; y++)
  603. {
  604. var i = y * Size.x + x;
  605. var d = ScreenWhiteTexture[i] - ScreenBlackTexture[i];
  606. differPixel[i] = new UnityEngine.Color(d.x, d.y, d.z) * scale;
  607. whitePixel[i] = new UnityEngine.Color(ScreenWhiteTexture[i].x, ScreenWhiteTexture[i].y, ScreenWhiteTexture[i].z) * scale;
  608. }
  609. });
  610. PixelsMultipleBatches.Add(differPixel);
  611. PixelsMultipleBatches.Add(whitePixel);
  612. }
  613. int conSize = (int)Math.Ceiling(0.007f * Size.y) * 2 + 1;
  614. conSize = Math.Max(conSize, 7); // 设置最小为7
  615. float minLength = conSize * 7.7f;
  616. minLength = locateIndex == -1 ? minLength : minLength * areaPercent; // minLength需要按比例缩小
  617. string log = $"[Log][ScreenLocate Auto] Size: ({Size.x},{Size.y}), 卷积核Size: {conSize}, 最小线段长度: {minLength}";
  618. var allLines = new List<LineIdentified>();
  619. List<Texture2D> LocateTexTemp = new List<Texture2D>();
  620. List<Matrix> ScreenLocateMatList = new List<Matrix>();
  621. foreach (var batch in PixelsMultipleBatches.Index())
  622. {
  623. var locateTex = ToLocateTex(PixelsMultipleBatches[batch]);
  624. LocateTexTemp.Add(locateTex);
  625. var ScreenLocateMat = locateTex.Too0Mat(); // 用于获取Lines的Matrix
  626. var lineCount = ZIMIdentifyQuadLSD(ref allLines, batch, ScreenLocateMat.zimIdentifyEdgeGradientAny(conSize), minLength);
  627. log += $"\r\n识别图片{batch}, 识别到的线段数量为: {lineCount}";
  628. ScreenLocateMatList.Add(ScreenLocateMat);
  629. }
  630. Texture2D ScreenLocateTexture = LocateTexTemp[0]; // for output
  631. // 估算屏幕中点,如果已有手动定位数据,根据现有数据取平均即可,否则从色差计算,ScreenLocateMatList[0]默认是屏幕的黑白色差
  632. Vector AvgPoint = QuadManual != null ? QuadManual.Quad.Centroid : GetAvgPoint(ScreenLocateMatList[0]);
  633. // 过滤得到四边形的四条边,
  634. var (quadLinesSemiAuto, quadLinesAuto) = FilterLines(ScreenLocateMatList, allLines, AvgPoint,
  635. out LineIdentified[] manualLines, out List<LineIdentified> possibleLines,
  636. conSize, conSize, minLength);
  637. #region 全自动识别的结果
  638. List<LineIdentified> LineIdentifiedAuto = new List<LineIdentified>(); // 线段顺序: 下、右、上、左
  639. for (int i = 0; i < 4; i++)
  640. {
  641. if (quadLinesAuto[i] != null)
  642. LineIdentifiedAuto.Add(quadLinesAuto[i]);
  643. }
  644. if (LineIdentifiedAuto.Count == 4) // 判断识别的线段能否拼成屏幕,能拼成则记录
  645. {
  646. var a = LineIdentifiedAuto[0].Line.Intersect(LineIdentifiedAuto[3].Line, false).Value;
  647. var b = LineIdentifiedAuto[0].Line.Intersect(LineIdentifiedAuto[1].Line, false).Value;
  648. var c = LineIdentifiedAuto[2].Line.Intersect(LineIdentifiedAuto[3].Line, false).Value;
  649. var d = LineIdentifiedAuto[1].Line.Intersect(LineIdentifiedAuto[2].Line, false).Value;
  650. QuadAuto = new QuadrilateralInCamera(a, b, c, d, new Vector(Size.x, Size.y));
  651. if (!QuadAuto.IsQuadComplete())
  652. QuadAuto = null;
  653. }
  654. #endregion
  655. #region 半自动识别
  656. List<LineIdentified> LineIdentifiedSemiAuto = new List<LineIdentified>(); // 线段顺序: 下、右、上、左
  657. bool[] newLines = new bool[4] { true, true, true, true };
  658. for (int i = 0; i < 4; i++)
  659. {
  660. if (quadLinesSemiAuto[i] != null)
  661. LineIdentifiedSemiAuto.Add(quadLinesSemiAuto[i]);
  662. else if (manualLines != null)
  663. {
  664. LineIdentifiedSemiAuto.Add(manualLines[i]);
  665. newLines[i] = false;
  666. }
  667. }
  668. if (LineIdentifiedSemiAuto.Count == 4) // 判断识别的线段能否拼成屏幕,能拼成则记录
  669. {
  670. var a = LineIdentifiedSemiAuto[0].Line.Intersect(LineIdentifiedSemiAuto[3].Line, false).Value;
  671. var b = LineIdentifiedSemiAuto[0].Line.Intersect(LineIdentifiedSemiAuto[1].Line, false).Value;
  672. var c = LineIdentifiedSemiAuto[2].Line.Intersect(LineIdentifiedSemiAuto[3].Line, false).Value;
  673. var d = LineIdentifiedSemiAuto[1].Line.Intersect(LineIdentifiedSemiAuto[2].Line, false).Value;
  674. QuadSemiAuto = new QuadrilateralInCamera(a, b, c, d, new Vector(Size.x, Size.y));
  675. if (!QuadSemiAuto.IsQuadComplete())
  676. QuadSemiAuto = null;
  677. }
  678. #endregion
  679. // 优先应用半自动的结果(也可以在外部手动设置)
  680. if (QuadSemiAuto == null && QuadAuto == null && Screen.QuadInCamera != null) // 如果可能,回退到上一个screen
  681. {
  682. Debug.Log("<color=aqua>[ScreenIdentification] 本次识别失败,回退到上次的识别结果</color>");
  683. quadTemp.Add(Screen.QuadInCamera.Quad);
  684. }
  685. else if (QuadSemiAuto != null)
  686. {
  687. Debug.Log("<color=aqua>[ScreenIdentification] 识别到四边形</color>");
  688. quadTemp.Add(QuadSemiAuto.Quad);
  689. }else if (QuadAuto != null)
  690. {
  691. Debug.Log("<color=aqua>[ScreenIdentification] 识别到四边形</color>");
  692. quadTemp.Add(QuadAuto.Quad);
  693. }
  694. #region 绘制 output texture
  695. // 绘制半自动
  696. var ScreenQuadMap = new Matrix(Size, Tiling: true); // 识别的到的屏幕四边形(半自动和自动在一张图上)
  697. foreach (var i in LineIdentifiedSemiAuto.Index())
  698. {
  699. if (newLines[i])
  700. o0Extension.DrawLine(ScreenQuadMap, LineIdentifiedSemiAuto[i].DrawLine, (x, y) => 2, new Geometry2D.Float.Vector(0, 10));
  701. else
  702. o0Extension.DrawLine(ScreenQuadMap, LineIdentifiedSemiAuto[i].DrawLine, (x, y) => 1, new Geometry2D.Float.Vector(0, 6), true);
  703. }
  704. // 绘制全自动
  705. foreach (var i in LineIdentifiedAuto.Index())
  706. o0Extension.DrawLine(ScreenQuadMap, LineIdentifiedAuto[i].DrawLine, (x, y) => 4, new Geometry2D.Float.Vector(0, 4), true);
  707. Texture2D ScreenQuad = ScreenQuadMap.ToTexRGBA(FloatValueToColor);
  708. Texture2D ScreenQuadWithScreen = ScreenQuad.Overlay(ScreenLocateTexture); // 叠加屏幕色差图
  709. // 绘制allLines
  710. var allLinesMap = new Matrix(Size, Tiling: true);
  711. foreach (var l in allLines)
  712. {
  713. if (l.DrawLine != null)
  714. o0Extension.DrawLine(allLinesMap, l.DrawLine, (x, y) => 3, new Geometry2D.Float.Vector(0, 2), true);
  715. }
  716. var allLinesTex = allLinesMap.ToTexRGBA(FloatValueToColor);
  717. ScreenLocate.DebugTexture(1, allLinesTex);
  718. // 还需要输出一张识别结果图,包含干扰线段
  719. var ChoosableLineMap = new Matrix(Size, Tiling: true);
  720. foreach (var l in possibleLines)
  721. {
  722. if (l != null && !quadLinesSemiAuto.Contains(l) && !manualLines.Contains(l))
  723. o0Extension.DrawLine(ChoosableLineMap, l.DrawLine, (x, y) => 3, new Geometry2D.Float.Vector(0, 2), true); // 其他的备选线段
  724. }
  725. foreach (var l in LineIdentifiedSemiAuto)
  726. {
  727. if (l != null)
  728. o0Extension.DrawLine(ChoosableLineMap, l.DrawLine, (x, y) => 2, new Geometry2D.Float.Vector(0, 5)); // 识别的结果
  729. }
  730. if (manualLines != null)
  731. {
  732. foreach (var l in manualLines)
  733. o0Extension.DrawLine(ChoosableLineMap, l.DrawLine, (x, y) => 1, new Geometry2D.Float.Vector(0, 2), true); // 旧的屏幕线段(例如上次手动识别的)
  734. }
  735. Texture2D ChoosableLineTex = ChoosableLineMap.ToTexRGBA(FloatValueToColor);
  736. #endregion
  737. log += $"\r\n屏幕四边形_手动识别{QuadManual != null}\r\n屏幕四边形_半自动识别{QuadSemiAuto != null}\r\n屏幕四边形_全自动识别{QuadAuto != null}";
  738. Debug.Log(log);
  739. // 是否将图片保存到本地
  740. if (ScreenLocate.Main.SaveToggle.isOn && ScreenLocate.Main.DebugOnZIMDemo)
  741. {
  742. var FileDirectory = $"Debug_屏幕定位/";
  743. SaveImages(FileDirectory, log, ScreenLocateTexture, allLinesTex, ChoosableLineTex, ScreenQuad);
  744. }
  745. //times.Add(watch.ElapsedMilliseconds);
  746. //UnityEngine.Debug.Log("time: " + (times[times.Count - 1] - times[times.Count - 2]));
  747. // opecncv处理, zim
  748. {
  749. //var cvLines = edge.cvHoughLinesP();
  750. //ScreenLocate.DebugTexture(5, cvLines);
  751. //var myLines = Hough.Transform(edgeMat);
  752. //var cvLines = edge.cvLine(myLines);
  753. //ScreenLocate.DebugTexture(5, cvLines);
  754. }
  755. {
  756. ScreenLocate.DebugTexture(2, ScreenLocateTexture);
  757. ScreenLocate.DebugTexture(3, ScreenQuad);
  758. ScreenLocate.DebugTexture(4, ScreenQuadWithScreen);
  759. ScreenLocate.DebugTexture(5, ChoosableLineTex);
  760. }
  761. foreach (var i in LocateTexTemp)
  762. {
  763. if (i != ScreenLocateTexture) // ScreenLocateTexture 由 ScreenLocate.DebugTexture 释放
  764. GameObject.Destroy(i);
  765. }
  766. }
  767. Vector GetAvgPoint(Matrix screenLocateMat)
  768. {
  769. // 加权平均
  770. Vector[] avgPointsColumn = new Vector[screenLocateMat.Size.x];
  771. float[] valueSumsColumn = new float[screenLocateMat.Size.x];
  772. Parallel.For(0, screenLocateMat.Size.x, i =>
  773. {
  774. for (int j = 0; j < screenLocateMat.Size.y; j++)
  775. {
  776. var value = screenLocateMat[i, j];
  777. valueSumsColumn[i] += value;
  778. avgPointsColumn[i] += new Vector(i, j) * value;
  779. }
  780. });
  781. Vector avgPoint = Vector.Zero;
  782. var valueSum = 0f;
  783. for (int i = 0; i < screenLocateMat.Size.x; i++)
  784. {
  785. avgPoint += avgPointsColumn[i];
  786. valueSum += valueSumsColumn[i];
  787. }
  788. avgPoint /= valueSum;
  789. return avgPoint;
  790. }
  791. // 返回查找到的线段数量,0是查找失败
  792. int ZIMIdentifyQuadLSD(ref List<LineIdentified> allLines, int batch, (Matrix edgeMat, Matrix edgeDirMat) edgeGradient,
  793. float minLength = 100)
  794. {
  795. var l = edgeGradient.edgeMat.IdentifyLineLSD(edgeGradient.edgeDirMat, minLength, 50, LineCaptureSize: new Vector(10, 6));
  796. if (l == null || l.Count == 0)
  797. return 0;
  798. allLines.AddRange(l.Select((i) => new LineIdentified(batch, i)));
  799. return l.Count;
  800. }
  801. // 返回四边形的四条边(半自动、全自动),List长度一定是4 (如果没有识别到就是null),且线段顺序是: 下、右、上、左
  802. (List<LineIdentified>, List<LineIdentified>) FilterLines(List<Matrix> screenLocateMatList, List<LineIdentified> allLines, Vector avgPoint,
  803. out LineIdentified[] manualLines, out List<LineIdentified> possibleLines, float conSize, float gradientLength, float minLength = 100)
  804. {
  805. //Debug.Log("[IdentifyLineLSD] lines.Count: " + lines.Count);
  806. var offset = new Vector((conSize - 1) / 2, (conSize - 1) / 2);
  807. // LSD计算得到的矩阵尺寸较小(因为卷积),这里必须进行位移
  808. for (int i = 0; i < allLines.Count; i++)
  809. allLines[i].Offset(offset);
  810. // 筛掉椭圆框外的线段(超出一半会筛掉)
  811. var innerLines = new List<LineIdentified>();
  812. for (int i = 0; i < allLines.Count; i++)
  813. {
  814. List<Vector> InArea = new List<Vector>();
  815. var dir = (allLines[i].Line.B - allLines[i].Line.A) / 4;
  816. var points = new Vector[5] { allLines[i].Line.A, allLines[i].Line.A + dir, allLines[i].Line.A + dir * 2f, allLines[i].Line.A + dir * 3f, allLines[i].Line.B }; // A点、中间的点、B点
  817. for (int pI = 0; pI < points.Length; pI++)
  818. {
  819. if (!ScreenLocate.Main.ScreenPixelCheaker.OutArea2D(points[pI], Size))
  820. InArea.Add(points[pI]);
  821. }
  822. if (InArea.Count < 2) // 少于2个点在内部
  823. continue;
  824. else if (InArea.Count < points.Length) // 不完全在内部
  825. allLines[i].DrawLine = new Line(InArea.First(), InArea.Last()); // 将部分线条设置为drawline,用于下一步的绘制
  826. else // 线段全部在椭圆内
  827. allLines[i].DrawLine = allLines[i].Line;
  828. innerLines.Add(allLines[i]);
  829. }
  830. // 角度阈值,用来判断线段的梯度方向是否指向屏幕中心(avgPoint)
  831. var avaAngleHalf = 75f;
  832. // 沿直线计算综合梯度(梯度乘以长度系数,再乘以距离系数), distanceRatio是实际距离除以最大距离
  833. float estimateGradient(LineIdentified line, float distanceRatio)
  834. {
  835. var dir = (line.Line.B - line.Line.A).Normalized;
  836. var vertical = new Vector(-dir.y, dir.x) * (gradientLength / 2);
  837. var step = 2;
  838. var ll = line.Line.Length;
  839. var lg = new List<float>();
  840. for (int i = 0; i <= ll; i += step)
  841. {
  842. var point = line.Line.A + dir * i;
  843. var ga = point + vertical;
  844. var gb = point - vertical;
  845. lg.Add(screenLocateMatList[line.Batch][(int)ga.x, (int)ga.y] - screenLocateMatList[line.Batch][(int)gb.x, (int)gb.y]);
  846. }
  847. float e = (float)Math.Sqrt(Math.Ceiling(line.Line.Length / minLength)); // 长度系数,筛选时梯度更大、长度更长的线段更优
  848. float d = (3 - distanceRatio) / 2; // 距离系数,距离越近,系数越大
  849. return e * d * Math.Abs(lg.Mean());
  850. }
  851. // 根据线段梯度的角度,判断是不是屏幕的边,out index代表是哪条边(顺序是: 下、右、上、左)
  852. bool isScreenLine(LineIdentified line, out int index)
  853. {
  854. var a = (avgPoint - (line.Line.A + line.Line.B) / 2).DegreeToXAxis();
  855. //Debug.Log(a + ", " + gradient + ", " + sum);
  856. index = -1;
  857. if (Math.Abs(a - line.GradientDegree) < avaAngleHalf || Math.Abs(a - 360 - line.GradientDegree) < avaAngleHalf || Math.Abs(a + 360 - line.GradientDegree) < avaAngleHalf)
  858. {
  859. if (line.GradientDegree > 45 && line.GradientDegree < 135) // 下
  860. index = 0;
  861. else if (line.GradientDegree > 135 && line.GradientDegree < 225) // 右
  862. index = 1;
  863. else if (line.GradientDegree > 225 && line.GradientDegree < 315) // 上
  864. index = 2;
  865. else
  866. index = 3;
  867. return true;
  868. }
  869. return false;
  870. }
  871. // 下、右、上、左, 半自动和自动
  872. var quadLinesSemiAuto = new List<(float, LineIdentified)>[4] { new List<(float, LineIdentified)>(), new List<(float, LineIdentified)>(), new List<(float, LineIdentified)>(), new List<(float, LineIdentified)>() };
  873. var quadLinesAuto = new List<(float, LineIdentified)>[4] { new List<(float, LineIdentified)>(), new List<(float, LineIdentified)>(), new List<(float, LineIdentified)>(), new List<(float, LineIdentified)>() };
  874. possibleLines = new List<LineIdentified>();
  875. manualLines = null;
  876. // 如果已有手动定位数据,根据现有数据筛选线条(半自动)
  877. if (QuadManual != null)
  878. {
  879. Debug.Log("[IdentifyLineLSD] 根据已有定位数据做筛选");
  880. var calibration = ScreenLocate.Main.ReDoLocateCalibrationRatio * Size.y;
  881. manualLines = QuadManual.GetLines().Select((i) => new LineIdentified(0, i, 0, 0, true)).ToArray();
  882. var pedals = manualLines.Select((i) => o0Extension.PointPedal(i.Line, avgPoint, out _)).ToArray(); // 当前定位的垂足,下、右、上、左
  883. foreach (var line in innerLines)
  884. {
  885. // 筛选条件:1-梯度方向匹配,2-垂足的距离足够近, 3-新的线段的中点,到旧线段的垂足,要在旧线段内
  886. if (isScreenLine(line, out int index))
  887. {
  888. var distanceToOld = (o0Extension.PointPedal(line.Line, avgPoint, out _) - pedals[index]).Length;
  889. if (distanceToOld < calibration)
  890. {
  891. var middleToOldLine = o0Extension.PointPedal(manualLines[index].Line, (line.Line.A + line.Line.B) / 2, out bool inLineSegment);
  892. if (inLineSegment)
  893. {
  894. quadLinesSemiAuto[index].Add((estimateGradient(line, distanceToOld / calibration), line));
  895. possibleLines.Add(line);
  896. }
  897. }
  898. }
  899. }
  900. }
  901. // 全自动
  902. foreach (var line in allLines)
  903. {
  904. if (isScreenLine(line , out int index))
  905. {
  906. if (line.Batch < 1) // 全自动只处理第一张图,默认是色差图
  907. {
  908. quadLinesAuto[index].Add((estimateGradient(line, 1), line));
  909. }
  910. }
  911. }
  912. var resultSemiAuto = new LineIdentified[4];
  913. var resultAuto = new LineIdentified[4];
  914. for (int i = 0; i < 4; i++)
  915. {
  916. if (quadLinesSemiAuto[i].Count > 0)
  917. resultSemiAuto[i] = quadLinesSemiAuto[i].Max((a, b) => a.Item1.CompareTo(b.Item1)).Item2;
  918. if (quadLinesAuto[i].Count > 0)
  919. resultAuto[i] = quadLinesAuto[i].Max((a, b) => a.Item1.CompareTo(b.Item1)).Item2;
  920. }
  921. return (resultSemiAuto.ToList(), resultAuto.ToList());
  922. }
  923. void SaveImages(string FileDirectory, string log,
  924. Texture2D ScreenLocateTex, Texture2D allLinesTex, Texture2D ChoosableLineTex, Texture2D ScreenQuadTex)
  925. {
  926. if (!Directory.Exists(FileDirectory))
  927. Directory.CreateDirectory(FileDirectory);
  928. var time = DateTime.Now.ToString("yyyyMMdd_HHmmss");
  929. var pngData = ScreenLocate.Main.OutputTextures[7]?.EncodeToPNG();
  930. if (pngData != null)
  931. File.WriteAllBytes($"{FileDirectory}{time}A屏幕原图.png", pngData);
  932. var pngData1 = ScreenLocateTex?.EncodeToPNG();
  933. if (pngData1 != null)
  934. File.WriteAllBytes($"{FileDirectory}{time}B黑白色差.png", pngData1);
  935. var pngData2 = allLinesTex?.EncodeToPNG();
  936. if (pngData2 != null)
  937. File.WriteAllBytes($"{FileDirectory}{time}C全部识别线段_半自动.png", pngData2);
  938. var pngData3 = ChoosableLineTex?.EncodeToPNG();
  939. if (pngData3 != null)
  940. File.WriteAllBytes($"{FileDirectory}{time}D备选线段_半自动.png", pngData3);
  941. var pngData4 = ScreenQuadTex?.EncodeToPNG();
  942. if (pngData4 != null)
  943. File.WriteAllBytes($"{FileDirectory}{time}E识别结果.png", pngData4);
  944. Debug.Log($"<color=aqua>({time}) 屏幕识别图片保存至:程序根目录/{FileDirectory}</color>");
  945. log +=
  946. $"\r\n屏幕原图保存{pngData != null}, " +
  947. $"\r\n黑白色差保存{pngData1 != null}, " +
  948. $"\r\n全部识别线段(半自动)保存{pngData2 != null}, " +
  949. $"\r\n备选线段(半自动)保存{pngData3 != null}, " +
  950. $"\r\n识别结果保存{pngData4 != null}";
  951. File.WriteAllText($"{FileDirectory}{time}屏幕自动定位_日志.log", log);
  952. }
  953. }
  954. }