ScreenIdentification.cs 48 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088
  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. }
  279. else if (quadTemp.Count == 1)
  280. {
  281. Screen.QuadInCamera = new QuadrilateralInCamera(quadTemp[0], new Vector(Size.x, Size.y));
  282. Debug.Log($"<color=aqua>[ScreenIdentification] 拟合成功,Quad: {Screen.QuadInCamera.QuadString}____{Screen.QuadInCamera.SizeString}</color>");
  283. }
  284. else
  285. {
  286. // Debug.Log($"拟合四边形 2 , quadTemp.Count: {quadTemp.Count}");
  287. // 线性拟合
  288. var xValue = new List<float>() { 0 };
  289. var predicts = new List<Vector>();
  290. foreach (var i in LocateAreaData[0])
  291. xValue.Add(i.size.x);
  292. Vector baseVertex = Vector.Zero; // x==0 时的点
  293. {
  294. foreach (var q in quadTemp)
  295. {
  296. baseVertex += q[areaSelected];
  297. }
  298. baseVertex /= quadTemp.Count;
  299. }
  300. double rs = 0.0;
  301. for (int i = 0; i < 4; i++)
  302. {
  303. if (i == areaSelected)
  304. {
  305. predicts.Add(baseVertex);
  306. }
  307. else
  308. {
  309. var yValue = new List<Vector>() { baseVertex };
  310. foreach (var q in quadTemp)
  311. {
  312. yValue.Add(q[i]);
  313. }
  314. var lr = LinerRegression1D.Fit(2, xValue.ToArray(), yValue.ToArray());
  315. rs += lr.RSquared / 3;
  316. predicts.Add(lr.Predict<Vector>(1));
  317. }
  318. }
  319. Screen.QuadInCamera = new QuadrilateralInCamera(predicts, new Vector(Size.x, Size.y));
  320. Debug.Log($"<color=aqua>[ScreenIdentification] 拟合成功,RSquared: {rs}, Quad: {Screen.QuadInCamera.QuadString}____{Screen.QuadInCamera.SizeString}</color>");
  321. //if (rs < 0.8) Screen.Quad = null;
  322. }
  323. OnLocateScreenEnd?.Invoke();
  324. }
  325. }
  326. public bool Update(Texture2D cam)
  327. {
  328. //if (!bStartLocateScreen) return false;
  329. if (delay != 0)
  330. {
  331. //ScreenLocate.Main.CreateUVCTexture2DFocusSizeIfNeeded(1280, 720);
  332. delay--;
  333. if (delay == 0)
  334. {
  335. ScreenLocate.Main.CameraSize = new Geometry2D.Vector<int>(cam.width, cam.height); // 记录当前的分辨率
  336. Debug.Log("[ScreenIdentification] 采样纹理,分辨率: [" + Size.x + ", " + Size.y + "]");
  337. }
  338. return true;
  339. }
  340. if (capture != 0)
  341. {
  342. //ScreenLocate.Main.CreateUVCTexture2DFocusSizeIfNeeded(1280, 720);
  343. CaptureStay(cam);
  344. capture--;
  345. if (capture == 0)
  346. {
  347. CaptureEnd();
  348. NextScreen();
  349. }
  350. return true;
  351. }
  352. return false;
  353. #region Old
  354. /*
  355. if (delay != 0)
  356. {
  357. delay--;
  358. return true;
  359. }
  360. if (capture != 0)
  361. {
  362. capture--;
  363. if (ScreenBlackTexture == null)
  364. ScreenBlackTexture = new Geometry.Vector<float>[Size.x * Size.y];
  365. var pixel = cam.GetPixels();
  366. Parallel.For(0, Size.x * Size.y, i =>
  367. {
  368. var ip = pixel[i];
  369. ScreenBlackTexture[i] += new Geometry.Vector<float>(ip.r, ip.g, ip.b);
  370. });
  371. if (capture == 0)
  372. ScreenLocate.SetScreen(UnityEngine.Color.black);
  373. return true;
  374. }
  375. if (delay != 0)
  376. {
  377. delay--;
  378. return true;
  379. }
  380. if (capture != 0)
  381. {
  382. capture--;
  383. if (ScreenWhiteTexture == null)
  384. ScreenWhiteTexture = new Geometry.Vector<float>[Size.x * Size.y];
  385. var pixel = cam.GetPixels();
  386. Parallel.For(0, Size.x * Size.y, i =>
  387. {
  388. var ip = pixel[i];
  389. ScreenWhiteTexture[i] += new Geometry.Vector<float>(ip.r, ip.g, ip.b);
  390. });
  391. if (capture == 0)
  392. ScreenLocate.SetScreen(UnityEngine.Color.black);
  393. return true;
  394. }
  395. if (delay != 0)
  396. {
  397. delay--;
  398. return true;
  399. }
  400. if (capture != 0)
  401. {
  402. capture--;
  403. var pixel = cam.GetPixels();
  404. Parallel.For(0, Size.x * Size.y, i =>
  405. {
  406. var ip = pixel[i];
  407. ScreenWhiteTexture[i] -= new Geometry.Vector<float>(ip.r, ip.g, ip.b);
  408. });
  409. if (capture == 0)
  410. {
  411. ScreenLocate.SetScreen(null);
  412. UnityEngine.Color[] newPixel = new UnityEngine.Color[Size.x * Size.y];
  413. Parallel.For(0, Size.x * Size.y, i => {
  414. var pi = ScreenWhiteTexture[i] /= capture;
  415. newPixel[i] = new UnityEngine.Color(pi.x, pi.y, pi.z);
  416. });
  417. //读取数据
  418. //{
  419. // var fileName = "3.bin";
  420. // ScreenLocateTexture = $"2023 04 16 厦门测试数据/{fileName}".FileReadByte<Vector<float>[]>();
  421. // Debug.Log($"Read {fileName}");
  422. // Parallel.For(0, Size.x * Size.y, i =>
  423. // {
  424. // var pi = ScreenLocateTexture[i];
  425. // newPixel[i] = new UnityEngine.Color(pi.x, pi.y, pi.z);
  426. // });
  427. //}
  428. var ScreenLocateTex = new Texture2D(Size.x, Size.y);
  429. ScreenLocateTex.SetPixels(newPixel);
  430. ScreenLocateTex.Apply();
  431. //ScreenLocate.DebugTexture(2, ScreenLocateTex);
  432. var ScreenLocateTexLighted = ScreenLocateTex.AutoLight(10);
  433. //ScreenLocate.DebugTexture(2, ScreenLocateTexLighted);
  434. //var FileSavePath = Application.persistentDataPath + "/ScreenLocateTexture.bin";
  435. bool Save = ScreenLocate.Main.SaveToggle.isOn;
  436. string time;
  437. if (Save)
  438. {
  439. time = DateTime.Now.ToString("yyyyMMdd_HHmmss");
  440. var FileSavePath = $"屏幕定位数据{time}.bin";
  441. FileSavePath.FileWriteByte(ScreenWhiteTexture);
  442. var bytes = ScreenLocateTexLighted.EncodeToPNG();
  443. File.WriteAllBytes($"屏幕定位数据{time}.png", bytes);
  444. Debug.Log("ScreenLocateTexture Saved To: " + FileSavePath);
  445. }
  446. var ScreenLocateTexR = ScreenLocateTexLighted.ToRGB(ColorChannel.Red);
  447. var ScreenLocateTexG = ScreenLocateTexLighted.ToRGB(ColorChannel.Green);
  448. var ScreenLocateTexB = ScreenLocateTexLighted.ToRGB(ColorChannel.Blue);
  449. ScreenLocate.DebugTexture(2, ScreenLocateTexR);
  450. //ScreenLocate.DebugTexture(4, ScreenLocateTexG);
  451. //ScreenLocate.DebugTexture(5, ScreenLocateTexB);
  452. var watch = new System.Diagnostics.Stopwatch();
  453. watch.Start();
  454. var times = new List<double>() { 0.0 };
  455. var ScreenLocateTexLightedMat = ScreenLocateTexLighted.Too0Mat();
  456. //var ScreenLocateTexLightedMat = texture.Too0Mat();
  457. //var (edge, edgeDir) = ScreenLocateTexLightedMat.IdentifyEdge();
  458. var (edge, edgeDir) = ScreenLocateTexLightedMat.zimIdentifyEdgeGradientAny(15);
  459. //ScreenLocate.DebugTexture(4, ScreenLocateTexLighted.Too0Mat().IdentifyEdgeGradient().ToTex());
  460. //ScreenLocate.DebugTexture(4, edge.ToTex());
  461. var quadLines = ScreenLocateTexLightedMat.IdentifyQuadLSD(edge, edgeDir, out List<Line> lightLines, 30);
  462. var drawLineMap = new MatrixF2D(edge..Size.x, edge.Size.y);
  463. int lineCount = 0;
  464. foreach (var l in quadLines)
  465. {
  466. if (l != null)
  467. {
  468. o0Extension.DrawLine(drawLineMap.DrawLine(l, (x, y) => 1, new Geometry2D.Float.Vector(0, 10));
  469. lineCount++;
  470. }
  471. }
  472. if (lineCount == 4)
  473. {
  474. var a = quadLines[0].Intersect(quadLines[3], false).Value;
  475. var b = quadLines[0].Intersect(quadLines[1], false).Value;
  476. var c = quadLines[2].Intersect(quadLines[3], false).Value;
  477. var d = quadLines[1].Intersect(quadLines[2], false).Value;
  478. Quad = new Quadrilateral(a, b, c, d);
  479. if (!Quad.IsInScreen(ScreenLocate.Main.WebCamera.Size))
  480. Quad = null;
  481. }
  482. ScreenLocate.Main.ShowScreen(Quad);
  483. //var lines = edge.IdentifyLineLSD(edgeDir, 100);
  484. ////var lines = ScreenLocateTexLightedMat.IdentifyLineLSD();
  485. //var drawLineMap = new MatrixF2D(edge..Size.x, edge.Size.y);
  486. //var returnMaxLines = lines.Sub(0, 10);
  487. //foreach (var (line, sum, gradient) in returnMaxLines)
  488. // o0Extension.DrawLine(drawLineMap.DrawLine(line, (x, y) => 1, new Geometry2D.Float.Vector(0, 10));
  489. ScreenLocate.DebugTexture(3, drawLineMap.ToTex());
  490. //{
  491. // var bytes = drawLineMap.ToTex().EncodeToPNG();
  492. // File.WriteAllBytes($"屏幕定位数据DrawLineMap.png", bytes);
  493. //}
  494. times.Add(watch.ElapsedMilliseconds);
  495. UnityEngine.Debug.Log("time: " + (times[times.Count - 1] - times[times.Count - 2]));
  496. //ScreenLocate.DebugTexture(5, edge.IdentifyLine(edgeDir).ToTex());
  497. //ScreenLocate.DebugTexture(4, ScreenLocateTexLighted.Too0Mat().IdentifyEdgeGradientX().ToTex());
  498. //ScreenLocate.DebugTexture(5, ScreenLocateTexLighted.Too0Mat().IdentifyEdgeGradientY().ToTex());
  499. //var convolutionLighted2 = ScreenLocateTexLighted.Too0Mat().IdentifyEdgeVariance().ToTex();
  500. // opecncv处理
  501. // zim
  502. {
  503. //var cvLines = edge.cvHoughLinesP();
  504. //ScreenLocate.DebugTexture(5, cvLines);
  505. //var myLines = Hough.Transform(edgeMat);
  506. //var cvLines = edge.cvLine(myLines);
  507. //ScreenLocate.DebugTexture(5, cvLines);
  508. }
  509. UnityEngine.Object.Destroy(ScreenLocateTex);
  510. //ScreenLocate.DebugTexture(4, convolutionLighted2);
  511. }
  512. return true;
  513. }
  514. /*
  515. var avg = new Geometry4D.Vector<float>();
  516. var pixel = texture.GetPixels();
  517. foreach(var i in pixel.Index())
  518. {
  519. var iP = pixel[i];
  520. avg += new Geometry4D.Vector<float>(iP.r, iP.g, iP.b, iP.a);
  521. }
  522. avg /= pixel.Count();
  523. /*
  524. var (texLightedR, texLightedG, texLightedB) = ToRGB(newTex);
  525. ScreenLocate.DebugTexture(3, texLightedR);
  526. ScreenLocate.DebugTexture(4, texLightedG);
  527. ScreenLocate.DebugTexture(5, texLightedB);
  528. //Debug.Log(avg);
  529. return false;
  530. /**/
  531. #endregion
  532. }
  533. float GetBrightness()
  534. {
  535. UnityEngine.Color[] differPixel = new UnityEngine.Color[Size.x * Size.y];
  536. Parallel.For(0, Size.x * Size.y, i =>
  537. {
  538. var pi = ScreenWhiteTexture[i] - ScreenBlackTexture[i];
  539. differPixel[i] = new UnityEngine.Color(pi.x, pi.y, pi.z);
  540. });
  541. var sum = 0f;
  542. foreach (var i in differPixel)
  543. {
  544. sum += i.Brightness();
  545. }
  546. sum /= differPixel.Length;
  547. //Debug.Log(sum);
  548. return sum;
  549. }
  550. // 转换成屏幕定位所需的纹理图像
  551. Texture2D ToLocateTex(UnityEngine.Color[] pixels)
  552. {
  553. var ScreenLocateTex = new Texture2D(Size.x, Size.y);
  554. ScreenLocateTex.SetPixels(pixels);
  555. ScreenLocateTex.Apply();
  556. //ScreenLocate.DebugTexture(2, ScreenLocateTex);
  557. return ScreenLocateTex.AutoLight(10);
  558. //ScreenLocate.DebugTexture(2, ScreenLocateTexLighted);
  559. //var ScreenLocateTexR = ToLocateTex.ToRGB(ColorChannel.Red);
  560. //var ScreenLocateTexG = ToLocateTex.ToRGB(ColorChannel.Green);
  561. //var ScreenLocateTexB = ToLocateTex.ToRGB(ColorChannel.Blue);
  562. //LocateLightedRedTex = ScreenLocateTexR;
  563. //ScreenLocate.DebugTexture(2, ScreenLocateTexR);
  564. //ScreenLocate.DebugTexture(4, ScreenLocateTexG);
  565. //ScreenLocate.DebugTexture(5, ScreenLocateTexB);
  566. //var ScreenLocateTexLightedMat = texture.Too0Mat();
  567. }
  568. /// <param name="lineWidth">识别的最小线段长度</param>
  569. /// <param name="debugImages">这个参数如果不为null且数量大于0,则执行debug操作</param>
  570. void QuadrilateralFit(List<Texture2D> debugImages = null)
  571. {
  572. // 如果有旧的手动数据,刷新一下Size
  573. QuadManual?.ReSize(new Vector(Size.x, Size.y), ScreenMap.ViewAspectRatioSetting);
  574. // 屏幕黑白差值,存放多批次的图像用于识别, 该List数量不能等于 0
  575. List<UnityEngine.Color[]> PixelsMultipleBatches = new List<UnityEngine.Color[]>();
  576. var sw = new System.Diagnostics.Stopwatch();
  577. sw.Start();
  578. //读取数据
  579. if (debugImages != null && debugImages.Count != 0)
  580. {
  581. foreach (var i in debugImages)
  582. {
  583. Debug.Log($"<color=aqua>Debug {i.name}</color>");
  584. PixelsMultipleBatches.Add(i.GetPixels());
  585. }
  586. }
  587. else // 获得屏幕差值
  588. {
  589. var maxWhite = 0f;
  590. foreach (var i in ScreenWhiteTexture)
  591. {
  592. var m = i.x > i.y ? (i.x > i.z ? i.x : i.z) : (i.y > i.z ? i.y : i.z);
  593. if (maxWhite < m)
  594. maxWhite = m;
  595. }
  596. var scale = 1.0f / maxWhite; // 放大对比度
  597. var differPixel = new UnityEngine.Color[Size.x * Size.y];
  598. var whitePixel = new UnityEngine.Color[Size.x * Size.y];
  599. Parallel.For(0, Size.x, x =>
  600. {
  601. for (int y = 0; y < Size.y; y++)
  602. {
  603. var i = y * Size.x + x;
  604. var d = ScreenWhiteTexture[i] - ScreenBlackTexture[i];
  605. differPixel[i] = new UnityEngine.Color(d.x, d.y, d.z) * scale;
  606. whitePixel[i] = new UnityEngine.Color(ScreenWhiteTexture[i].x, ScreenWhiteTexture[i].y, ScreenWhiteTexture[i].z) * scale;
  607. }
  608. });
  609. PixelsMultipleBatches.Add(differPixel);
  610. PixelsMultipleBatches.Add(whitePixel);
  611. }
  612. int conSize = (int)Math.Ceiling(0.007f * Size.y) * 2 + 1;
  613. conSize = Math.Max(conSize, 7); // 设置最小为7
  614. float minLength = conSize * 7.7f;
  615. minLength = locateIndex == -1 ? minLength : minLength * areaPercent; // minLength需要按比例缩小
  616. string log = $"[Log][ScreenLocate Auto] Size: ({Size.x},{Size.y}), 卷积核Size: {conSize}, 最小线段长度: {minLength}";
  617. var allLines = new List<LineIdentified>();
  618. List<Texture2D> LocateTexTemp = new List<Texture2D>();
  619. List<Matrix> ScreenLocateMatList = new List<Matrix>();
  620. foreach (var batch in PixelsMultipleBatches.Index())
  621. {
  622. var locateTex = ToLocateTex(PixelsMultipleBatches[batch]);
  623. LocateTexTemp.Add(locateTex);
  624. var ScreenLocateMat = locateTex.Too0Mat(); // 用于获取Lines的Matrix
  625. var lineCount = ZIMIdentifyQuadLSD(ref allLines, batch, ScreenLocateMat.zimIdentifyEdgeGradientAny(conSize), minLength);
  626. log += $"\r\n识别图片{batch}, 识别到的线段数量为: {lineCount}";
  627. ScreenLocateMatList.Add(ScreenLocateMat);
  628. }
  629. Texture2D ScreenLocateTexture = LocateTexTemp[0]; // for output
  630. // 估算屏幕中点,如果已有手动定位数据,根据现有数据取平均即可,否则从色差计算,ScreenLocateMatList[0]默认是屏幕的黑白色差
  631. Vector AvgPoint = QuadManual != null ? QuadManual.Quad.Centroid : GetAvgPoint(ScreenLocateMatList[0]);
  632. // 过滤得到四边形的四条边,
  633. var (quadLinesSemiAuto, quadLinesAuto) = FilterLines(ScreenLocateMatList, allLines, AvgPoint,
  634. out LineIdentified[] manualLines, out List<LineIdentified> possibleLines,
  635. conSize, conSize, minLength);
  636. #region 全自动识别的结果
  637. List<LineIdentified> LineIdentifiedAuto = new List<LineIdentified>(); // 线段顺序: 下、右、上、左
  638. for (int i = 0; i < 4; i++)
  639. {
  640. if (quadLinesAuto[i] != null)
  641. LineIdentifiedAuto.Add(quadLinesAuto[i]);
  642. }
  643. if (LineIdentifiedAuto.Count == 4) // 判断识别的线段能否拼成屏幕,能拼成则记录
  644. {
  645. var a = LineIdentifiedAuto[0].Line.Intersect(LineIdentifiedAuto[3].Line, false).Value;
  646. var b = LineIdentifiedAuto[0].Line.Intersect(LineIdentifiedAuto[1].Line, false).Value;
  647. var c = LineIdentifiedAuto[2].Line.Intersect(LineIdentifiedAuto[3].Line, false).Value;
  648. var d = LineIdentifiedAuto[1].Line.Intersect(LineIdentifiedAuto[2].Line, false).Value;
  649. QuadAuto = new QuadrilateralInCamera(a, b, c, d, new Vector(Size.x, Size.y));
  650. if (!QuadAuto.IsQuadComplete())
  651. QuadAuto = null;
  652. }
  653. #endregion
  654. #region 半自动识别
  655. List<LineIdentified> LineIdentifiedSemiAuto = new List<LineIdentified>(); // 线段顺序: 下、右、上、左
  656. bool[] newLines = new bool[4] { true, true, true, true };
  657. for (int i = 0; i < 4; i++)
  658. {
  659. if (quadLinesSemiAuto[i] != null)
  660. LineIdentifiedSemiAuto.Add(quadLinesSemiAuto[i]);
  661. else if (manualLines != null)
  662. {
  663. LineIdentifiedSemiAuto.Add(manualLines[i]);
  664. newLines[i] = false;
  665. }
  666. }
  667. if (LineIdentifiedSemiAuto.Count == 4) // 判断识别的线段能否拼成屏幕,能拼成则记录
  668. {
  669. var a = LineIdentifiedSemiAuto[0].Line.Intersect(LineIdentifiedSemiAuto[3].Line, false).Value;
  670. var b = LineIdentifiedSemiAuto[0].Line.Intersect(LineIdentifiedSemiAuto[1].Line, false).Value;
  671. var c = LineIdentifiedSemiAuto[2].Line.Intersect(LineIdentifiedSemiAuto[3].Line, false).Value;
  672. var d = LineIdentifiedSemiAuto[1].Line.Intersect(LineIdentifiedSemiAuto[2].Line, false).Value;
  673. QuadSemiAuto = new QuadrilateralInCamera(a, b, c, d, new Vector(Size.x, Size.y));
  674. if (!QuadSemiAuto.IsQuadComplete())
  675. QuadSemiAuto = null;
  676. }
  677. #endregion
  678. // 优先应用半自动的结果(也可以在外部手动设置)
  679. if (QuadSemiAuto == null && QuadAuto == null && Screen.QuadInCamera != null) // 如果可能,回退到上一个screen
  680. {
  681. Debug.Log("<color=aqua>[ScreenIdentification] 本次识别失败,回退到上次的识别结果</color>");
  682. quadTemp.Add(Screen.QuadInCamera.Quad);
  683. }
  684. else if (QuadSemiAuto != null)
  685. {
  686. Debug.Log("<color=aqua>[ScreenIdentification] 识别到四边形</color>");
  687. quadTemp.Add(QuadSemiAuto.Quad);
  688. }else if (QuadAuto != null)
  689. {
  690. Debug.Log("<color=aqua>[ScreenIdentification] 识别到四边形</color>");
  691. quadTemp.Add(QuadAuto.Quad);
  692. }
  693. #region 绘制 output texture
  694. // 绘制半自动
  695. var ScreenQuadMap = new Matrix(Size, Tiling: true); // 识别的到的屏幕四边形(半自动和自动在一张图上)
  696. foreach (var i in LineIdentifiedSemiAuto.Index())
  697. {
  698. if (newLines[i])
  699. o0Extension.DrawLine(ScreenQuadMap, LineIdentifiedSemiAuto[i].DrawLine, (x, y) => 2, new Geometry2D.Float.Vector(0, 10));
  700. else
  701. o0Extension.DrawLine(ScreenQuadMap, LineIdentifiedSemiAuto[i].DrawLine, (x, y) => 1, new Geometry2D.Float.Vector(0, 6), true);
  702. }
  703. // 绘制全自动
  704. foreach (var i in LineIdentifiedAuto.Index())
  705. o0Extension.DrawLine(ScreenQuadMap, LineIdentifiedAuto[i].DrawLine, (x, y) => 4, new Geometry2D.Float.Vector(0, 4), true);
  706. Texture2D ScreenQuad = ScreenQuadMap.ToTexRGBA(FloatValueToColor);
  707. Texture2D ScreenQuadWithScreen = ScreenQuad.Overlay(ScreenLocateTexture); // 叠加屏幕色差图
  708. // 绘制allLines
  709. var allLinesMap = new Matrix(Size, Tiling: true);
  710. foreach (var l in allLines)
  711. {
  712. if (l.DrawLine != null)
  713. o0Extension.DrawLine(allLinesMap, l.DrawLine, (x, y) => 3, new Geometry2D.Float.Vector(0, 2), true);
  714. }
  715. var allLinesTex = allLinesMap.ToTexRGBA(FloatValueToColor);
  716. ScreenLocate.DebugTexture(1, allLinesTex);
  717. // 还需要输出一张识别结果图,包含干扰线段
  718. var ChoosableLineMap = new Matrix(Size, Tiling: true);
  719. foreach (var l in possibleLines)
  720. {
  721. if (l != null && !quadLinesSemiAuto.Contains(l) && !manualLines.Contains(l))
  722. o0Extension.DrawLine(ChoosableLineMap, l.DrawLine, (x, y) => 3, new Geometry2D.Float.Vector(0, 2), true); // 其他的备选线段
  723. }
  724. foreach (var l in LineIdentifiedSemiAuto)
  725. {
  726. if (l != null)
  727. o0Extension.DrawLine(ChoosableLineMap, l.DrawLine, (x, y) => 2, new Geometry2D.Float.Vector(0, 5)); // 识别的结果
  728. }
  729. if (manualLines != null)
  730. {
  731. foreach (var l in manualLines)
  732. o0Extension.DrawLine(ChoosableLineMap, l.DrawLine, (x, y) => 1, new Geometry2D.Float.Vector(0, 2), true); // 旧的屏幕线段(例如上次手动识别的)
  733. }
  734. Texture2D ChoosableLineTex = ChoosableLineMap.ToTexRGBA(FloatValueToColor);
  735. #endregion
  736. log += $"\r\n屏幕四边形_手动识别{QuadManual != null}\r\n屏幕四边形_半自动识别{QuadSemiAuto != null}\r\n屏幕四边形_全自动识别{QuadAuto != null}";
  737. Debug.Log(log);
  738. // 是否将图片保存到本地
  739. if (ScreenLocate.Main.SaveToggle.isOn && ScreenLocate.Main.DebugOnZIMDemo)
  740. {
  741. var FileDirectory = $"Debug_屏幕定位/";
  742. SaveImages(FileDirectory, log, ScreenLocateTexture, allLinesTex, ChoosableLineTex, ScreenQuad);
  743. }
  744. //times.Add(watch.ElapsedMilliseconds);
  745. //UnityEngine.Debug.Log("time: " + (times[times.Count - 1] - times[times.Count - 2]));
  746. // opecncv处理, zim
  747. {
  748. //var cvLines = edge.cvHoughLinesP();
  749. //ScreenLocate.DebugTexture(5, cvLines);
  750. //var myLines = Hough.Transform(edgeMat);
  751. //var cvLines = edge.cvLine(myLines);
  752. //ScreenLocate.DebugTexture(5, cvLines);
  753. }
  754. {
  755. ScreenLocate.DebugTexture(2, ScreenLocateTexture);
  756. ScreenLocate.DebugTexture(3, ScreenQuad);
  757. ScreenLocate.DebugTexture(4, ScreenQuadWithScreen);
  758. ScreenLocate.DebugTexture(5, ChoosableLineTex);
  759. }
  760. foreach (var i in LocateTexTemp)
  761. {
  762. if (i != ScreenLocateTexture) // ScreenLocateTexture 由 ScreenLocate.DebugTexture 释放
  763. GameObject.Destroy(i);
  764. }
  765. }
  766. Vector GetAvgPoint(Matrix screenLocateMat)
  767. {
  768. // 加权平均
  769. Vector[] avgPointsColumn = new Vector[screenLocateMat.Size.x];
  770. float[] valueSumsColumn = new float[screenLocateMat.Size.x];
  771. Parallel.For(0, screenLocateMat.Size.x, i =>
  772. {
  773. for (int j = 0; j < screenLocateMat.Size.y; j++)
  774. {
  775. var value = screenLocateMat[i, j];
  776. valueSumsColumn[i] += value;
  777. avgPointsColumn[i] += new Vector(i, j) * value;
  778. }
  779. });
  780. Vector avgPoint = Vector.Zero;
  781. var valueSum = 0f;
  782. for (int i = 0; i < screenLocateMat.Size.x; i++)
  783. {
  784. avgPoint += avgPointsColumn[i];
  785. valueSum += valueSumsColumn[i];
  786. }
  787. avgPoint /= valueSum;
  788. return avgPoint;
  789. }
  790. // 返回查找到的线段数量,0是查找失败
  791. int ZIMIdentifyQuadLSD(ref List<LineIdentified> allLines, int batch, (Matrix edgeMat, Matrix edgeDirMat) edgeGradient,
  792. float minLength = 100)
  793. {
  794. var l = edgeGradient.edgeMat.IdentifyLineLSD(edgeGradient.edgeDirMat, minLength, 50, LineCaptureSize: new Vector(10, 6));
  795. if (l == null || l.Count == 0)
  796. return 0;
  797. allLines.AddRange(l.Select((i) => new LineIdentified(batch, i)));
  798. return l.Count;
  799. }
  800. // 返回四边形的四条边(半自动、全自动),List长度一定是4 (如果没有识别到就是null),且线段顺序是: 下、右、上、左
  801. (List<LineIdentified>, List<LineIdentified>) FilterLines(List<Matrix> screenLocateMatList, List<LineIdentified> allLines, Vector avgPoint,
  802. out LineIdentified[] manualLines, out List<LineIdentified> possibleLines, float conSize, float gradientLength, float minLength = 100)
  803. {
  804. //Debug.Log("[IdentifyLineLSD] lines.Count: " + lines.Count);
  805. var offset = new Vector((conSize - 1) / 2, (conSize - 1) / 2);
  806. // LSD计算得到的矩阵尺寸较小(因为卷积),这里必须进行位移
  807. for (int i = 0; i < allLines.Count; i++)
  808. allLines[i].Offset(offset);
  809. // 筛掉椭圆框外的线段(超出一半会筛掉)
  810. var innerLines = new List<LineIdentified>();
  811. for (int i = 0; i < allLines.Count; i++)
  812. {
  813. List<Vector> InArea = new List<Vector>();
  814. var dir = (allLines[i].Line.B - allLines[i].Line.A) / 4;
  815. 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点
  816. for (int pI = 0; pI < points.Length; pI++)
  817. {
  818. if (!ScreenLocate.Main.ScreenPixelCheaker.OutArea2D(points[pI], Size))
  819. InArea.Add(points[pI]);
  820. }
  821. if (InArea.Count < 2) // 少于2个点在内部
  822. continue;
  823. else if (InArea.Count < points.Length) // 不完全在内部
  824. allLines[i].DrawLine = new Line(InArea.First(), InArea.Last()); // 将部分线条设置为drawline,用于下一步的绘制
  825. else // 线段全部在椭圆内
  826. allLines[i].DrawLine = allLines[i].Line;
  827. innerLines.Add(allLines[i]);
  828. }
  829. // 角度阈值,用来判断线段的梯度方向是否指向屏幕中心(avgPoint)
  830. var avaAngleHalf = 75f;
  831. // 沿直线计算综合梯度(梯度乘以长度系数,再乘以距离系数), distanceRatio是实际距离除以最大距离
  832. float estimateGradient(LineIdentified line, float distanceRatio)
  833. {
  834. var dir = (line.Line.B - line.Line.A).Normalized;
  835. var vertical = new Vector(-dir.y, dir.x) * (gradientLength / 2);
  836. var step = 2;
  837. var ll = line.Line.Length;
  838. var lg = new List<float>();
  839. for (int i = 0; i <= ll; i += step)
  840. {
  841. var point = line.Line.A + dir * i;
  842. var ga = point + vertical;
  843. var gb = point - vertical;
  844. lg.Add(screenLocateMatList[line.Batch][(int)ga.x, (int)ga.y] - screenLocateMatList[line.Batch][(int)gb.x, (int)gb.y]);
  845. }
  846. float e = (float)Math.Sqrt(Math.Ceiling(line.Line.Length / minLength)); // 长度系数,筛选时梯度更大、长度更长的线段更优
  847. float d = (3 - distanceRatio) / 2; // 距离系数,距离越近,系数越大
  848. return e * d * Math.Abs(lg.Mean());
  849. }
  850. // 根据线段梯度的角度,判断是不是屏幕的边,out index代表是哪条边(顺序是: 下、右、上、左)
  851. bool isScreenLine(LineIdentified line, out int index)
  852. {
  853. var a = (avgPoint - (line.Line.A + line.Line.B) / 2).DegreeToXAxis();
  854. //Debug.Log(a + ", " + gradient + ", " + sum);
  855. index = -1;
  856. if (Math.Abs(a - line.GradientDegree) < avaAngleHalf || Math.Abs(a - 360 - line.GradientDegree) < avaAngleHalf || Math.Abs(a + 360 - line.GradientDegree) < avaAngleHalf)
  857. {
  858. if (line.GradientDegree > 45 && line.GradientDegree < 135) // 下
  859. index = 0;
  860. else if (line.GradientDegree > 135 && line.GradientDegree < 225) // 右
  861. index = 1;
  862. else if (line.GradientDegree > 225 && line.GradientDegree < 315) // 上
  863. index = 2;
  864. else
  865. index = 3;
  866. return true;
  867. }
  868. return false;
  869. }
  870. // 下、右、上、左, 半自动和自动
  871. var quadLinesSemiAuto = new List<(float, LineIdentified)>[4] { new List<(float, LineIdentified)>(), new List<(float, LineIdentified)>(), new List<(float, LineIdentified)>(), new List<(float, LineIdentified)>() };
  872. var quadLinesAuto = new List<(float, LineIdentified)>[4] { new List<(float, LineIdentified)>(), new List<(float, LineIdentified)>(), new List<(float, LineIdentified)>(), new List<(float, LineIdentified)>() };
  873. possibleLines = new List<LineIdentified>();
  874. manualLines = null;
  875. // 如果已有手动定位数据,根据现有数据筛选线条(半自动)
  876. if (QuadManual != null)
  877. {
  878. Debug.Log("[IdentifyLineLSD] 根据已有定位数据做筛选");
  879. var calibration = ScreenLocate.Main.ReDoLocateCalibrationRatio * Size.y;
  880. manualLines = QuadManual.GetLines().Select((i) => new LineIdentified(0, i, 0, 0, true)).ToArray();
  881. var pedals = manualLines.Select((i) => o0Extension.PointPedal(i.Line, avgPoint, out _)).ToArray(); // 当前定位的垂足,下、右、上、左
  882. foreach (var line in innerLines)
  883. {
  884. // 筛选条件:1-梯度方向匹配,2-垂足的距离足够近, 3-新的线段的中点,到旧线段的垂足,要在旧线段内
  885. if (isScreenLine(line, out int index))
  886. {
  887. var distanceToOld = (o0Extension.PointPedal(line.Line, avgPoint, out _) - pedals[index]).Length;
  888. if (distanceToOld < calibration)
  889. {
  890. var middleToOldLine = o0Extension.PointPedal(manualLines[index].Line, (line.Line.A + line.Line.B) / 2, out bool inLineSegment);
  891. if (inLineSegment)
  892. {
  893. quadLinesSemiAuto[index].Add((estimateGradient(line, distanceToOld / calibration), line));
  894. possibleLines.Add(line);
  895. }
  896. }
  897. }
  898. }
  899. }
  900. // 全自动
  901. foreach (var line in allLines)
  902. {
  903. if (isScreenLine(line , out int index))
  904. {
  905. if (line.Batch < 1) // 全自动只处理第一张图,默认是色差图
  906. {
  907. quadLinesAuto[index].Add((estimateGradient(line, 1), line));
  908. }
  909. }
  910. }
  911. var resultSemiAuto = new LineIdentified[4];
  912. var resultAuto = new LineIdentified[4];
  913. for (int i = 0; i < 4; i++)
  914. {
  915. if (quadLinesSemiAuto[i].Count > 0)
  916. resultSemiAuto[i] = quadLinesSemiAuto[i].Max((a, b) => a.Item1.CompareTo(b.Item1)).Item2;
  917. if (quadLinesAuto[i].Count > 0)
  918. resultAuto[i] = quadLinesAuto[i].Max((a, b) => a.Item1.CompareTo(b.Item1)).Item2;
  919. }
  920. return (resultSemiAuto.ToList(), resultAuto.ToList());
  921. }
  922. void SaveImages(string FileDirectory, string log,
  923. Texture2D ScreenLocateTex, Texture2D allLinesTex, Texture2D ChoosableLineTex, Texture2D ScreenQuadTex)
  924. {
  925. if (!Directory.Exists(FileDirectory))
  926. Directory.CreateDirectory(FileDirectory);
  927. var time = DateTime.Now.ToString("yyyyMMdd_HHmmss");
  928. var pngData = ScreenLocate.Main.OutputTextures[7]?.EncodeToPNG();
  929. if (pngData != null)
  930. File.WriteAllBytes($"{FileDirectory}{time}A屏幕原图.png", pngData);
  931. var pngData1 = ScreenLocateTex?.EncodeToPNG();
  932. if (pngData1 != null)
  933. File.WriteAllBytes($"{FileDirectory}{time}B黑白色差.png", pngData1);
  934. var pngData2 = allLinesTex?.EncodeToPNG();
  935. if (pngData2 != null)
  936. File.WriteAllBytes($"{FileDirectory}{time}C全部识别线段_半自动.png", pngData2);
  937. var pngData3 = ChoosableLineTex?.EncodeToPNG();
  938. if (pngData3 != null)
  939. File.WriteAllBytes($"{FileDirectory}{time}D备选线段_半自动.png", pngData3);
  940. var pngData4 = ScreenQuadTex?.EncodeToPNG();
  941. if (pngData4 != null)
  942. File.WriteAllBytes($"{FileDirectory}{time}E识别结果.png", pngData4);
  943. Debug.Log($"<color=aqua>({time}) 屏幕识别图片保存至:程序根目录/{FileDirectory}</color>");
  944. log +=
  945. $"\r\n屏幕原图保存{pngData != null}, " +
  946. $"\r\n黑白色差保存{pngData1 != null}, " +
  947. $"\r\n全部识别线段(半自动)保存{pngData2 != null}, " +
  948. $"\r\n备选线段(半自动)保存{pngData3 != null}, " +
  949. $"\r\n识别结果保存{pngData4 != null}";
  950. File.WriteAllText($"{FileDirectory}{time}屏幕自动定位_日志.log", log);
  951. }
  952. }
  953. }