You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

FinderPatternFinder.cs 31 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808
  1. /*
  2. * Copyright 2007 ZXing authors
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. using System;
  17. using System.Collections.Generic;
  18. using ZXing.Common;
  19. namespace ZXing.QrCode.Internal
  20. {
  21. /// <summary>
  22. /// <p>This class attempts to find finder patterns in a QR Code. Finder patterns are the square
  23. /// markers at three corners of a QR Code.</p>
  24. ///
  25. /// <p>This class is thread-safe but not reentrant. Each thread must allocate its own object.
  26. /// </summary>
  27. /// <author>Sean Owen</author>
  28. public class FinderPatternFinder
  29. {
  30. private const int CENTER_QUORUM = 2;
  31. /// <summary>
  32. /// 1 pixel/module times 3 modules/center
  33. /// </summary>
  34. protected internal const int MIN_SKIP = 3;
  35. /// <summary>
  36. /// support up to version 10 for mobile clients
  37. /// </summary>
  38. protected internal const int MAX_MODULES = 57;
  39. private const int INTEGER_MATH_SHIFT = 8;
  40. private readonly BitMatrix image;
  41. private List<FinderPattern> possibleCenters;
  42. private bool hasSkipped;
  43. private readonly int[] crossCheckStateCount;
  44. private readonly ResultPointCallback resultPointCallback;
  45. /// <summary>
  46. /// <p>Creates a finder that will search the image for three finder patterns.</p>
  47. /// </summary>
  48. /// <param name="image">image to search</param>
  49. public FinderPatternFinder(BitMatrix image)
  50. : this(image, null)
  51. {
  52. }
  53. /// <summary>
  54. /// Initializes a new instance of the <see cref="FinderPatternFinder"/> class.
  55. /// </summary>
  56. /// <param name="image">The image.</param>
  57. /// <param name="resultPointCallback">The result point callback.</param>
  58. public FinderPatternFinder(BitMatrix image, ResultPointCallback resultPointCallback)
  59. {
  60. this.image = image;
  61. this.possibleCenters = new List<FinderPattern>();
  62. this.crossCheckStateCount = new int[5];
  63. this.resultPointCallback = resultPointCallback;
  64. }
  65. /// <summary>
  66. /// Gets the image.
  67. /// </summary>
  68. virtual protected internal BitMatrix Image
  69. {
  70. get
  71. {
  72. return image;
  73. }
  74. }
  75. /// <summary>
  76. /// Gets the possible centers.
  77. /// </summary>
  78. virtual protected internal List<FinderPattern> PossibleCenters
  79. {
  80. get
  81. {
  82. return possibleCenters;
  83. }
  84. }
  85. internal virtual FinderPatternInfo find(IDictionary<DecodeHintType, object> hints)
  86. {
  87. bool tryHarder = hints != null && hints.ContainsKey(DecodeHintType.TRY_HARDER);
  88. bool pureBarcode = hints != null && hints.ContainsKey(DecodeHintType.PURE_BARCODE);
  89. int maxI = image.Height;
  90. int maxJ = image.Width;
  91. // We are looking for black/white/black/white/black modules in
  92. // 1:1:3:1:1 ratio; this tracks the number of such modules seen so far
  93. // Let's assume that the maximum version QR Code we support takes up 1/4 the height of the
  94. // image, and then account for the center being 3 modules in size. This gives the smallest
  95. // number of pixels the center could be, so skip this often. When trying harder, look for all
  96. // QR versions regardless of how dense they are.
  97. int iSkip = (3 * maxI) / (4 * MAX_MODULES);
  98. if (iSkip < MIN_SKIP || tryHarder)
  99. {
  100. iSkip = MIN_SKIP;
  101. }
  102. bool done = false;
  103. int[] stateCount = new int[5];
  104. for (int i = iSkip - 1; i < maxI && !done; i += iSkip)
  105. {
  106. // Get a row of black/white values
  107. stateCount[0] = 0;
  108. stateCount[1] = 0;
  109. stateCount[2] = 0;
  110. stateCount[3] = 0;
  111. stateCount[4] = 0;
  112. int currentState = 0;
  113. for (int j = 0; j < maxJ; j++)
  114. {
  115. if (image[j, i])
  116. {
  117. // Black pixel
  118. if ((currentState & 1) == 1)
  119. {
  120. // Counting white pixels
  121. currentState++;
  122. }
  123. stateCount[currentState]++;
  124. }
  125. else
  126. {
  127. // White pixel
  128. if ((currentState & 1) == 0)
  129. {
  130. // Counting black pixels
  131. if (currentState == 4)
  132. {
  133. // A winner?
  134. if (foundPatternCross(stateCount))
  135. {
  136. // Yes
  137. bool confirmed = handlePossibleCenter(stateCount, i, j, pureBarcode);
  138. if (confirmed)
  139. {
  140. // Start examining every other line. Checking each line turned out to be too
  141. // expensive and didn't improve performance.
  142. iSkip = 2;
  143. if (hasSkipped)
  144. {
  145. done = haveMultiplyConfirmedCenters();
  146. }
  147. else
  148. {
  149. int rowSkip = findRowSkip();
  150. if (rowSkip > stateCount[2])
  151. {
  152. // Skip rows between row of lower confirmed center
  153. // and top of presumed third confirmed center
  154. // but back up a bit to get a full chance of detecting
  155. // it, entire width of center of finder pattern
  156. // Skip by rowSkip, but back off by stateCount[2] (size of last center
  157. // of pattern we saw) to be conservative, and also back off by iSkip which
  158. // is about to be re-added
  159. i += rowSkip - stateCount[2] - iSkip;
  160. j = maxJ - 1;
  161. }
  162. }
  163. }
  164. else
  165. {
  166. stateCount[0] = stateCount[2];
  167. stateCount[1] = stateCount[3];
  168. stateCount[2] = stateCount[4];
  169. stateCount[3] = 1;
  170. stateCount[4] = 0;
  171. currentState = 3;
  172. continue;
  173. }
  174. // Clear state to start looking again
  175. currentState = 0;
  176. stateCount[0] = 0;
  177. stateCount[1] = 0;
  178. stateCount[2] = 0;
  179. stateCount[3] = 0;
  180. stateCount[4] = 0;
  181. }
  182. else
  183. {
  184. // No, shift counts back by two
  185. stateCount[0] = stateCount[2];
  186. stateCount[1] = stateCount[3];
  187. stateCount[2] = stateCount[4];
  188. stateCount[3] = 1;
  189. stateCount[4] = 0;
  190. currentState = 3;
  191. }
  192. }
  193. else
  194. {
  195. stateCount[++currentState]++;
  196. }
  197. }
  198. else
  199. {
  200. // Counting white pixels
  201. stateCount[currentState]++;
  202. }
  203. }
  204. }
  205. if (foundPatternCross(stateCount))
  206. {
  207. bool confirmed = handlePossibleCenter(stateCount, i, maxJ, pureBarcode);
  208. if (confirmed)
  209. {
  210. iSkip = stateCount[0];
  211. if (hasSkipped)
  212. {
  213. // Found a third one
  214. done = haveMultiplyConfirmedCenters();
  215. }
  216. }
  217. }
  218. }
  219. FinderPattern[] patternInfo = selectBestPatterns();
  220. if (patternInfo == null)
  221. return null;
  222. ResultPoint.orderBestPatterns(patternInfo);
  223. return new FinderPatternInfo(patternInfo);
  224. }
  225. /// <summary> Given a count of black/white/black/white/black pixels just seen and an end position,
  226. /// figures the location of the center of this run.
  227. /// </summary>
  228. private static float? centerFromEnd(int[] stateCount, int end)
  229. {
  230. var result = (end - stateCount[4] - stateCount[3]) - stateCount[2] / 2.0f;
  231. if (Single.IsNaN(result))
  232. return null;
  233. return result;
  234. }
  235. /// <param name="stateCount">count of black/white/black/white/black pixels just read
  236. /// </param>
  237. /// <returns> true iff the proportions of the counts is close enough to the 1/1/3/1/1 ratios
  238. /// used by finder patterns to be considered a match
  239. /// </returns>
  240. protected internal static bool foundPatternCross(int[] stateCount)
  241. {
  242. int totalModuleSize = 0;
  243. for (int i = 0; i < 5; i++)
  244. {
  245. int count = stateCount[i];
  246. if (count == 0)
  247. {
  248. return false;
  249. }
  250. totalModuleSize += count;
  251. }
  252. if (totalModuleSize < 7)
  253. {
  254. return false;
  255. }
  256. int moduleSize = (totalModuleSize << INTEGER_MATH_SHIFT) / 7;
  257. int maxVariance = moduleSize / 2;
  258. // Allow less than 50% variance from 1-1-3-1-1 proportions
  259. return Math.Abs(moduleSize - (stateCount[0] << INTEGER_MATH_SHIFT)) < maxVariance &&
  260. Math.Abs(moduleSize - (stateCount[1] << INTEGER_MATH_SHIFT)) < maxVariance &&
  261. Math.Abs(3 * moduleSize - (stateCount[2] << INTEGER_MATH_SHIFT)) < 3 * maxVariance &&
  262. Math.Abs(moduleSize - (stateCount[3] << INTEGER_MATH_SHIFT)) < maxVariance &&
  263. Math.Abs(moduleSize - (stateCount[4] << INTEGER_MATH_SHIFT)) < maxVariance;
  264. }
  265. private int[] CrossCheckStateCount
  266. {
  267. get
  268. {
  269. crossCheckStateCount[0] = 0;
  270. crossCheckStateCount[1] = 0;
  271. crossCheckStateCount[2] = 0;
  272. crossCheckStateCount[3] = 0;
  273. crossCheckStateCount[4] = 0;
  274. return crossCheckStateCount;
  275. }
  276. }
  277. /// <summary>
  278. /// After a vertical and horizontal scan finds a potential finder pattern, this method
  279. /// "cross-cross-cross-checks" by scanning down diagonally through the center of the possible
  280. /// finder pattern to see if the same proportion is detected.
  281. /// </summary>
  282. /// <param name="startI">row where a finder pattern was detected</param>
  283. /// <param name="centerJ">center of the section that appears to cross a finder pattern</param>
  284. /// <param name="maxCount">maximum reasonable number of modules that should be observed in any reading state, based on the results of the horizontal scan</param>
  285. /// <param name="originalStateCountTotal">The original state count total.</param>
  286. /// <returns>true if proportions are withing expected limits</returns>
  287. private bool crossCheckDiagonal(int startI, int centerJ, int maxCount, int originalStateCountTotal)
  288. {
  289. int maxI = image.Height;
  290. int maxJ = image.Width;
  291. int[] stateCount = CrossCheckStateCount;
  292. // Start counting up, left from center finding black center mass
  293. int i = 0;
  294. while (startI - i >= 0 && image[centerJ - i, startI - i])
  295. {
  296. stateCount[2]++;
  297. i++;
  298. }
  299. if ((startI - i < 0) || (centerJ - i < 0))
  300. {
  301. return false;
  302. }
  303. // Continue up, left finding white space
  304. while ((startI - i >= 0) && (centerJ - i >= 0) && !image[centerJ - i, startI - i] && stateCount[1] <= maxCount)
  305. {
  306. stateCount[1]++;
  307. i++;
  308. }
  309. // If already too many modules in this state or ran off the edge:
  310. if ((startI - i < 0) || (centerJ - i < 0) || stateCount[1] > maxCount)
  311. {
  312. return false;
  313. }
  314. // Continue up, left finding black border
  315. while ((startI - i >= 0) && (centerJ - i >= 0) && image[centerJ - i, startI - i] && stateCount[0] <= maxCount)
  316. {
  317. stateCount[0]++;
  318. i++;
  319. }
  320. if (stateCount[0] > maxCount)
  321. {
  322. return false;
  323. }
  324. // Now also count down, right from center
  325. i = 1;
  326. while ((startI + i < maxI) && (centerJ + i < maxJ) && image[centerJ + i, startI + i])
  327. {
  328. stateCount[2]++;
  329. i++;
  330. }
  331. // Ran off the edge?
  332. if ((startI + i >= maxI) || (centerJ + i >= maxJ))
  333. {
  334. return false;
  335. }
  336. while ((startI + i < maxI) && (centerJ + i < maxJ) && !image[centerJ + i, startI + i] && stateCount[3] < maxCount)
  337. {
  338. stateCount[3]++;
  339. i++;
  340. }
  341. if ((startI + i >= maxI) || (centerJ + i >= maxJ) || stateCount[3] >= maxCount)
  342. {
  343. return false;
  344. }
  345. while ((startI + i < maxI) && (centerJ + i < maxJ) && image[centerJ + i, startI + i] && stateCount[4] < maxCount)
  346. {
  347. stateCount[4]++;
  348. i++;
  349. }
  350. if (stateCount[4] >= maxCount)
  351. {
  352. return false;
  353. }
  354. // If we found a finder-pattern-like section, but its size is more than 100% different than
  355. // the original, assume it's a false positive
  356. int stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + stateCount[4];
  357. return Math.Abs(stateCountTotal - originalStateCountTotal) < 2*originalStateCountTotal &&
  358. foundPatternCross(stateCount);
  359. }
  360. /// <summary>
  361. /// <p>After a horizontal scan finds a potential finder pattern, this method
  362. /// "cross-checks" by scanning down vertically through the center of the possible
  363. /// finder pattern to see if the same proportion is detected.</p>
  364. /// </summary>
  365. /// <param name="startI">row where a finder pattern was detected</param>
  366. /// <param name="centerJ">center of the section that appears to cross a finder pattern</param>
  367. /// <param name="maxCount">maximum reasonable number of modules that should be
  368. /// observed in any reading state, based on the results of the horizontal scan</param>
  369. /// <param name="originalStateCountTotal">The original state count total.</param>
  370. /// <returns>
  371. /// vertical center of finder pattern, or null if not found
  372. /// </returns>
  373. private float? crossCheckVertical(int startI, int centerJ, int maxCount, int originalStateCountTotal)
  374. {
  375. int maxI = image.Height;
  376. int[] stateCount = CrossCheckStateCount;
  377. // Start counting up from center
  378. int i = startI;
  379. while (i >= 0 && image[centerJ, i])
  380. {
  381. stateCount[2]++;
  382. i--;
  383. }
  384. if (i < 0)
  385. {
  386. return null;
  387. }
  388. while (i >= 0 && !image[centerJ, i] && stateCount[1] <= maxCount)
  389. {
  390. stateCount[1]++;
  391. i--;
  392. }
  393. // If already too many modules in this state or ran off the edge:
  394. if (i < 0 || stateCount[1] > maxCount)
  395. {
  396. return null;
  397. }
  398. while (i >= 0 && image[centerJ, i] && stateCount[0] <= maxCount)
  399. {
  400. stateCount[0]++;
  401. i--;
  402. }
  403. if (stateCount[0] > maxCount)
  404. {
  405. return null;
  406. }
  407. // Now also count down from center
  408. i = startI + 1;
  409. while (i < maxI && image[centerJ, i])
  410. {
  411. stateCount[2]++;
  412. i++;
  413. }
  414. if (i == maxI)
  415. {
  416. return null;
  417. }
  418. while (i < maxI && !image[centerJ, i] && stateCount[3] < maxCount)
  419. {
  420. stateCount[3]++;
  421. i++;
  422. }
  423. if (i == maxI || stateCount[3] >= maxCount)
  424. {
  425. return null;
  426. }
  427. while (i < maxI && image[centerJ, i] && stateCount[4] < maxCount)
  428. {
  429. stateCount[4]++;
  430. i++;
  431. }
  432. if (stateCount[4] >= maxCount)
  433. {
  434. return null;
  435. }
  436. // If we found a finder-pattern-like section, but its size is more than 40% different than
  437. // the original, assume it's a false positive
  438. int stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + stateCount[4];
  439. if (5 * Math.Abs(stateCountTotal - originalStateCountTotal) >= 2 * originalStateCountTotal)
  440. {
  441. return null;
  442. }
  443. return foundPatternCross(stateCount) ? centerFromEnd(stateCount, i) : null;
  444. }
  445. /// <summary> <p>Like {@link #crossCheckVertical(int, int, int, int)}, and in fact is basically identical,
  446. /// except it reads horizontally instead of vertically. This is used to cross-cross
  447. /// check a vertical cross check and locate the real center of the alignment pattern.</p>
  448. /// </summary>
  449. private float? crossCheckHorizontal(int startJ, int centerI, int maxCount, int originalStateCountTotal)
  450. {
  451. int maxJ = image.Width;
  452. int[] stateCount = CrossCheckStateCount;
  453. int j = startJ;
  454. while (j >= 0 && image[j, centerI])
  455. {
  456. stateCount[2]++;
  457. j--;
  458. }
  459. if (j < 0)
  460. {
  461. return null;
  462. }
  463. while (j >= 0 && !image[j, centerI] && stateCount[1] <= maxCount)
  464. {
  465. stateCount[1]++;
  466. j--;
  467. }
  468. if (j < 0 || stateCount[1] > maxCount)
  469. {
  470. return null;
  471. }
  472. while (j >= 0 && image[j, centerI] && stateCount[0] <= maxCount)
  473. {
  474. stateCount[0]++;
  475. j--;
  476. }
  477. if (stateCount[0] > maxCount)
  478. {
  479. return null;
  480. }
  481. j = startJ + 1;
  482. while (j < maxJ && image[j, centerI])
  483. {
  484. stateCount[2]++;
  485. j++;
  486. }
  487. if (j == maxJ)
  488. {
  489. return null;
  490. }
  491. while (j < maxJ && !image[j, centerI] && stateCount[3] < maxCount)
  492. {
  493. stateCount[3]++;
  494. j++;
  495. }
  496. if (j == maxJ || stateCount[3] >= maxCount)
  497. {
  498. return null;
  499. }
  500. while (j < maxJ && image[j, centerI] && stateCount[4] < maxCount)
  501. {
  502. stateCount[4]++;
  503. j++;
  504. }
  505. if (stateCount[4] >= maxCount)
  506. {
  507. return null;
  508. }
  509. // If we found a finder-pattern-like section, but its size is significantly different than
  510. // the original, assume it's a false positive
  511. int stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + stateCount[4];
  512. if (5 * Math.Abs(stateCountTotal - originalStateCountTotal) >= originalStateCountTotal)
  513. {
  514. return null;
  515. }
  516. return foundPatternCross(stateCount) ? centerFromEnd(stateCount, j) : null;
  517. }
  518. /// <summary>
  519. /// <p>This is called when a horizontal scan finds a possible alignment pattern. It will
  520. /// cross check with a vertical scan, and if successful, will, ah, cross-cross-check
  521. /// with another horizontal scan. This is needed primarily to locate the real horizontal
  522. /// center of the pattern in cases of extreme skew.
  523. /// And then we cross-cross-cross check with another diagonal scan.</p>
  524. /// If that succeeds the finder pattern location is added to a list that tracks
  525. /// the number of times each location has been nearly-matched as a finder pattern.
  526. /// Each additional find is more evidence that the location is in fact a finder
  527. /// pattern center
  528. /// </summary>
  529. /// <param name="stateCount">reading state module counts from horizontal scan</param>
  530. /// <param name="i">row where finder pattern may be found</param>
  531. /// <param name="j">end of possible finder pattern in row</param>
  532. /// <param name="pureBarcode">if set to <c>true</c> [pure barcode].</param>
  533. /// <returns>
  534. /// true if a finder pattern candidate was found this time
  535. /// </returns>
  536. protected bool handlePossibleCenter(int[] stateCount, int i, int j, bool pureBarcode)
  537. {
  538. int stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] +
  539. stateCount[4];
  540. float? centerJ = centerFromEnd(stateCount, j);
  541. if (centerJ == null)
  542. return false;
  543. float? centerI = crossCheckVertical(i, (int)centerJ.Value, stateCount[2], stateCountTotal);
  544. if (centerI != null)
  545. {
  546. // Re-cross check
  547. centerJ = crossCheckHorizontal((int)centerJ.Value, (int)centerI.Value, stateCount[2], stateCountTotal);
  548. if (centerJ != null &&
  549. (!pureBarcode || crossCheckDiagonal((int) centerI, (int) centerJ, stateCount[2], stateCountTotal)))
  550. {
  551. float estimatedModuleSize = stateCountTotal / 7.0f;
  552. bool found = false;
  553. for (int index = 0; index < possibleCenters.Count; index++)
  554. {
  555. var center = possibleCenters[index];
  556. // Look for about the same center and module size:
  557. if (center.aboutEquals(estimatedModuleSize, centerI.Value, centerJ.Value))
  558. {
  559. possibleCenters.RemoveAt(index);
  560. possibleCenters.Insert(index, center.combineEstimate(centerI.Value, centerJ.Value, estimatedModuleSize));
  561. found = true;
  562. break;
  563. }
  564. }
  565. if (!found)
  566. {
  567. var point = new FinderPattern(centerJ.Value, centerI.Value, estimatedModuleSize);
  568. possibleCenters.Add(point);
  569. if (resultPointCallback != null)
  570. {
  571. resultPointCallback(point);
  572. }
  573. }
  574. return true;
  575. }
  576. }
  577. return false;
  578. }
  579. /// <returns> number of rows we could safely skip during scanning, based on the first
  580. /// two finder patterns that have been located. In some cases their position will
  581. /// allow us to infer that the third pattern must lie below a certain point farther
  582. /// down in the image.
  583. /// </returns>
  584. private int findRowSkip()
  585. {
  586. int max = possibleCenters.Count;
  587. if (max <= 1)
  588. {
  589. return 0;
  590. }
  591. ResultPoint firstConfirmedCenter = null;
  592. foreach (var center in possibleCenters)
  593. {
  594. if (center.Count >= CENTER_QUORUM)
  595. {
  596. if (firstConfirmedCenter == null)
  597. {
  598. firstConfirmedCenter = center;
  599. }
  600. else
  601. {
  602. // We have two confirmed centers
  603. // How far down can we skip before resuming looking for the next
  604. // pattern? In the worst case, only the difference between the
  605. // difference in the x / y coordinates of the two centers.
  606. // This is the case where you find top left last.
  607. hasSkipped = true;
  608. //UPGRADE_WARNING: Data types in Visual C# might be different. Verify the accuracy of narrowing conversions. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1042'"
  609. return (int)(Math.Abs(firstConfirmedCenter.X - center.X) - Math.Abs(firstConfirmedCenter.Y - center.Y)) / 2;
  610. }
  611. }
  612. }
  613. return 0;
  614. }
  615. /// <returns> true iff we have found at least 3 finder patterns that have been detected
  616. /// at least {@link #CENTER_QUORUM} times each, and, the estimated module size of the
  617. /// candidates is "pretty similar"
  618. /// </returns>
  619. private bool haveMultiplyConfirmedCenters()
  620. {
  621. int confirmedCount = 0;
  622. float totalModuleSize = 0.0f;
  623. int max = possibleCenters.Count;
  624. foreach (var pattern in possibleCenters)
  625. {
  626. if (pattern.Count >= CENTER_QUORUM)
  627. {
  628. confirmedCount++;
  629. totalModuleSize += pattern.EstimatedModuleSize;
  630. }
  631. }
  632. if (confirmedCount < 3)
  633. {
  634. return false;
  635. }
  636. // OK, we have at least 3 confirmed centers, but, it's possible that one is a "false positive"
  637. // and that we need to keep looking. We detect this by asking if the estimated module sizes
  638. // vary too much. We arbitrarily say that when the total deviation from average exceeds
  639. // 5% of the total module size estimates, it's too much.
  640. float average = totalModuleSize / max;
  641. float totalDeviation = 0.0f;
  642. for (int i = 0; i < max; i++)
  643. {
  644. var pattern = possibleCenters[i];
  645. totalDeviation += Math.Abs(pattern.EstimatedModuleSize - average);
  646. }
  647. return totalDeviation <= 0.05f * totalModuleSize;
  648. }
  649. /// <returns> the 3 best {@link FinderPattern}s from our list of candidates. The "best" are
  650. /// those that have been detected at least {@link #CENTER_QUORUM} times, and whose module
  651. /// size differs from the average among those patterns the least
  652. /// </returns>
  653. private FinderPattern[] selectBestPatterns()
  654. {
  655. int startSize = possibleCenters.Count;
  656. if (startSize < 3)
  657. {
  658. // Couldn't find enough finder patterns
  659. return null;
  660. }
  661. // Filter outlier possibilities whose module size is too different
  662. if (startSize > 3)
  663. {
  664. // But we can only afford to do so if we have at least 4 possibilities to choose from
  665. float totalModuleSize = 0.0f;
  666. float square = 0.0f;
  667. foreach (var center in possibleCenters)
  668. {
  669. float size = center.EstimatedModuleSize;
  670. totalModuleSize += size;
  671. square += size * size;
  672. }
  673. float average = totalModuleSize / startSize;
  674. float stdDev = (float)Math.Sqrt(square / startSize - average * average);
  675. possibleCenters.Sort(new FurthestFromAverageComparator(average));
  676. float limit = Math.Max(0.2f * average, stdDev);
  677. for (int i = 0; i < possibleCenters.Count && possibleCenters.Count > 3; i++)
  678. {
  679. FinderPattern pattern = possibleCenters[i];
  680. if (Math.Abs(pattern.EstimatedModuleSize - average) > limit)
  681. {
  682. possibleCenters.RemoveAt(i);
  683. i--;
  684. }
  685. }
  686. }
  687. if (possibleCenters.Count > 3)
  688. {
  689. // Throw away all but those first size candidate points we found.
  690. float totalModuleSize = 0.0f;
  691. foreach (var possibleCenter in possibleCenters)
  692. {
  693. totalModuleSize += possibleCenter.EstimatedModuleSize;
  694. }
  695. float average = totalModuleSize / possibleCenters.Count;
  696. possibleCenters.Sort(new CenterComparator(average));
  697. //possibleCenters.subList(3, possibleCenters.Count).clear();
  698. possibleCenters = possibleCenters.GetRange(0, 3);
  699. }
  700. return new[]
  701. {
  702. possibleCenters[0],
  703. possibleCenters[1],
  704. possibleCenters[2]
  705. };
  706. }
  707. /// <summary>
  708. /// Orders by furthest from average
  709. /// </summary>
  710. private sealed class FurthestFromAverageComparator : IComparer<FinderPattern>
  711. {
  712. private readonly float average;
  713. public FurthestFromAverageComparator(float f)
  714. {
  715. average = f;
  716. }
  717. public int Compare(FinderPattern x, FinderPattern y)
  718. {
  719. float dA = Math.Abs(y.EstimatedModuleSize - average);
  720. float dB = Math.Abs(x.EstimatedModuleSize - average);
  721. return dA < dB ? -1 : dA == dB ? 0 : 1;
  722. }
  723. }
  724. /// <summary> <p>Orders by {@link FinderPattern#getCount()}, descending.</p></summary>
  725. private sealed class CenterComparator : IComparer<FinderPattern>
  726. {
  727. private readonly float average;
  728. public CenterComparator(float f)
  729. {
  730. average = f;
  731. }
  732. public int Compare(FinderPattern x, FinderPattern y)
  733. {
  734. if (y.Count == x.Count)
  735. {
  736. float dA = Math.Abs(y.EstimatedModuleSize - average);
  737. float dB = Math.Abs(x.EstimatedModuleSize - average);
  738. return dA < dB ? 1 : dA == dB ? 0 : -1;
  739. }
  740. return y.Count - x.Count;
  741. }
  742. }
  743. }
  744. }