// vim:ts=4:sw=4:expandtab 'use strict'; function plot(elementId, seriesList) { let plots = []; for (let i = 0; i < seriesList.length; i++) { let y = seriesList[i]; let x = []; for (let i = 0; i < y.length; i++) { x.push(i); } plots.push({x: x, y: y}); } Plotly.plot(document.getElementById(elementId), plots); } function removeColours(src, dst, threshold) { let from = src.data32S; let to = dst.data32S; let pixels = src.rows * src.cols; for (let i = 0; i < pixels; i++) { let pixel = from[i]; let r = pixel & 0xFF; let g = (pixel >> 8) & 0xFF; let b = (pixel >> 16) & 0xFF; to[i] = from[i] | (Math.max(r, g, b) - Math.min(r, g, b) > threshold ? 0xFFFFFF : 0); } } function preprocessImage(src, dst, gaussianBlurSize, adaptiveThresholdBlockSize, adaptiveThresholdMeanAdjustment, numDilations) { cv.GaussianBlur(src, dst, new cv.Size(gaussianBlurSize, gaussianBlurSize), 0); cv.adaptiveThreshold(dst, dst, 255, cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY_INV, adaptiveThresholdBlockSize, adaptiveThresholdMeanAdjustment); let kernel = cv.getStructuringElement(cv.MORPH_CROSS, new cv.Size(3, 3)); try { cv.dilate(dst, dst, kernel, new cv.Point(-1, -1), numDilations); } finally { kernel.delete(); } } function morphOpenImage(src, dst, kernelSize, iterations) { let kernel = cv.getStructuringElement(cv.MORPH_RECT, kernelSize); try { cv.morphologyEx(src, dst, cv.MORPH_OPEN, kernel, new cv.Point(-1, -1), iterations); } finally { kernel.delete(); } } function findBiggestContour(img) { let contours = new cv.MatVector(); try { let hierarchy = new cv.Mat(); try { cv.findContours(img, contours, hierarchy, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE); let biggest = null; let maxArea = 0; for (let i = 0; i < contours.size(); i++) { let contour = contours.get(i); let area = cv.contourArea(contour); if (area > maxArea) { maxArea = area; if (biggest !== null) { biggest.delete(); } biggest = contour; } else { contour.delete(); } } return biggest; } finally { hierarchy.delete(); } } finally { contours.delete(); } } function erodeContour(imageSize, contour, kernelSize, iterations) { let contourImg = cv.Mat.zeros(imageSize.height, imageSize.width, cv.CV_8U); try { let contours = new cv.MatVector(); try { contours.push_back(contour); cv.drawContours(contourImg, contours, 0, new cv.Scalar(255), -1); } finally { contours.delete(); } morphOpenImage(contourImg, contourImg, new cv.Size(kernelSize, kernelSize), iterations); return findBiggestContour(contourImg); } finally { contourImg.delete(); } } function getContourCorners(imageSize, contour) { let topLeft = new cv.Point(imageSize.width, imageSize.height); let topRight = new cv.Point(-1, imageSize.height); let bottomLeft = new cv.Point(imageSize.width, -1); let bottomRight = new cv.Point(-1, -1); for (let i = 0; i < contour.rows; i++) { let vertex = new cv.Point(contour.data32S[i * 2], contour.data32S[i * 2 + 1]); let sum = vertex.x + vertex.y; let diff = vertex.x - vertex.y; if (sum < topLeft.x + topLeft.y) { topLeft = vertex; } if (sum > bottomRight.x + bottomRight.y) { bottomRight = vertex; } if (diff < bottomLeft.x - bottomLeft.y) { bottomLeft = vertex; } if (diff > topRight.x - topRight.y) { topRight = vertex; } } return [topLeft, topRight, bottomRight, bottomLeft]; } function segmentLength(p1, p2) { let dx = p1.x - p2.x; let dy = p1.y - p2.y; return Math.sqrt(dx ** 2 + dy ** 2); } function getLongestSide(corners) { let previous = corners[corners.length - 1]; let max = 0; for (let i = 0; i < corners.length; i++) { let current = corners[i]; let length = segmentLength(previous, current); if (length > max) { max = length; } previous = current; } return max; } function extractSquare(img, corners) { let longest = getLongestSide(corners); let end = longest - 1; 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]); try { let destRect = cv.matFromArray(4, 1, cv.CV_32FC2, [0, 0, end, 0, end, end, 0, end]); try { let m = cv.getPerspectiveTransform(sourceRect, destRect); try { let destImg = new cv.Mat(); try { cv.warpPerspective(img, destImg, m, new cv.Size(longest, longest)); return destImg; } catch (err) { destImg.delete(); throw err; } } finally { m.delete(); } } finally { destRect.delete(); } } finally { sourceRect.delete(); } } function indexOfMax(arr) { return arr.reduce((iMax, x, i, arr) => x > arr[iMax] ? i : iMax, 0); } function getFundamentalFrequency(mag) { mag = mag.slice(0, Math.ceil(mag.length / 2)); mag[0] = 0; return indexOfMax(mag); } function createMatVector(length) { let vec = new cv.MatVector(); try { let mat = new cv.Mat(); try { for (let i = 0; i < length; i++) { vec.push_back(mat); } } finally { mat.delete(); } return vec; } catch (err) { vec.delete(); throw err; } } function getLineFFT(img, lineDetectorElementSize, axis) { let lines = new cv.Mat(); try { morphOpenImage(img, lines, axis === 1 ? new cv.Size(lineDetectorElementSize, 1) : new cv.Size(1, lineDetectorElementSize), 1); let sums = new cv.Mat(); try { cv.reduce(lines, sums, axis, cv.REDUCE_SUM, cv.CV_32FC1); let fft = new cv.Mat(); try { cv.dft(sums, fft, cv.DFT_COMPLEX_OUTPUT, 0); return fft; } catch (err) { fft.delete(); throw err; } } finally { sums.delete(); } } finally { lines.delete(); } } function getFFTMagnitude(fft) { let planes = createMatVector(2); try { cv.split(fft, planes); let real = planes.get(0); try { let imag = planes.get(1); try { let ret = []; let length = Math.max(real.cols, real.rows); for (let i = 0; i < length; i++) { ret.push(Math.sqrt(real.data32F[i] ** 2 + imag.data32F[i] ** 2)) } return ret; } finally { imag.delete(); } } finally { real.delete(); } } finally { planes.delete(); } } function getLineFrequency(img, lineDetectorElementSize, axis) { let fft = getLineFFT(img, lineDetectorElementSize, axis); try { return getFundamentalFrequency(getFFTMagnitude(fft)); } finally { fft.delete(); } }