| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362 |
- // 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();
- }
- }
- function extractGridColours(img, numRows, numCols, samplingBlockSizeRatio) {
- let imageSize = img.size();
- let rowOffset = Math.floor(imageSize.height * samplingBlockSizeRatio / numRows / 2);
- let rowHeight = 2 * rowOffset + 1;
- let colOffset = Math.floor(imageSize.width * samplingBlockSizeRatio / numCols / 2);
- let colWidth = 2 * colOffset + 1;
- let gridColours = [];
- for (let row = 0; row < numRows; row++) {
- let line = [];
- let y = Math.floor((row + 0.5) / numRows * imageSize.height) - rowOffset;
- for (let col = 0; col < numCols; col++) {
- let x = Math.floor((col + 0.5) / numCols * imageSize.width) - colOffset;
- let roi = img.roi(new cv.Rect(x, y, colWidth, rowHeight));
- try {
- line.push(cv.mean(roi)[0]);
- } finally {
- roi.delete();
- }
- }
- gridColours.push(line);
- }
- return gridColours;
- }
- function getGridColourThreshold(gridColours) {
- let colours = gridColours.reduce(function(acc, x) { return acc.concat(x); }, []).sort(function (a, b) { return a - b; });
- let deltaMax = 0;
- let iMax;
- for (let i = 0; i < colours.length; i++) {
- let delta = colours[i] - colours[i - 1];
- if (delta > deltaMax) {
- deltaMax = delta;
- iMax = i;
- }
- }
- return (colours[iMax] + colours[iMax - 1]) / 2;
- }
- function gridColoursToBlocks(gridColours, numRows, numCols, samplingThreshold) {
- let blocks = JSON.parse(JSON.stringify(gridColours));
- let warning = false;
- let midpoint = Math.floor(numRows / 2) + (numRows % 2 > 0 ? 1 : 0);
- for (let row = 0; row < midpoint; row++) {
- for (let col = 0; col < numCols; col++) {
- // If there is an odd number of rows then row and row2 will point to
- // the same row when we reach the middle. Doesn't seem worth adding a
- // special case.
- let row2 = numRows - row - 1;
- let col2 = numCols - col - 1;
- let delta1 = gridColours[row][col] - samplingThreshold;
- let delta2 = gridColours[row2][col2] - samplingThreshold;
- let filled;
- if ((delta1 > 0) && (delta2 > 0)) {
- filled = false;
- } else if ((delta1 < 0) && (delta2 < 0)) {
- filled = true;
- } else {
- warning = true;
- if (Math.abs(delta1) > Math.abs(delta2)) {
- filled = delta1 < 0;
- } else {
- filled = delta2 < 0;
- }
- }
- blocks[row][col] = {filled: filled}
- blocks[row2][col2] = {filled: filled}
- }
- }
- let number = 1
- for (let row = 0; row < numRows; row++) {
- for (let col = 0; col < numCols; col++) {
- if (! blocks[row][col].filled && (
- (((col == 0) || blocks[row][col - 1].filled) && (col < numCols - 1) && ! blocks[row][col + 1].filled) ||
- (((row == 0) || blocks[row - 1][col].filled) && (row < numRows - 1) && ! blocks[row + 1][col].filled)
- )) {
- blocks[row][col].number = number
- number += 1
- }
- }
- }
- return {warning: warning, blocks: blocks};
- }
- function drawGrid(canvas, blocks, numRows, numCols, gridLineThickness, gridSquareSize, gridBorderSize) {
- let step = gridSquareSize + gridLineThickness;
- let gridHeight = numRows * step + gridLineThickness;
- let gridWidth = numCols * step + gridLineThickness;
- canvas.width = 2 * gridBorderSize + gridWidth;
- canvas.height = 2 * gridBorderSize + gridHeight;
- let context = canvas.getContext('2d');
- context.fillStyle = 'black';
- context.fillRect(gridBorderSize, gridBorderSize, gridWidth, gridHeight);
- context.fillStyle = 'white';
- for (let row = 0; row < numRows; row++) {
- let y = row * step + gridLineThickness + gridBorderSize;
- for (let col = 0; col < numCols; col++) {
- if (! blocks[row][col].filled) {
- let x = col * step + gridLineThickness + gridBorderSize;
- context.fillRect(x, y, gridSquareSize, gridSquareSize);
- }
- }
- }
- }
|