secureid_ocr.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640
  1. #include <jni.h>
  2. #include <android/bitmap.h>
  3. #include <android/asset_manager.h>
  4. #include <android/asset_manager_jni.h>
  5. #include <android/log.h>
  6. #include <vector>
  7. #include <utility>
  8. #include <string>
  9. #include <math.h>
  10. #include <stdint.h>
  11. #include <stdlib.h>
  12. #include <stdio.h>
  13. #include <string.h>
  14. #include <libyuv.h>
  15. #include "fast-edge.h"
  16. #include "genann.h"
  17. #ifndef max
  18. #define max(a, b) (a>b ? a : b)
  19. #define min(a, b) (a<b ? a : b)
  20. #endif
  21. #define TAG "ocr"
  22. #define _LOG_WRAP(...) __VA_ARGS__
  23. #define LOGV(...) {__android_log_print(ANDROID_LOG_VERBOSE, TAG, _LOG_WRAP(__VA_ARGS__));}
  24. #define LOGD(...) {__android_log_print(ANDROID_LOG_DEBUG, TAG, _LOG_WRAP(__VA_ARGS__));}
  25. #define LOGI(...) {__android_log_print(ANDROID_LOG_INFO, TAG, _LOG_WRAP(__VA_ARGS__));}
  26. #define LOGW(...) {__android_log_print(ANDROID_LOG_WARN, TAG, _LOG_WRAP(__VA_ARGS__));}
  27. #define LOGE(...) {__android_log_print(ANDROID_LOG_ERROR, TAG, _LOG_WRAP(__VA_ARGS__));}
  28. namespace ocr{
  29. struct line{
  30. double theta;
  31. double r;
  32. };
  33. std::vector<line> detectLines(struct image* img, int threshold){
  34. // The size of the neighbourhood in which to search for other local maxima
  35. const int neighbourhoodSize = 4;
  36. // How many discrete values of theta shall we check?
  37. const int maxTheta = 180;
  38. // Using maxTheta, work out the step
  39. const double thetaStep = M_PI / maxTheta;
  40. int width=img->width;
  41. int height=img->height;
  42. // Calculate the maximum height the hough array needs to have
  43. int houghHeight = (int) (sqrt(2.0) * max(height, width)) / 2;
  44. // Double the height of the hough array to cope with negative r values
  45. int doubleHeight = 2 * houghHeight;
  46. // Create the hough array
  47. int* houghArray = new int[maxTheta*doubleHeight];
  48. memset(houghArray, 0, sizeof(int)*maxTheta*doubleHeight);
  49. // Find edge points and vote in array
  50. int centerX = width / 2;
  51. int centerY = height / 2;
  52. // Count how many points there are
  53. int numPoints = 0;
  54. // cache the values of sin and cos for faster processing
  55. double* sinCache = new double[maxTheta];
  56. double* cosCache = new double[maxTheta];
  57. for (int t = 0; t < maxTheta; t++) {
  58. double realTheta = t * thetaStep;
  59. sinCache[t] = sin(realTheta);
  60. cosCache[t] = cos(realTheta);
  61. }
  62. // Now find edge points and update the hough array
  63. for (int x = 0; x < width; x++) {
  64. for (int y = 0; y < height; y++) {
  65. // Find non-black pixels
  66. if ((img->pixel_data[y*width+x] & 0x000000ff) != 0) {
  67. // Go through each value of theta
  68. for (int t = 0; t < maxTheta; t++) {
  69. //Work out the r values for each theta step
  70. int r = (int) (((x - centerX) * cosCache[t]) + ((y - centerY) * sinCache[t]));
  71. // this copes with negative values of r
  72. r += houghHeight;
  73. if (r < 0 || r >= doubleHeight) continue;
  74. // Increment the hough array
  75. houghArray[t*doubleHeight+r]++;
  76. }
  77. numPoints++;
  78. }
  79. }
  80. }
  81. // Initialise the vector of lines that we'll return
  82. std::vector<line> lines;
  83. // Only proceed if the hough array is not empty
  84. if (numPoints == 0){
  85. delete[] houghArray;
  86. delete[] sinCache;
  87. delete[] cosCache;
  88. return lines;
  89. }
  90. // Search for local peaks above threshold to draw
  91. for (int t = 0; t < maxTheta; t++) {
  92. //loop:
  93. for (int r = neighbourhoodSize; r < doubleHeight - neighbourhoodSize; r++) {
  94. // Only consider points above threshold
  95. if (houghArray[t*doubleHeight+r] > threshold) {
  96. int peak = houghArray[t*doubleHeight+r];
  97. // Check that this peak is indeed the local maxima
  98. for (int dx = -neighbourhoodSize; dx <= neighbourhoodSize; dx++) {
  99. for (int dy = -neighbourhoodSize; dy <= neighbourhoodSize; dy++) {
  100. int dt = t + dx;
  101. int dr = r + dy;
  102. if (dt < 0) dt = dt + maxTheta;
  103. else if (dt >= maxTheta) dt = dt - maxTheta;
  104. if (houghArray[dt*doubleHeight+dr] > peak) {
  105. // found a bigger point nearby, skip
  106. goto loop;
  107. }
  108. }
  109. }
  110. // calculate the true value of theta
  111. double theta = t * thetaStep;
  112. // add the line to the vector
  113. line l={theta, (double)r-houghHeight};
  114. lines.push_back(l);
  115. }
  116. loop:
  117. continue;
  118. }
  119. }
  120. delete[] houghArray;
  121. delete[] sinCache;
  122. delete[] cosCache;
  123. return lines;
  124. }
  125. void binarizeBitmapPart(uint32_t* inPixels, unsigned char* outPixels, size_t width, size_t height, size_t inStride, size_t outStride){
  126. uint32_t histogram[256]={0};
  127. uint32_t intensitySum=0;
  128. for(unsigned int y=0;y<height;y++){
  129. for(unsigned int x=0;x<width;x++){
  130. uint32_t px=inPixels[y*inStride/sizeof(uint32_t)+x];
  131. int l=(((px & 0xFF)+((px & 0xFF00) >> 8)+((px & 0xFF0000) >> 16))/3);
  132. outPixels[y*outStride+x]=(unsigned char)l;
  133. histogram[l]++;
  134. intensitySum+=l;
  135. }
  136. }
  137. int threshold=0;
  138. double best_sigma = 0.0;
  139. int first_class_pixel_count = 0;
  140. int first_class_intensity_sum = 0;
  141. for (int thresh = 0; thresh < 255; ++thresh) {
  142. first_class_pixel_count += histogram[thresh];
  143. first_class_intensity_sum += thresh * histogram[thresh];
  144. double first_class_prob = first_class_pixel_count / (double) (width*height);
  145. double second_class_prob = 1.0 - first_class_prob;
  146. double first_class_mean = first_class_intensity_sum / (double) first_class_pixel_count;
  147. double second_class_mean = (intensitySum - first_class_intensity_sum)
  148. / (double) ((width*height) - first_class_pixel_count);
  149. double mean_delta = first_class_mean - second_class_mean;
  150. double sigma = first_class_prob * second_class_prob * mean_delta * mean_delta;
  151. if (sigma > best_sigma) {
  152. best_sigma = sigma;
  153. threshold = thresh;
  154. }
  155. }
  156. for(unsigned int y=0;y<height;y++){
  157. for(unsigned int x=0;x<width;x++){
  158. uint32_t px=inPixels[y*inStride/sizeof(uint32_t)+x];
  159. outPixels[y*outStride+x]=(px & 0xFF)<threshold && ((px & 0xFF00) >> 8)<threshold && ((px & 0xFF0000) >> 16)<threshold ? (unsigned char)255 : (unsigned char)0;
  160. }
  161. }
  162. }
  163. }
  164. extern "C" JNIEXPORT jintArray Java_org_telegram_messenger_MrzRecognizer_findCornerPoints(JNIEnv* env, jclass clasz, jobject bitmap){
  165. AndroidBitmapInfo info={0};
  166. if(AndroidBitmap_getInfo(env, bitmap, &info)!=ANDROID_BITMAP_RESULT_SUCCESS){
  167. return NULL;
  168. }
  169. if(info.format!=ANDROID_BITMAP_FORMAT_RGBA_8888){
  170. return NULL;
  171. }
  172. //LOGD("Bitmap info: %d x %d, stride %d", info.width, info.height, info.stride);
  173. unsigned int width=info.width;
  174. unsigned int height=info.height;
  175. uint32_t* bitmapPixels;
  176. if(AndroidBitmap_lockPixels(env, bitmap, reinterpret_cast<void**>(&bitmapPixels))!=ANDROID_BITMAP_RESULT_SUCCESS){
  177. LOGE("AndroidBitmap_lockPixels failed!");
  178. return NULL;
  179. }
  180. struct ocr::image imgIn, imgOut;
  181. imgIn.width=imgOut.width=width;
  182. imgIn.height=imgOut.height=height;
  183. imgIn.pixel_data=(unsigned char*)malloc(width*height);
  184. imgOut.pixel_data=(unsigned char*)calloc(width*height, 1);
  185. for(unsigned int y=0;y<height;y++){
  186. for(unsigned int x=0;x<width;x++){
  187. uint32_t px=bitmapPixels[info.stride*y/sizeof(uint32_t)+x];
  188. imgIn.pixel_data[width*y+x]=(unsigned char) (((px & 0xFF)+((px & 0xFF00) >> 8)+((px & 0xFF0000) >> 16))/3);
  189. }
  190. }
  191. AndroidBitmap_unlockPixels(env, bitmap);
  192. ocr::canny_edge_detect(&imgIn, &imgOut);
  193. std::vector<ocr::line> lines=ocr::detectLines(&imgOut, 100);
  194. for(int i=0;i<width*height;i++){
  195. imgOut.pixel_data[i]/=2;
  196. }
  197. std::vector<std::vector<ocr::line>> parallelGroups;
  198. for(int i=0;i<36;i++){
  199. parallelGroups.emplace_back();
  200. }
  201. ocr::line* left=NULL;
  202. ocr::line* right=NULL;
  203. ocr::line* top=NULL;
  204. ocr::line* bottom=NULL;
  205. for(std::vector<ocr::line>::iterator l=lines.begin();l!=lines.end();){
  206. // remove lines at irrelevant angles
  207. if(!(l->theta>M_PI*0.4 && l->theta<M_PI*0.6) && !(l->theta<M_PI*0.1 || l->theta>M_PI*0.9)){
  208. l=lines.erase(l);
  209. continue;
  210. }
  211. // remove vertical lines close to the middle of the image
  212. if((l->theta<M_PI*0.1 || l->theta>M_PI*0.9) && abs((int)l->r)<height/4){
  213. l=lines.erase(l);
  214. continue;
  215. }
  216. // find the leftmost and rightmost lines
  217. if(l->theta<M_PI*0.1 || l->theta>M_PI*0.9){
  218. double rk=l->theta<0.5 ? 1.0 : -1.0;
  219. if(!left || left->r>l->r*rk){
  220. left=&*l;
  221. }
  222. if(!right || right->r<l->r*rk){
  223. right=&*l;
  224. }
  225. }
  226. // group parallel-ish lines with 5-degree increments
  227. parallelGroups[floor(l->theta/M_PI*36)].push_back(*l);
  228. ++l;
  229. }
  230. // the text on the page tends to produce a lot of parallel lines - so we assume the top & bottom edges of the page
  231. // are topmost & bottommost lines in the largest group of horizontal lines
  232. std::vector<ocr::line>& largestParallelGroup=parallelGroups[0];
  233. for(std::vector<std::vector<ocr::line>>::iterator group=parallelGroups.begin();group!=parallelGroups.end();++group){
  234. if(largestParallelGroup.size()<group->size())
  235. largestParallelGroup=*group;
  236. }
  237. for(std::vector<ocr::line>::iterator l=largestParallelGroup.begin();l!=largestParallelGroup.end();++l){
  238. // If the image is horizontal, we assume it's just the data page or an ID card so we're going for the topmost line.
  239. // If it's vertical, it likely contains both the data page and the page adjacent to it so we're going for the line that is closest to the center of the image.
  240. // Nobody in their right mind is going to be taking vertical pictures of ID cards, right?
  241. if(width>height){
  242. if(!top || top->r>l->r){
  243. top=&*l;
  244. }
  245. }else{
  246. if(!top || fabs(l->r)<fabs(top->r)){
  247. top=&*l;
  248. }
  249. }
  250. if(!bottom || bottom->r<l->r){
  251. bottom=&*l;
  252. }
  253. }
  254. jintArray result=NULL;
  255. if(top && bottom && left && right){
  256. //LOGI("bottom theta %f", bottom->theta);
  257. if(bottom->theta>1.65 || bottom->theta<1.55){
  258. //LOGD("left: %f, right: %f\n", left->r, right->r);
  259. int points[8]={0};
  260. bool foundTopLeft=false, foundTopRight=false, foundBottomLeft=false, foundBottomRight=false;
  261. double centerX=width/2.0;
  262. double centerY=height/2.0;
  263. double ltsin=sin(left->theta);
  264. double ltcos=cos(left->theta);
  265. double rtsin=sin(right->theta);
  266. double rtcos=cos(right->theta);
  267. double ttsin=sin(top->theta);
  268. double ttcos=cos(top->theta);
  269. double btsin=sin(bottom->theta);
  270. double btcos=cos(bottom->theta);
  271. for (int y = -((int)height)/4; y < (int)height; y++) {
  272. int lx = (int) (((left->r - ((y - centerY) * ltsin)) / ltcos) + centerX);
  273. int ty = (int) (((top->r - ((lx - centerX) * ttcos)) / ttsin) + centerY);
  274. if(ty==y){
  275. points[0]=lx;
  276. points[1]=y;
  277. foundTopLeft=true;
  278. if(foundTopRight)
  279. break;
  280. }
  281. int rx = (int) (((right->r - ((y - centerY) * rtsin)) / rtcos) + centerX);
  282. ty = (int) (((top->r - ((rx - centerX) * ttcos)) / ttsin) + centerY);
  283. if(ty==y){
  284. points[2]=rx;
  285. points[3]=y;
  286. foundTopRight=true;
  287. if(foundTopLeft)
  288. break;
  289. }
  290. }
  291. for (int y = height+height/3; y>=0; y--) {
  292. int lx = (int) (((left->r - ((y - centerY) * ltsin)) / ltcos) + centerX);
  293. int by = (int) (((bottom->r - ((lx - centerX) * btcos)) / btsin) + centerY);
  294. if(by==y){
  295. points[4]=lx;
  296. points[5]=y;
  297. foundBottomLeft=true;
  298. if(foundBottomRight)
  299. break;
  300. }
  301. int rx = (int) (((right->r - ((y - centerY) * rtsin)) / rtcos) + centerX);
  302. by = (int) (((bottom->r - ((rx - centerX) * btcos)) / btsin) + centerY);
  303. if(by==y){
  304. points[6]=rx;
  305. points[7]=y;
  306. foundBottomRight=true;
  307. if(foundBottomLeft)
  308. break;
  309. }
  310. }
  311. if(foundTopLeft && foundTopRight && foundBottomLeft && foundBottomRight){
  312. result=env->NewIntArray(8);
  313. env->SetIntArrayRegion(result, 0, 8, points);
  314. //LOGD("Points: (%d %d) (%d %d) (%d %d) (%d %d)", points[0], points[1], points[2], points[3], points[4], points[5], points[6], points[7]);
  315. }
  316. }else{
  317. //LOGD("No perspective correction needed");
  318. }
  319. }
  320. free(imgIn.pixel_data);
  321. free(imgOut.pixel_data);
  322. return result;
  323. }
  324. extern "C" JNIEXPORT jobjectArray Java_org_telegram_messenger_MrzRecognizer_binarizeAndFindCharacters(JNIEnv* env, jclass clasz, jobject inBmp, jobject outBmp){
  325. AndroidBitmapInfo inInfo={0}, outInfo={0};
  326. if(AndroidBitmap_getInfo(env, inBmp, &inInfo)!=ANDROID_BITMAP_RESULT_SUCCESS || AndroidBitmap_getInfo(env, outBmp, &outInfo)!=ANDROID_BITMAP_RESULT_SUCCESS){
  327. LOGE("AndroidBitmap_getInfo failed");
  328. return NULL;
  329. }
  330. if(inInfo.width!=outInfo.width || inInfo.height!=outInfo.height || inInfo.format!=ANDROID_BITMAP_FORMAT_RGBA_8888 || outInfo.format!=ANDROID_BITMAP_FORMAT_A_8){
  331. LOGE("bitmap validation failed");
  332. return NULL;
  333. }
  334. unsigned int height=inInfo.height;
  335. unsigned int width=inInfo.width;
  336. uint32_t* inPixels;
  337. unsigned char* outPixels;
  338. if(AndroidBitmap_lockPixels(env, inBmp, reinterpret_cast<void**>(&inPixels))!=ANDROID_BITMAP_RESULT_SUCCESS){
  339. LOGE("AndroidBitmap_lockPixels failed");
  340. return NULL;
  341. }
  342. if(AndroidBitmap_lockPixels(env, outBmp, reinterpret_cast<void**>(&outPixels))!=ANDROID_BITMAP_RESULT_SUCCESS){
  343. AndroidBitmap_unlockPixels(env, inBmp);
  344. LOGE("AndroidBitmap_lockPixels failed");
  345. return NULL;
  346. }
  347. for(unsigned int y=0;y<height;y+=120){
  348. for(unsigned int x=0; x<width; x+=120){
  349. int partWidth=x+120<width ? 120 : (width-x);
  350. int partHeight=y+120<height ? 120 : (height-y);
  351. ocr::binarizeBitmapPart(&inPixels[(y*inInfo.stride/sizeof(uint32_t))+x], outPixels+(y*outInfo.stride)+x, partWidth, partHeight, inInfo.stride, outInfo.stride);
  352. }
  353. }
  354. // remove any single pixels without adjacent ones - these are usually noise
  355. for(unsigned int y=height/2;y<height-1;y++){
  356. unsigned int yOffset=y*outInfo.stride;
  357. unsigned int yOffsetPrev=(y-1)*outInfo.stride;
  358. unsigned int yOffsetNext=(y+1)*outInfo.stride;
  359. for(unsigned int x=1;x<width-1;x++){
  360. int pixelCount=0;
  361. if(outPixels[yOffsetPrev+x-1]!=0)
  362. pixelCount++;
  363. if(outPixels[yOffsetPrev+x]!=0)
  364. pixelCount++;
  365. if(outPixels[yOffsetPrev+x+1]!=0)
  366. pixelCount++;
  367. if(outPixels[yOffset+x-1]!=0)
  368. pixelCount++;
  369. if(outPixels[yOffset+x]!=0)
  370. pixelCount++;
  371. if(outPixels[yOffset+x+1]!=0)
  372. pixelCount++;
  373. if(outPixels[yOffsetNext+x-1]!=0)
  374. pixelCount++;
  375. if(outPixels[yOffsetNext+x]!=0)
  376. pixelCount++;
  377. if(outPixels[yOffsetNext+x+1]!=0)
  378. pixelCount++;
  379. if(pixelCount<3)
  380. outPixels[yOffset+x]=0;
  381. }
  382. }
  383. // search from the bottom up for continuous areas of mostly empty pixels
  384. unsigned int consecutiveEmptyRows=0;
  385. std::vector<std::pair<unsigned int, unsigned int>> emptyAreaYs;
  386. for(unsigned int y=height-1;y>=height/2;y--){
  387. unsigned int consecutiveEmptyPixels=0;
  388. unsigned int maxEmptyPixels=0;
  389. for(unsigned int x=0;x<width;x++){
  390. if(outPixels[y*outInfo.stride+x]==0){
  391. consecutiveEmptyPixels++;
  392. }else{
  393. maxEmptyPixels=max(maxEmptyPixels, consecutiveEmptyPixels);
  394. consecutiveEmptyPixels=0;
  395. }
  396. }
  397. maxEmptyPixels=max(maxEmptyPixels, consecutiveEmptyPixels);
  398. if(maxEmptyPixels>width/10*8){
  399. consecutiveEmptyRows++;
  400. }else if(consecutiveEmptyRows>0){
  401. emptyAreaYs.emplace_back(y, y+consecutiveEmptyRows);
  402. consecutiveEmptyRows=0;
  403. }
  404. }
  405. std::vector<jobjectArray> result;
  406. jclass rectClass=env->FindClass("android/graphics/Rect");
  407. jmethodID rectConstructor=env->GetMethodID(rectClass, "<init>", "(IIII)V");
  408. // using the areas found above, do the same thing but horizontally and between them in an attempt to ultimately find the bounds of the MRZ characters
  409. for(std::vector<std::pair<unsigned int, unsigned int>>::iterator p=emptyAreaYs.begin();p!=emptyAreaYs.end();++p){
  410. std::vector<std::pair<unsigned int, unsigned int>>::iterator next=std::next(p);
  411. if(next!=emptyAreaYs.end()){
  412. unsigned int lineHeight=p->first-next->second;
  413. // An MRZ line can't really be this thin so this probably isn't one
  414. if(lineHeight<10)
  415. continue;
  416. unsigned int consecutiveEmptyCols=0;
  417. std::vector<std::pair<unsigned int, unsigned int>> emptyAreaXs;
  418. for(unsigned int x=0;x<width;x++){
  419. unsigned int consecutiveEmptyPixels=0;
  420. unsigned int maxEmptyPixels=0;
  421. unsigned int bottomFilledPixels=0; // count these separately because we want those L's recognized correctly
  422. for(unsigned int y=next->second;y<p->first;y++){
  423. if(outPixels[y*outInfo.stride+x]==0){
  424. consecutiveEmptyPixels++;
  425. }else{
  426. maxEmptyPixels=max(maxEmptyPixels, consecutiveEmptyPixels);
  427. consecutiveEmptyPixels=0;
  428. if(y>p->first-3)
  429. bottomFilledPixels++;
  430. }
  431. }
  432. maxEmptyPixels=consecutiveEmptyPixels;
  433. if(lineHeight-maxEmptyPixels<=lineHeight/15 && bottomFilledPixels==0){
  434. consecutiveEmptyCols++;
  435. }else if(consecutiveEmptyCols>0){
  436. emptyAreaXs.emplace_back(x-consecutiveEmptyCols, x);
  437. consecutiveEmptyCols=0;
  438. }
  439. }
  440. if(consecutiveEmptyCols>0){
  441. emptyAreaXs.emplace_back(width-consecutiveEmptyCols, width);
  442. }
  443. if(emptyAreaXs.size()>30){
  444. bool foundLeftPadding=false;
  445. std::vector<jobject> rects;
  446. for(std::vector<std::pair<unsigned int, unsigned int>>::iterator h=emptyAreaXs.begin();h!=emptyAreaXs.end();++h){
  447. std::vector<std::pair<unsigned int, unsigned int>>::iterator nextH=std::next(h);
  448. if(!foundLeftPadding && h->second-h->first>width/35){
  449. foundLeftPadding=true;
  450. }else if(foundLeftPadding && h->second-h->first>width/30){
  451. if(rects.size()>=30){
  452. break;
  453. }else{
  454. // restart the search because now we've (hopefully) found the real padding
  455. rects.erase(rects.begin(), rects.end());
  456. }
  457. }
  458. if(nextH!=emptyAreaXs.end() && foundLeftPadding){
  459. unsigned int top=next->second;
  460. unsigned int bottom=p->first;
  461. // move the top and bottom edges towards each other as part of normalization
  462. for(unsigned int y=top;y<bottom;y++){
  463. bool found=false;
  464. for(unsigned int x=h->second; x<nextH->first; x++){
  465. if(outPixels[y*outInfo.stride+x]!=0){
  466. top=y;
  467. found=true;
  468. break;
  469. }
  470. }
  471. if(found)
  472. break;
  473. }
  474. for(unsigned int y=bottom;y>top;y--){
  475. bool found=false;
  476. for(unsigned int x=h->second; x<nextH->first; x++){
  477. if(outPixels[y*outInfo.stride+x]!=0){
  478. bottom=y;
  479. found=true;
  480. break;
  481. }
  482. }
  483. if(found)
  484. break;
  485. }
  486. if(bottom-top<lineHeight/4)
  487. continue;
  488. if(rects.size()<44){
  489. jobject rect=env->NewObject(rectClass, rectConstructor, h->second, top, nextH->first, bottom);
  490. rects.push_back(rect);
  491. }
  492. }
  493. }
  494. jobjectArray lineArray=env->NewObjectArray(static_cast<jsize>(rects.size()), rectClass, NULL);
  495. int i=0;
  496. for(std::vector<jobject>::iterator r=rects.begin();r!=rects.end();++r){
  497. env->SetObjectArrayElement(lineArray, i, *r);
  498. i++;
  499. }
  500. result.push_back(lineArray);
  501. if((rects.size()>=44 && result.size()==2) || (rects.size()>=30 && result.size()==3)){
  502. break;
  503. }
  504. }
  505. }
  506. }
  507. AndroidBitmap_unlockPixels(env, inBmp);
  508. AndroidBitmap_unlockPixels(env, outBmp);
  509. if(result.empty())
  510. return NULL;
  511. jobjectArray resultArray=env->NewObjectArray(static_cast<jsize>(result.size()), env->GetObjectClass(result[0]), NULL);
  512. int i=0;
  513. for(std::vector<jobjectArray>::iterator a=result.begin();a!=result.end();++a){
  514. env->SetObjectArrayElement(resultArray, static_cast<jsize>(result.size()-i-1), *a);
  515. i++;
  516. }
  517. return resultArray;
  518. }
  519. extern "C" JNIEXPORT jstring Java_org_telegram_messenger_MrzRecognizer_performRecognition(JNIEnv* env, jclass clasz, jobject bitmap, jint numRows, jint numCols, jobject jAssetManager){
  520. AAssetManager* assets=AAssetManager_fromJava(env, jAssetManager);
  521. AAsset* nnData=AAssetManager_open(assets, "secureid_ocr_nn.dat", AASSET_MODE_STREAMING);
  522. if(!nnData){
  523. LOGE("AAssetManager_open failed");
  524. return NULL;
  525. }
  526. struct genann* ann=genann_init(150, 1, 90, 37);
  527. AAsset_read(nnData, ann->weight, sizeof(double)*ann->total_weights);
  528. AAsset_close(nnData);
  529. std::string res;
  530. const char* alphabet="ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890<";
  531. AndroidBitmapInfo info;
  532. unsigned char* pixels;
  533. AndroidBitmap_getInfo(env, bitmap, &info);
  534. if(AndroidBitmap_lockPixels(env, bitmap, reinterpret_cast<void**>(&pixels))!=ANDROID_BITMAP_RESULT_SUCCESS){
  535. LOGE("AndroidBitmap_lockPixels failed");
  536. genann_free(ann);
  537. return NULL;
  538. }
  539. double nnInput[150];
  540. for(int row=0;row<numRows;row++){
  541. for(int col=0;col<numCols;col++){
  542. unsigned int offX=static_cast<unsigned int>(col*10);
  543. unsigned int offY=static_cast<unsigned int>(row*15);
  544. for(unsigned int y=0;y<15;y++){
  545. for(unsigned int x=0;x<10;x++){
  546. nnInput[y*10+x]=(double)pixels[(offY+y)*info.stride+offX+x]/255.0;
  547. }
  548. }
  549. const double* nnOut=genann_run(ann, nnInput);
  550. unsigned int bestIndex=0;
  551. for(unsigned int i=0;i<37;i++){
  552. if(nnOut[i]>nnOut[bestIndex])
  553. bestIndex=i;
  554. }
  555. res+=alphabet[bestIndex];
  556. }
  557. if(row!=numRows-1)
  558. res+="\n";
  559. }
  560. genann_free(ann);
  561. return env->NewStringUTF(res.c_str());
  562. }
  563. extern "C" JNIEXPORT void Java_org_telegram_messenger_MrzRecognizer_setYuvBitmapPixels(JNIEnv* env, jclass clasz, jobject bitmap, jbyteArray jpixels){
  564. jbyte* _pixels=env->GetByteArrayElements(jpixels, NULL);
  565. uint8_t* pixels=reinterpret_cast<uint8_t*>(_pixels);
  566. AndroidBitmapInfo info;
  567. uint32_t* bpixels;
  568. if(AndroidBitmap_getInfo(env, bitmap, &info)==ANDROID_BITMAP_RESULT_SUCCESS){
  569. if(info.format==ANDROID_BITMAP_FORMAT_RGBA_8888){
  570. if(AndroidBitmap_lockPixels(env, bitmap, reinterpret_cast<void**>(&bpixels))==ANDROID_BITMAP_RESULT_SUCCESS){
  571. libyuv::NV12ToARGB(pixels, info.width, pixels+info.width*info.height, info.width, reinterpret_cast<uint8_t*>(bpixels), info.stride, info.width, info.height);
  572. AndroidBitmap_unlockPixels(env, bitmap);
  573. }
  574. }
  575. }
  576. env->ReleaseByteArrayElements(jpixels, _pixels, JNI_ABORT);
  577. }