Ei kuvausta

xword.js 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. // vim:ts=4:sw=4:expandtab
  2. 'use strict';
  3. function plot(elementId, seriesList) {
  4. let plots = [];
  5. for (let i = 0; i < seriesList.length; i++) {
  6. let y = seriesList[i];
  7. let x = [];
  8. for (let i = 0; i < y.length; i++) {
  9. x.push(i);
  10. }
  11. plots.push({x: x, y: y});
  12. }
  13. Plotly.plot(document.getElementById(elementId), plots);
  14. }
  15. function removeColours(src, dst, threshold) {
  16. let from = src.data32S;
  17. let to = dst.data32S;
  18. let pixels = src.rows * src.cols;
  19. for (let i = 0; i < pixels; i++) {
  20. let pixel = from[i];
  21. let r = pixel & 0xFF;
  22. let g = (pixel >> 8) & 0xFF;
  23. let b = (pixel >> 16) & 0xFF;
  24. to[i] = from[i] | (Math.max(r, g, b) - Math.min(r, g, b) > threshold ? 0xFFFFFF : 0);
  25. }
  26. }
  27. function preprocessImage(src, dst, gaussianBlurSize, adaptiveThresholdBlockSize, adaptiveThresholdMeanAdjustment, numDilations) {
  28. cv.GaussianBlur(src, dst, new cv.Size(gaussianBlurSize, gaussianBlurSize), 0);
  29. cv.adaptiveThreshold(dst, dst, 255, cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY_INV, adaptiveThresholdBlockSize, adaptiveThresholdMeanAdjustment);
  30. let kernel = cv.getStructuringElement(cv.MORPH_CROSS, new cv.Size(3, 3));
  31. try {
  32. cv.dilate(dst, dst, kernel, new cv.Point(-1, -1), numDilations);
  33. } finally {
  34. kernel.delete();
  35. }
  36. }
  37. function morphOpenImage(src, dst, kernelSize, iterations) {
  38. let kernel = cv.getStructuringElement(cv.MORPH_RECT, kernelSize);
  39. try {
  40. cv.morphologyEx(src, dst, cv.MORPH_OPEN, kernel, new cv.Point(-1, -1), iterations);
  41. } finally {
  42. kernel.delete();
  43. }
  44. }
  45. function findBiggestContour(img) {
  46. let contours = new cv.MatVector();
  47. try {
  48. let hierarchy = new cv.Mat();
  49. try {
  50. cv.findContours(img, contours, hierarchy, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE);
  51. let biggest = null;
  52. let maxArea = 0;
  53. for (let i = 0; i < contours.size(); i++) {
  54. let contour = contours.get(i);
  55. let area = cv.contourArea(contour);
  56. if (area > maxArea) {
  57. maxArea = area;
  58. if (biggest !== null) {
  59. biggest.delete();
  60. }
  61. biggest = contour;
  62. } else {
  63. contour.delete();
  64. }
  65. }
  66. return biggest;
  67. } finally {
  68. hierarchy.delete();
  69. }
  70. } finally {
  71. contours.delete();
  72. }
  73. }
  74. function erodeContour(imageSize, contour, kernelSize, iterations) {
  75. let contourImg = cv.Mat.zeros(imageSize.height, imageSize.width, cv.CV_8U);
  76. try {
  77. let contours = new cv.MatVector();
  78. try {
  79. contours.push_back(contour);
  80. cv.drawContours(contourImg, contours, 0, new cv.Scalar(255), -1);
  81. } finally {
  82. contours.delete();
  83. }
  84. morphOpenImage(contourImg, contourImg, new cv.Size(kernelSize, kernelSize), iterations);
  85. return findBiggestContour(contourImg);
  86. } finally {
  87. contourImg.delete();
  88. }
  89. }
  90. function getContourCorners(imageSize, contour) {
  91. let topLeft = new cv.Point(imageSize.width, imageSize.height);
  92. let topRight = new cv.Point(-1, imageSize.height);
  93. let bottomLeft = new cv.Point(imageSize.width, -1);
  94. let bottomRight = new cv.Point(-1, -1);
  95. for (let i = 0; i < contour.rows; i++) {
  96. let vertex = new cv.Point(contour.data32S[i * 2], contour.data32S[i * 2 + 1]);
  97. let sum = vertex.x + vertex.y;
  98. let diff = vertex.x - vertex.y;
  99. if (sum < topLeft.x + topLeft.y) {
  100. topLeft = vertex;
  101. }
  102. if (sum > bottomRight.x + bottomRight.y) {
  103. bottomRight = vertex;
  104. }
  105. if (diff < bottomLeft.x - bottomLeft.y) {
  106. bottomLeft = vertex;
  107. }
  108. if (diff > topRight.x - topRight.y) {
  109. topRight = vertex;
  110. }
  111. }
  112. return [topLeft, topRight, bottomRight, bottomLeft];
  113. }
  114. function segmentLength(p1, p2) {
  115. let dx = p1.x - p2.x;
  116. let dy = p1.y - p2.y;
  117. return Math.sqrt(dx ** 2 + dy ** 2);
  118. }
  119. function getLongestSide(corners) {
  120. let previous = corners[corners.length - 1];
  121. let max = 0;
  122. for (let i = 0; i < corners.length; i++) {
  123. let current = corners[i];
  124. let length = segmentLength(previous, current);
  125. if (length > max) {
  126. max = length;
  127. }
  128. previous = current;
  129. }
  130. return max;
  131. }
  132. function extractSquare(img, corners) {
  133. let longest = getLongestSide(corners);
  134. let end = longest - 1;
  135. let sourceRect = cv.matFromArray(4, 1, cv.CV_32FC2, [corners[0].x, corners[0].y, corners[1].x, corners[1].y, corners[2].x, corners[2].y, corners[3].x, corners[3].y]);
  136. try {
  137. let destRect = cv.matFromArray(4, 1, cv.CV_32FC2, [0, 0, end, 0, end, end, 0, end]);
  138. try {
  139. let m = cv.getPerspectiveTransform(sourceRect, destRect);
  140. try {
  141. let destImg = new cv.Mat();
  142. try {
  143. cv.warpPerspective(img, destImg, m, new cv.Size(longest, longest));
  144. return destImg;
  145. } catch (err) {
  146. destImg.delete();
  147. throw err;
  148. }
  149. } finally {
  150. m.delete();
  151. }
  152. } finally {
  153. destRect.delete();
  154. }
  155. } finally {
  156. sourceRect.delete();
  157. }
  158. }
  159. function indexOfMax(arr) {
  160. return arr.reduce((iMax, x, i, arr) => x > arr[iMax] ? i : iMax, 0);
  161. }
  162. function getFundamentalFrequency(mag) {
  163. mag = mag.slice(0, Math.ceil(mag.length / 2));
  164. mag[0] = 0;
  165. return indexOfMax(mag);
  166. }
  167. function createMatVector(length) {
  168. let vec = new cv.MatVector();
  169. try {
  170. let mat = new cv.Mat();
  171. try {
  172. for (let i = 0; i < length; i++) {
  173. vec.push_back(mat);
  174. }
  175. } finally {
  176. mat.delete();
  177. }
  178. return vec;
  179. } catch (err) {
  180. vec.delete();
  181. throw err;
  182. }
  183. }
  184. function getLineFFT(img, lineDetectorElementSize, axis) {
  185. let lines = new cv.Mat();
  186. try {
  187. morphOpenImage(img, lines, axis === 1 ? new cv.Size(lineDetectorElementSize, 1) : new cv.Size(1, lineDetectorElementSize), 1);
  188. let sums = new cv.Mat();
  189. try {
  190. cv.reduce(lines, sums, axis, cv.REDUCE_SUM, cv.CV_32FC1);
  191. let fft = new cv.Mat();
  192. try {
  193. cv.dft(sums, fft, cv.DFT_COMPLEX_OUTPUT, 0);
  194. return fft;
  195. } catch (err) {
  196. fft.delete();
  197. throw err;
  198. }
  199. } finally {
  200. sums.delete();
  201. }
  202. } finally {
  203. lines.delete();
  204. }
  205. }
  206. function getFFTMagnitude(fft) {
  207. let planes = createMatVector(2);
  208. try {
  209. cv.split(fft, planes);
  210. let real = planes.get(0);
  211. try {
  212. let imag = planes.get(1);
  213. try {
  214. let ret = [];
  215. let length = Math.max(real.cols, real.rows);
  216. for (let i = 0; i < length; i++) {
  217. ret.push(Math.sqrt(real.data32F[i] ** 2 + imag.data32F[i] ** 2))
  218. }
  219. return ret;
  220. } finally {
  221. imag.delete();
  222. }
  223. } finally {
  224. real.delete();
  225. }
  226. } finally {
  227. planes.delete();
  228. }
  229. }
  230. function getLineFrequency(img, lineDetectorElementSize, axis) {
  231. let fft = getLineFFT(img, lineDetectorElementSize, axis);
  232. try {
  233. return getFundamentalFrequency(getFFTMagnitude(fft));
  234. } finally {
  235. fft.delete();
  236. }
  237. }
  238. function extractGridColours(img, numRows, numCols, samplingBlockSizeRatio) {
  239. let imageSize = img.size();
  240. let rowOffset = Math.floor(imageSize.height * samplingBlockSizeRatio / numRows / 2);
  241. let rowHeight = 2 * rowOffset + 1;
  242. let colOffset = Math.floor(imageSize.width * samplingBlockSizeRatio / numCols / 2);
  243. let colWidth = 2 * colOffset + 1;
  244. let gridColours = [];
  245. for (let row = 0; row < numRows; row++) {
  246. let line = [];
  247. let y = Math.floor((row + 0.5) / numRows * imageSize.height) - rowOffset;
  248. for (let col = 0; col < numCols; col++) {
  249. let x = Math.floor((col + 0.5) / numCols * imageSize.width) - colOffset;
  250. let roi = img.roi(new cv.Rect(x, y, colWidth, rowHeight));
  251. try {
  252. line.push(cv.mean(roi)[0]);
  253. } finally {
  254. roi.delete();
  255. }
  256. }
  257. gridColours.push(line);
  258. }
  259. return gridColours;
  260. }
  261. function getGridColourThreshold(gridColours) {
  262. let colours = gridColours.reduce(function(acc, x) { return acc.concat(x); }, []).sort(function (a, b) { return a - b; });
  263. let deltaMax = 0;
  264. let iMax;
  265. for (let i = 0; i < colours.length; i++) {
  266. let delta = colours[i] - colours[i - 1];
  267. if (delta > deltaMax) {
  268. deltaMax = delta;
  269. iMax = i;
  270. }
  271. }
  272. return (colours[iMax] + colours[iMax - 1]) / 2;
  273. }
  274. function gridColoursToBlocks(gridColours, numRows, numCols, samplingThreshold) {
  275. let blocks = JSON.parse(JSON.stringify(gridColours));
  276. let warning = false;
  277. let midpoint = Math.floor(numRows / 2) + (numRows % 2 > 0 ? 1 : 0);
  278. for (let row = 0; row < midpoint; row++) {
  279. for (let col = 0; col < numCols; col++) {
  280. // If there is an odd number of rows then row and row2 will point to
  281. // the same row when we reach the middle. Doesn't seem worth adding a
  282. // special case.
  283. let row2 = numRows - row - 1;
  284. let col2 = numCols - col - 1;
  285. let delta1 = gridColours[row][col] - samplingThreshold;
  286. let delta2 = gridColours[row2][col2] - samplingThreshold;
  287. let filled;
  288. if ((delta1 > 0) && (delta2 > 0)) {
  289. filled = false;
  290. } else if ((delta1 < 0) && (delta2 < 0)) {
  291. filled = true;
  292. } else {
  293. warning = true;
  294. if (Math.abs(delta1) > Math.abs(delta2)) {
  295. filled = delta1 < 0;
  296. } else {
  297. filled = delta2 < 0;
  298. }
  299. }
  300. blocks[row][col] = {filled: filled}
  301. blocks[row2][col2] = {filled: filled}
  302. }
  303. }
  304. let number = 1
  305. for (let row = 0; row < numRows; row++) {
  306. for (let col = 0; col < numCols; col++) {
  307. if (! blocks[row][col].filled && (
  308. (((col == 0) || blocks[row][col - 1].filled) && (col < numCols - 1) && ! blocks[row][col + 1].filled) ||
  309. (((row == 0) || blocks[row - 1][col].filled) && (row < numRows - 1) && ! blocks[row + 1][col].filled)
  310. )) {
  311. blocks[row][col].number = number
  312. number += 1
  313. }
  314. }
  315. }
  316. return {warning: warning, blocks: blocks};
  317. }
  318. function drawGrid(canvas, blocks, numRows, numCols, gridLineThickness, gridSquareSize, gridBorderSize) {
  319. let step = gridSquareSize + gridLineThickness;
  320. let gridHeight = numRows * step + gridLineThickness;
  321. let gridWidth = numCols * step + gridLineThickness;
  322. canvas.width = 2 * gridBorderSize + gridWidth;
  323. canvas.height = 2 * gridBorderSize + gridHeight;
  324. let context = canvas.getContext('2d');
  325. context.fillStyle = 'black';
  326. context.fillRect(gridBorderSize, gridBorderSize, gridWidth, gridHeight);
  327. context.fillStyle = 'white';
  328. for (let row = 0; row < numRows; row++) {
  329. let y = row * step + gridLineThickness + gridBorderSize;
  330. for (let col = 0; col < numCols; col++) {
  331. if (! blocks[row][col].filled) {
  332. let x = col * step + gridLineThickness + gridBorderSize;
  333. context.fillRect(x, y, gridSquareSize, gridSquareSize);
  334. }
  335. }
  336. }
  337. }