ScreenIdentification.cs 54 KB

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