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

ZXPDF417Detector.m 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. /*
  2. * Copyright 2012 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. #import "ZXBitArray.h"
  17. #import "ZXBitMatrix.h"
  18. #import "ZXBinaryBitmap.h"
  19. #import "ZXDecodeHints.h"
  20. #import "ZXErrors.h"
  21. #import "ZXGridSampler.h"
  22. #import "ZXMathUtils.h"
  23. #import "ZXPDF417Detector.h"
  24. #import "ZXPDF417DetectorResult.h"
  25. #import "ZXPerspectiveTransform.h"
  26. #import "ZXResultPoint.h"
  27. const int ZX_PDF417_INDEXES_START_PATTERN[] = {0, 4, 1, 5};
  28. const int ZX_PDF417_INDEXES_STOP_PATTERN[] = {6, 2, 7, 3};
  29. const float ZX_PDF417_MAX_AVG_VARIANCE = 0.42f;
  30. const float ZX_PDF417_MAX_INDIVIDUAL_VARIANCE = 0.8f;
  31. // B S B S B S B S Bar/Space pattern
  32. // 11111111 0 1 0 1 0 1 000
  33. const int ZX_PDF417_DETECTOR_START_PATTERN[] = {8, 1, 1, 1, 1, 1, 1, 3};
  34. // 1111111 0 1 000 1 0 1 00 1
  35. const int ZX_PDF417_DETECTOR_STOP_PATTERN[] = {7, 1, 1, 3, 1, 1, 1, 2, 1};
  36. const int ZX_PDF417_MAX_PIXEL_DRIFT = 3;
  37. const int ZX_PDF417_MAX_PATTERN_DRIFT = 5;
  38. // if we set the value too low, then we don't detect the correct height of the bar if the start patterns are damaged.
  39. // if we set the value too high, then we might detect the start pattern from a neighbor barcode.
  40. const int ZX_PDF417_SKIPPED_ROW_COUNT_MAX = 25;
  41. // A PDF471 barcode should have at least 3 rows, with each row being >= 3 times the module width. Therefore it should be at least
  42. // 9 pixels tall. To be conservative, we use about half the size to ensure we don't miss it.
  43. const int ZX_PDF417_ROW_STEP = 5;
  44. const int ZX_PDF417_BARCODE_MIN_HEIGHT = 10;
  45. @implementation ZXPDF417Detector
  46. + (ZXPDF417DetectorResult *)detect:(ZXBinaryBitmap *)image hints:(ZXDecodeHints *)hints multiple:(BOOL)multiple error:(NSError **)error {
  47. // TODO detection improvement, tryHarder could try several different luminance thresholds/blackpoints or even
  48. // different binarizers
  49. //boolean tryHarder = hints != null && hints.containsKey(DecodeHintType.TRY_HARDER);
  50. ZXBitMatrix *bitMatrix = [image blackMatrixWithError:error];
  51. NSArray *barcodeCoordinates = [self detect:multiple bitMatrix:bitMatrix error:error];
  52. if (!barcodeCoordinates) {
  53. return nil;
  54. }
  55. if ([barcodeCoordinates count] == 0) {
  56. bitMatrix = [bitMatrix copy];
  57. [bitMatrix rotate180];
  58. barcodeCoordinates = [self detect:multiple bitMatrix:bitMatrix error:error];
  59. if (!barcodeCoordinates) {
  60. return nil;
  61. }
  62. }
  63. return [[ZXPDF417DetectorResult alloc] initWithBits:bitMatrix points:barcodeCoordinates];
  64. }
  65. /**
  66. * Detects PDF417 codes in an image. Only checks 0 degree rotation
  67. * @param multiple if true, then the image is searched for multiple codes. If false, then at most one code will
  68. * be found and returned
  69. * @param bitMatrix bit matrix to detect barcodes in
  70. * @return List of ResultPoint arrays containing the coordinates of found barcodes
  71. */
  72. + (NSArray *)detect:(BOOL)multiple bitMatrix:(ZXBitMatrix *)bitMatrix error:(NSError **)error {
  73. NSMutableArray *barcodeCoordinates = [NSMutableArray array];
  74. int row = 0;
  75. int column = 0;
  76. BOOL foundBarcodeInRow = NO;
  77. while (row < bitMatrix.height) {
  78. NSArray *vertices = [self findVertices:bitMatrix startRow:row startColumn:column];
  79. if (vertices[0] == [NSNull null] && vertices[3] == [NSNull null]) {
  80. if (!foundBarcodeInRow) {
  81. // we didn't find any barcode so that's the end of searching
  82. break;
  83. }
  84. // we didn't find a barcode starting at the given column and row. Try again from the first column and slightly
  85. // below the lowest barcode we found so far.
  86. foundBarcodeInRow = NO;
  87. column = 0;
  88. for (NSArray *barcodeCoordinate in barcodeCoordinates) {
  89. if (barcodeCoordinate[1] != [NSNull null]) {
  90. row = MAX(row, (int) [(ZXResultPoint *)barcodeCoordinate[1] y]);
  91. }
  92. if (barcodeCoordinate[3] != [NSNull null]) {
  93. row = MAX(row, (int) [(ZXResultPoint *)barcodeCoordinate[3] y]);
  94. }
  95. }
  96. row += ZX_PDF417_ROW_STEP;
  97. continue;
  98. }
  99. foundBarcodeInRow = YES;
  100. [barcodeCoordinates addObject:vertices];
  101. if (!multiple) {
  102. break;
  103. }
  104. // if we didn't find a right row indicator column, then continue the search for the next barcode after the
  105. // start pattern of the barcode just found.
  106. if (vertices[2] != [NSNull null]) {
  107. column = (int) [(ZXResultPoint *)vertices[2] x];
  108. row = (int) [(ZXResultPoint *)vertices[2] y];
  109. } else {
  110. column = (int) [(ZXResultPoint *)vertices[4] x];
  111. row = (int) [(ZXResultPoint *)vertices[4] y];
  112. }
  113. }
  114. return barcodeCoordinates;
  115. }
  116. /**
  117. * Locate the vertices and the codewords area of a black blob using the Start
  118. * and Stop patterns as locators.
  119. *
  120. * @param matrix the scanned barcode image.
  121. * @return an array containing the vertices:
  122. * vertices[0] x, y top left barcode
  123. * vertices[1] x, y bottom left barcode
  124. * vertices[2] x, y top right barcode
  125. * vertices[3] x, y bottom right barcode
  126. * vertices[4] x, y top left codeword area
  127. * vertices[5] x, y bottom left codeword area
  128. * vertices[6] x, y top right codeword area
  129. * vertices[7] x, y bottom right codeword area
  130. */
  131. + (NSMutableArray *)findVertices:(ZXBitMatrix *)matrix startRow:(int)startRow startColumn:(int)startColumn {
  132. int height = matrix.height;
  133. int width = matrix.width;
  134. NSMutableArray *result = [NSMutableArray arrayWithCapacity:8];
  135. for (int i = 0; i < 8; i++) {
  136. [result addObject:[NSNull null]];
  137. }
  138. [self copyToResult:result
  139. tmpResult:[self findRowsWithPattern:matrix
  140. height:height
  141. width:width
  142. startRow:startRow
  143. startColumn:startColumn
  144. pattern:ZX_PDF417_DETECTOR_START_PATTERN
  145. patternLen:sizeof(ZX_PDF417_DETECTOR_START_PATTERN) / sizeof(int)]
  146. destinationIndexes:ZX_PDF417_INDEXES_START_PATTERN
  147. length:sizeof(ZX_PDF417_INDEXES_START_PATTERN) / sizeof(int)];
  148. if (result[4] != [NSNull null]) {
  149. startColumn = (int) [(ZXResultPoint *)result[4] x];
  150. startRow = (int) [(ZXResultPoint *)result[4] y];
  151. }
  152. [self copyToResult:result
  153. tmpResult:[self findRowsWithPattern:matrix
  154. height:height
  155. width:width
  156. startRow:startRow
  157. startColumn:startColumn
  158. pattern:ZX_PDF417_DETECTOR_STOP_PATTERN
  159. patternLen:sizeof(ZX_PDF417_DETECTOR_STOP_PATTERN) / sizeof(int)]
  160. destinationIndexes:ZX_PDF417_INDEXES_STOP_PATTERN
  161. length:sizeof(ZX_PDF417_INDEXES_STOP_PATTERN) / sizeof(int)];
  162. return result;
  163. }
  164. + (void)copyToResult:(NSMutableArray *)result tmpResult:(NSMutableArray *)tmpResult destinationIndexes:(const int[])destinationIndexes length:(int)length {
  165. for (int i = 0; i < length; i++) {
  166. result[destinationIndexes[i]] = tmpResult[i];
  167. }
  168. }
  169. + (NSMutableArray *)findRowsWithPattern:(ZXBitMatrix *)matrix height:(int)height width:(int)width startRow:(int)startRow
  170. startColumn:(int)startColumn pattern:(const int[])pattern patternLen:(int)patternLen {
  171. NSMutableArray *result = [NSMutableArray array];
  172. for (int i = 0; i < 4; i++) {
  173. [result addObject:[NSNull null]];
  174. }
  175. BOOL found = NO;
  176. int counters[patternLen];
  177. memset(counters, 0, patternLen * sizeof(int));
  178. for (; startRow < height; startRow += ZX_PDF417_ROW_STEP) {
  179. NSRange loc = [self findGuardPattern:matrix column:startColumn row:startRow width:width whiteFirst:false pattern:pattern patternLen:patternLen counters:counters];
  180. if (loc.location != NSNotFound) {
  181. while (startRow > 0) {
  182. NSRange previousRowLoc = [self findGuardPattern:matrix column:startColumn row:--startRow width:width whiteFirst:false pattern:pattern patternLen:patternLen counters:counters];
  183. if (previousRowLoc.location != NSNotFound) {
  184. loc = previousRowLoc;
  185. } else {
  186. startRow++;
  187. break;
  188. }
  189. }
  190. result[0] = [[ZXResultPoint alloc] initWithX:loc.location y:startRow];
  191. result[1] = [[ZXResultPoint alloc] initWithX:NSMaxRange(loc) y:startRow];
  192. found = YES;
  193. break;
  194. }
  195. }
  196. int stopRow = startRow + 1;
  197. // Last row of the current symbol that contains pattern
  198. if (found) {
  199. int skippedRowCount = 0;
  200. NSRange previousRowLoc = NSMakeRange((NSUInteger) [(ZXResultPoint *)result[0] x], ((NSUInteger)[(ZXResultPoint *)result[1] x]) - ((NSUInteger)[(ZXResultPoint *)result[0] x]));
  201. for (; stopRow < height; stopRow++) {
  202. NSRange loc = [self findGuardPattern:matrix column:(int)previousRowLoc.location row:stopRow width:width whiteFirst:NO pattern:pattern patternLen:patternLen counters:counters];
  203. // a found pattern is only considered to belong to the same barcode if the start and end positions
  204. // don't differ too much. Pattern drift should be not bigger than two for consecutive rows. With
  205. // a higher number of skipped rows drift could be larger. To keep it simple for now, we allow a slightly
  206. // larger drift and don't check for skipped rows.
  207. if (loc.location != NSNotFound &&
  208. ABS((int)previousRowLoc.location - (int)loc.location) < ZX_PDF417_MAX_PATTERN_DRIFT &&
  209. ABS((int)NSMaxRange(previousRowLoc) - (int)NSMaxRange(loc)) < ZX_PDF417_MAX_PATTERN_DRIFT) {
  210. previousRowLoc = loc;
  211. skippedRowCount = 0;
  212. } else {
  213. if (skippedRowCount > ZX_PDF417_SKIPPED_ROW_COUNT_MAX) {
  214. break;
  215. } else {
  216. skippedRowCount++;
  217. }
  218. }
  219. }
  220. stopRow -= skippedRowCount + 1;
  221. result[2] = [[ZXResultPoint alloc] initWithX:previousRowLoc.location y:stopRow];
  222. result[3] = [[ZXResultPoint alloc] initWithX:NSMaxRange(previousRowLoc) y:stopRow];
  223. }
  224. if (stopRow - startRow < ZX_PDF417_BARCODE_MIN_HEIGHT) {
  225. for (int i = 0; i < 4; i++) {
  226. result[i] = [NSNull null];
  227. }
  228. }
  229. return result;
  230. }
  231. /**
  232. * @param matrix row of black/white values to search
  233. * @param column x position to start search
  234. * @param row y position to start search
  235. * @param width the number of pixels to search on this row
  236. * @param pattern pattern of counts of number of black and white pixels that are
  237. * being searched for as a pattern
  238. * @param counters array of counters, as long as pattern, to re-use
  239. * @return start/end horizontal offset of guard pattern, as an array of two ints.
  240. */
  241. + (NSRange)findGuardPattern:(ZXBitMatrix *)matrix
  242. column:(int)column
  243. row:(int)row
  244. width:(int)width
  245. whiteFirst:(BOOL)whiteFirst
  246. pattern:(const int[])pattern
  247. patternLen:(int)patternLen
  248. counters:(int *)counters {
  249. int patternLength = patternLen;
  250. memset(counters, 0, patternLength * sizeof(int));
  251. BOOL isWhite = whiteFirst;
  252. int patternStart = column;
  253. int pixelDrift = 0;
  254. // if there are black pixels left of the current pixel shift to the left, but only for ZX_PDF417_MAX_PIXEL_DRIFT pixels
  255. while ([matrix getX:patternStart y:row] && patternStart > 0 && pixelDrift++ < ZX_PDF417_MAX_PIXEL_DRIFT) {
  256. patternStart--;
  257. }
  258. int x = patternStart;
  259. int counterPosition = 0;
  260. for (;x < width; x++) {
  261. BOOL pixel = [matrix getX:x y:row];
  262. if (pixel ^ isWhite) {
  263. counters[counterPosition] = counters[counterPosition] + 1;
  264. } else {
  265. if (counterPosition == patternLength - 1) {
  266. if ([self patternMatchVariance:counters countersSize:patternLength pattern:pattern maxIndividualVariance:ZX_PDF417_MAX_INDIVIDUAL_VARIANCE] < ZX_PDF417_MAX_AVG_VARIANCE) {
  267. return NSMakeRange(patternStart, x - patternStart);
  268. }
  269. patternStart += counters[0] + counters[1];
  270. for (int y = 2; y < patternLength; y++) {
  271. counters[y - 2] = counters[y];
  272. }
  273. counters[patternLength - 2] = 0;
  274. counters[patternLength - 1] = 0;
  275. counterPosition--;
  276. } else {
  277. counterPosition++;
  278. }
  279. counters[counterPosition] = 1;
  280. isWhite = !isWhite;
  281. }
  282. }
  283. if (counterPosition == patternLength - 1) {
  284. if ([self patternMatchVariance:counters countersSize:patternLen pattern:pattern maxIndividualVariance:ZX_PDF417_MAX_INDIVIDUAL_VARIANCE] < ZX_PDF417_MAX_AVG_VARIANCE) {
  285. return NSMakeRange(patternStart, x - patternStart - 1);
  286. }
  287. }
  288. return NSMakeRange(NSNotFound, 0);
  289. }
  290. /**
  291. * Determines how closely a set of observed counts of runs of black/white
  292. * values matches a given target pattern. This is reported as the ratio of
  293. * the total variance from the expected pattern proportions across all
  294. * pattern elements, to the length of the pattern.
  295. *
  296. * @param counters observed counters
  297. * @param pattern expected pattern
  298. * @param maxIndividualVariance The most any counter can differ before we give up
  299. * @return ratio of total variance between counters and pattern compared to total pattern size
  300. */
  301. + (float)patternMatchVariance:(int *)counters countersSize:(int)countersSize pattern:(const int[])pattern maxIndividualVariance:(float)maxIndividualVariance {
  302. int numCounters = countersSize;
  303. int total = 0;
  304. int patternLength = 0;
  305. for (int i = 0; i < numCounters; i++) {
  306. total += counters[i];
  307. patternLength += pattern[i];
  308. }
  309. if (total < patternLength || patternLength == 0) {
  310. return FLT_MAX;
  311. }
  312. float unitBarWidth = (float) total / patternLength;
  313. maxIndividualVariance *= unitBarWidth;
  314. float totalVariance = 0.0f;
  315. for (int x = 0; x < numCounters; x++) {
  316. int counter = counters[x];
  317. float scaledPattern = pattern[x] * unitBarWidth;
  318. float variance = counter > scaledPattern ? counter - scaledPattern : scaledPattern - counter;
  319. if (variance > maxIndividualVariance) {
  320. return FLT_MAX;
  321. }
  322. totalVariance += variance;
  323. }
  324. return totalVariance / total;
  325. }
  326. @end