ScreenIdentification.cs 55 KB

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