Repository: wellflat/imageprocessing-labs Branch: main Commit: e56f13ec7fbf Files: 125 Total size: 2.2 MB Directory structure: gitextract_72c15x7w/ ├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── cv/ │ ├── corner_detection/ │ │ ├── README.md │ │ └── cornerdetect.js │ ├── fft/ │ │ ├── README.md │ │ ├── fft.js │ │ ├── filter.js │ │ ├── qunit-fft-test.js │ │ └── spectrum.js │ ├── fisheye_transform/ │ │ ├── README.md │ │ └── fisheye.js │ ├── image_filter/ │ │ ├── README.md │ │ ├── cavin.js │ │ └── cvsandbox.js │ ├── lsd/ │ │ ├── .babelrc │ │ ├── .eslintignore │ │ ├── .eslintrc.json │ │ ├── README.md │ │ ├── jsconfig.json │ │ ├── line-segment-detector/ │ │ │ ├── .gitignore │ │ │ ├── README.md │ │ │ ├── jsconfig.json │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ ├── funcs.ts │ │ │ │ ├── lsd.ts │ │ │ │ └── types.ts │ │ │ ├── tsconfig.json │ │ │ └── webpack.config.js │ │ ├── package.json │ │ ├── src/ │ │ │ ├── app.js │ │ │ ├── funcs.js │ │ │ ├── index.html │ │ │ ├── lsd.js │ │ │ ├── lsd_component.js │ │ │ └── types.js │ │ └── webpack.config.js │ ├── ort_web/ │ │ ├── HelloONNX.vue │ │ └── README.md │ ├── pixel_clustering/ │ │ ├── README.md │ │ ├── kmeans.js │ │ └── pixelcluster.js │ ├── poisson_blending/ │ │ ├── README.md │ │ └── poisson.js │ ├── stereo_matching/ │ │ ├── README.md │ │ ├── stereo-core.js │ │ └── stereo.js │ └── utils/ │ └── histogram.js ├── draw/ │ ├── README.md │ ├── heart.js │ ├── klein.js │ ├── lib3d.js │ ├── mobius.js │ └── webgl/ │ ├── glsample.js │ └── webglsample.html └── ml/ ├── README.md ├── arow/ │ ├── README.md │ ├── app.ts │ ├── arow.ts │ ├── data_loader.ts │ ├── out/ │ │ ├── app.js │ │ ├── arow.js │ │ ├── data_loader.js │ │ └── types.js │ └── types.ts ├── autoencoder/ │ ├── css/ │ │ └── app.css │ ├── index.html │ ├── out/ │ │ ├── da.js │ │ ├── matrix.js │ │ └── vector.js │ ├── packages.config │ ├── spec/ │ │ ├── Chutzpah.json │ │ ├── Tests/ │ │ │ ├── da_test.ts │ │ │ ├── matrix_t_test.ts │ │ │ ├── matrix_test.ts │ │ │ ├── vector_t_test.ts │ │ │ └── vector_test.ts │ │ └── packages.config │ └── src/ │ ├── app.ts │ ├── controller.ts │ ├── da.ts │ ├── matrix.ts │ ├── matrix_t.ts │ ├── storage.ts │ ├── vector.ts │ └── vector_t.ts ├── datasets/ │ ├── README.md │ ├── boston_data.js │ ├── digits_data.ts │ ├── iris_data.ts │ └── out/ │ ├── digits_data.js │ └── iris_data.js ├── decisiontree/ │ └── decisiontree.js ├── gbdt/ │ ├── README.md │ ├── package.json │ └── src/ │ ├── boston_data.js │ ├── gradient_boosting.js │ ├── index.js │ ├── metrics.js │ └── regression_tree.js ├── kmeans/ │ └── kmeans.js ├── logistic_regression/ │ ├── logistic_regression.ts │ ├── matrix.ts │ └── vector.ts ├── scw/ │ ├── README.md │ ├── data_loader.ts │ ├── out/ │ │ ├── app.js │ │ ├── data_loader.js │ │ ├── scw.js │ │ └── types.js │ ├── scw.ts │ └── types.ts ├── t-sne/ │ ├── .babelrc │ ├── .eslintrc.json │ ├── .gitignore │ ├── README.md │ ├── package.json │ └── src/ │ ├── digits_data.js │ ├── index.js │ ├── tsne.js │ └── utils.js └── utils/ ├── data_utils.ts ├── metrics.ts └── preprocessing.ts ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ *.ts linguist-language=JavaScript ================================================ FILE: .gitignore ================================================ ## macOS .DS_Store ## emacs *~ \#* .\#* ## vim *.swp *.swo ## VSCode .vscode/ ## PROJECT::GENERAL logs/ *.log npm-debug.log* node_modules/ typings/ .npm .eslintcache ## PROJECT::SPECIFIC dst/ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. obj/ *.dll *.dll.config *.pdb *.suo # Chutzpah Test files _Chutzpah* ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2017 wellflat Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ ## Image processing and Machine learning labs   [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square)](https://opensource.org/licenses/MIT) computer vision, image processing and machine learning on the web browser or node ### note * Fast Fourier Transform (1D/2D-FFT) * Stereo Matching * Poisson Image Editing * Line Segment Detector * Corner Detection * Fish-Eye Transform * Image Processing Filters * Image Histogram Calculation * Image Feature Extraction * Decision Tree Learning * K-Means++ Clustering * Logistic Regression * Adaptive Regularization of Weight Vectors (AROW) * Soft Confidence Weighted Learning (SCW) * Gradient Boosting Decision Tree (GBDT) * Neural Network (Denoising Autoencoders) * t-Distributed Stochastic Neighbor Embedding (t-SNE) * 3D Shape Drawing (Mobius Strip, Klein Bottle, Heart Surface ...) * WebGL Samples * ONNX Runtime for Web (ORT Web) * etc.. ### demo [Demo Site](https://rest-term.com/labs/html5/index.html) ### license Copyright © 2017 wellflat Licensed under the [MIT License][MIT] [MIT]: http://www.opensource.org/licenses/mit-license.php ================================================ FILE: cv/corner_detection/README.md ================================================ # Corner Detection Module ## description Corner detection using Harris operator see also [blog entry][entry] ### sample [![corner_detection](https://raw.github.com/wiki/wellflat/imageprocessing-labs/images/room_corners.jpg)](http://rest-term.com/labs/html5/corner.html) (Tests: IE9, Firefox15.0, Chrome21.0, Safari6.0, Opera12.0) ## usage ```js // parameters for Harris corner detection // blockSize: Neighborhood size (3×3 or 5×5) // k: Harris detector free parameter (recommends 0.04 ~ 0.15) // qualityLevel: Parameter characterizing the minimal accepted quality of image corners var params = { blockSize: 3, k: 0.04, qualityLevel: 0.01 }; // img: ImageData object // returns Array of detected corners var corners = CornerDetector.detect(img, CornerDetector.HARRIS, params); ``` license ---------- Copyright © 2014 wellflat Licensed under the [MIT License][MIT] [MIT]: http://www.opensource.org/licenses/mit-license.php [entry]: http://rest-term.com/archives/2986/ ================================================ FILE: cv/corner_detection/cornerdetect.js ================================================ /** * Corner Detector module */ (function() { var CornerDetector; // top-level namespace var _root = this; // reference to 'window' or 'global' if(typeof exports !== 'undefined') { CornerDetector = exports; // for CommonJS } else { CornerDetector = _root.CornerDetector = {}; } var version = { release: '0.1.0', date: '2012-09' }; CornerDetector.toString = function() { return "version " + version.release + ", released " + version.date; }; // core operations var core = { detect : function(img, type, params) { core._validateParams(params); switch(type) { case 'eigen': // implement ? throw new Error('not implementation error'); break; case 'harris': return core._detectHarris(img, params); break; case 'fast': // implement ? throw new Error('not implementation error'); break; default: return core._detectHarris(img, params); break; } }, _validateParams : function(params) { var _params = ['qualityLevel', 'blockSize', 'k'], msg = ''; for(var i=0; i<_params.length; i++) { if(!params.hasOwnProperty(_params[i])) { msg = 'invalid parameters, required \'' + _params[i] + '\''; throw new Error(msg); } if(!parseFloat(params[_params[i]])) { msg = 'invalid parameters, required number \'' + _params[i] + '\''; throw new TypeError(msg); } } if(params.blockSize%2 !== 1) { throw new Error('odd number required \'blockSize\''); } if(params.blockSize > 5) { throw new Error('unsupported \'blockSize\' ' + params.blockSize); } }, /* Harris Operator */ _detectHarris : function(img, params) { var w = img.width, h = img.height, imgdata = img.data, len = w*h << 2, src, cov, eig, corners, r = (params.blockSize - 1)/2, dx, dy, dxdata, dydata, kernelx, kernely, maxval, quality, x, y, kx, ky, i, j, step, tmp; if(typeof Float32Array === 'function') { src = cov = new Float32Array(w*h*3); corners = new Float32Array(w*h); } else { src = cov = corners = []; } // change container, uint8 to float32 for(i=0,j=0; i tmp) { tmp = 0; break; } } } if(tmp !== 0) { corners[i] = eig[i]; } } } // threshold maxval = 0; len = eig.length; for(i=0; i maxval) maxval = corners[i]; } quality = maxval*params.qualityLevel; for(j=0; j> 2; // bit reversal for(var l=0; l<_n; l++) { m = _bitrev[l]; if(l < m) { tmp = re[l]; re[l] = re[m]; re[m] = tmp; tmp = im[l]; im[l] = im[m]; im[m] = tmp; } } // butterfly operation for(var k=1; k<_n; k<<=1) { h = 0; d = _n/(k << 1); for(var j=0; j> 1; while(k <= j) { j -= k; k >>= 1; } j += k; _bitrev[i] = j; } }, // makes trigonometiric function table _makeCosSinTable : function() { var n2 = _n >> 1, n4 = _n >> 2, n8 = _n >> 3, n2p4 = n2 + n4, t = Math.sin(Math.PI/_n), dc = 2*t*t, ds = Math.sqrt(dc*(2 - dc)), c = _cstb[n4] = 1, s = _cstb[0] = 0; t = 2*dc; for(var i=1; i> 1; for(var y=0; y> 1, sqrt = Math.sqrt; for(var y=-n2; y> 1, sqrt = Math.sqrt; for(var y=-n2; y radius) { re[p] = im[p] = 0; } } } }, // applies Band-Pass Filter BPF : function(re, im, radius, bandwidth) { var i = 0, p = 0, r = 0.0, n2 = _n >> 1, sqrt = Math.sqrt; for(var y=-n2; y (radius + bandwidth)) { re[p] = im[p] = 0; } } } }, // windowing using hamming window windowing : function(data, inv) { var len = data.length, pi = Math.PI, cos = Math.cos; for(var i=0; i max) { max = spectrum[i]; } } imax = 1/max; for(var j=0; j> 1; } } _context.putImageData(_img, 0, 0); } }; // aliases (public APIs) SpectrumViewer.init = core.init; SpectrumViewer.render = core.render; }).call(this); ================================================ FILE: cv/fft/filter.js ================================================ /** * Spatial Frequency Filtering * High-pass/Low-pass/Band-pass Filter * Windowing using hamming window */ (function() { var FrequencyFilter; // top-level namespace var _root = this; // reference to 'window' or 'global' if(typeof exports !== 'undefined') { FrequencyFilter = exports; // for CommonJS } else { FrequencyFilter = _root.FrequencyFilter = {}; } // core operations var _n = 0; var core = { init : function(n) { if(n !== 0 && (n & (n - 1)) === 0) { _n = n; } else { throw new Error("init: radix-2 required"); } }, // swap quadrant swap : function(re, im) { var xn, yn, i, j, k, l, tmp, len = _n >> 1; for(var y=0; y> 1; for(var y=-n2; y> 1; for(var y=-n2; y radius) { re[p] = im[p] = 0; } } } }, // apply Band-Pass Filter BPF : function(re, im, radius, bandwidth) { var i = 0, p = 0, r = 0.0, n2 = _n >> 1; for(var y=-n2; y (radius + bandwidth)) { re[p] = im[p] = 0; } } } }, // windowing using hamming window windowing : function(data, inv) { var len = data.length, pi = Math.PI; for(var i=0; i 0 ? diff : -diff; ok(diff < error, "fft1d -> ifft1d, Re error: " + diff); diff = im[j] - im2[j]; diff = diff > 0 ? diff : -diff; ok(diff < error, "fft1d -> ifft1d, Im error: " + diff); } // 2D-FFT/IFFT re = re2.concat(); im = im2.concat(); FFT.fft2d(re, im); FFT.ifft2d(re, im); for(var j=0; j 0 ? diff : -diff; ok(diff < error, "fft2d -> ifft2d, Re error: " + diff); diff = im[j] - im2[j]; diff = diff > 0 ? diff : -diff; ok(diff < error, "fft2d -> ifft2d, Im error: " + diff); } }, false); } catch(e) { return; } }); ================================================ FILE: cv/fft/spectrum.js ================================================ /** * FFT Power Spectrum Viewer */ (function() { var SpectrumViewer; // top-level namespace var _root = this; // reference to 'window' or 'global' if(typeof exports !== 'undefined') { SpectrumViewer = exports; // for CommonJS } else { SpectrumViewer = _root.SpectrumViewer = {}; } // core operations var _context = null, _n = 0, _img = null, _data = null; var core = { init : function(context) { _context = context; _n = context.canvas.width, _img = context.getImageData(0, 0, _n, _n); _data = _img.data; }, // render FFT power spectrum on the Canvas render : function(re, im, islog) { var val = 0, i = 0, p = 0, spectrum = [], max = 1.0, imax = 0.0, n2 = _n*_n; for(var i=0; i max) { max = spectrum[i]; } } imax = 1/max; for(var j=0; j> 1; } } _context.putImageData(_img, 0, 0); } }; // aliases (public APIs) SpectrumViewer.init = core.init; SpectrumViewer.render = core.render; }).call(this); ================================================ FILE: cv/fisheye_transform/README.md ================================================ # Fish-Eye Transform Module ## description Circular Fish-Eye transform see also [blog entry][entry] ### sample [![fisheye_transform](https://raw.github.com/wiki/wellflat/imageprocessing-labs/images/fisheye_transform.jpg)](http://rest-term.com/labs/html5/fisheye.html) (Tests: IE9, Firefox16.0, Chrome21.0, Safari6.0, Opera12.0) ## usage ```js // img: ImageData object // focalLength: focal-length Number (int) // radius: circle radius Number (int) var focalLength = 55; var radius = 60; // returns ImageData object var result = Fisheye.transform(img, focalLength, radius); ``` license ---------- Copyright © 2014 wellflat Licensed under the [MIT License][MIT] [FishEye]: http://rest-term.com/labs/html5/fisheye.html [MIT]: http://www.opensource.org/licenses/mit-license.php [entry]: http://rest-term.com/archives/2991/ ================================================ FILE: cv/fisheye_transform/fisheye.js ================================================ /** * Fish-Eye Transform module */ (function() { var Fisheye; // top-level namespace var _root = this; // reference to 'window' or 'global' if(typeof exports !== 'undefined') { Fisheye = exports; // for CommonJS } else { Fisheye = _root.Fisheye = {}; } var version = { release: '0.1.0', date: '2012-09' }; Fisheye.toString = function() { return "version " + version.release + ", released " + version.date; }; // core operations var _ctx = document.createElement('canvas').getContext('2d'); var core = { transform : function(img, focalLength, radius) { var w = img.width, h = img.height, src = img.data, f = focalLength, r = radius, buff = [0, 0, 0], dstimg, dst, sqrt = Math.sqrt, round = Math.round, cx, cy, dx, dy, nx, ny, d, fd, i, j, step; _ctx.canvas.width = w; _ctx.canvas.height = h; _ctx.fillStyle = 'rgba(0, 0, 0, 0)'; _ctx.fillRect(0, 0, w, h); dstimg = _ctx.getImageData(0, 0, w, h); dst = dstimg.data; cx = w/2; cy = h/2; for(var y=0; y threshold) { div = 1/acc; data[i] = buff[0]*div; data[i + 1] = buff[1]*div; data[i + 2] = buff[2]*div; data[i + 3] = 1; // as flag } } //data[i + 3] = 255; } } } }; // public API Fisheye.transform = core.transform; }).call(this); ================================================ FILE: cv/image_filter/README.md ================================================ # Image Processing Filters ## description Image processing filters * Convolution Filter [![image_filter](https://raw.github.com/wiki/wellflat/imageprocessing-labs/images/convolution_filter.jpg)](http://rest-term.com/labs/html5/convolution.html) * Symmetric Nearest Neighbor Filter [![image_filter](https://raw.github.com/wiki/wellflat/imageprocessing-labs/images/snn_filter.jpg)](http://rest-term.com/labs/html5/snn.html) * Jitter Jilter [![image_filter](https://raw.github.com/wiki/wellflat/imageprocessing-labs/images/jitter_filter.jpg)](http://rest-term.com/labs/html5/jitter.html) ### samples [Convolution Filter][Convolution] [Symmetric Nearest Neighbor Filter][SNN] [Jitter Filter][Jitter] (Tests: Firefox3.6, Safari5.0, Chrome10.0, Opera11.60) license ---------- Copyright © 2014 wellflat Licensed under the [MIT License][MIT] [Convolution]: http://rest-term.com/labs/html5/convolution.html [SNN]: http://rest-term.com/labs/html5/snn.html [Jitter]: http://rest-term.com/labs/html5/jitter.html [MIT]: http://www.opensource.org/licenses/mit-license.php ================================================ FILE: cv/image_filter/cavin.js ================================================ /** * Cavin.js * Computer Vision and Image Processing Library for HTML5 Canvas */ (function() { /** * intialize */ var Cavin; // top-level namespace var root = this; // reference to 'window' or 'global' var ctx = null; // local reference to CanvasRenderingContext2D var imgdata = null; // local reference to ImageData // local reference to slice/splice var slice = Array.prototype.slice, splice = Array.prototype.splice; if(typeof exports !== 'undefined') { Cavin = exports; // for CommonJS } else { Cavin = root.Cavin = {}; } // current version of the library Cavin.version = { release: '0.1.0', date: '2012/03', }; Cavin.toString = function() { return "version " + Cavin.version.release + ", released " + Cavin.version.date; }; // set the js library that will be used for DOM manipulation, // by default will use jQuery var $ = root.jQuery; Cavin.setDomLibrary = function(lib) { $ = lib; }; /** * image I/O modules */ var IO = Cavin.IO = { read: function() { var args = slice.call(arguments), img = new Image(), opt = IO._parseOpt(args); img.src = opt['path']; ctx = document.createElement('canvas').getContext('2d'); img.addEventListener('load', function(e) { ctx.canvas.width = img.width; ctx.canvas.height = img.height; ctx.drawImage(img, 0, 0); imgdata = ctx.getImageData(0, 0, img.width, img.height); opt['success'](imgdata); }, false); img.addEventListener('error', function(e) { opt['error']('can\'t load image: ' + opt['path']); }, false) }, write: function(data, target) { var ctx; if(typeof target === 'string') { ctx = document.querySelector(target).getContext('2d'); } else if(typeof target === 'object') { ctx = target; // CanvasRenderingContext2D } ctx.putImageData(data, 0, 0); }, getImageData: function(context) { var w = context.canvas.width, h = context.canvas.height; return context.getImageData(0, 0, w, h); }, _parseOpt: function(args) { var opt = {}; // {path:string, success:function, error:function} if(args.length === 1 && typeof args[0] === 'object') { if(typeof args[0]['path'] !== 'string') { throw new Error('string object required: path'); } else { opt['path'] = args[0]['path']; } if(typeof args[0]['success'] != 'function') { throw new Error('function object required: success'); } else { opt['success'] = args[0]['success']; } if(typeof args[0]['error'] != 'function') { throw new Error('function object required: error'); } else { opt['error'] = args[0]['error']; } } // path, callback if(args.length === 2 && typeof args[0] === 'string') { opt['path'] = args[0]; if(typeof args[1] != 'function') { throw new Error('function object required'); } else { opt['success'] = args[1]; } } return opt; }, }; // aliases Cavin.get = Cavin.IO.read; Cavin.put = Cavin.IO.write; /** * image processing filter modules */ var Filter = Cavin.Filter = { /* convolution filter (x*x squire kernel) */ convolution: function(kernel, divisor, bias) { var srcimg = imgdata, w = srcimg.width, h = srcimg.height, srcdata = srcimg.data, dstimg = ctx.createImageData(w, h), dstdata = dstimg.data, div = 1/divisor, r = Math.sqrt(kernel.length), buff = [0, 0, 0], i, j, k, v, px, py, step, kstep; if(divisor === 0) { throw new Error('division zero'); } if(r%2 != 1) { throw new Error('square kernel required'); } r = (r - 1)/2; for(var y=0; y 255 ? 255 : v; v = buff[1]*div + bias; dstdata[i + 1] = v < 0 ? 0 : v > 255 ? 255 : v; v = buff[2]*div + bias; dstdata[i + 2] = v < 0 ? 0 : v > 255 ? 255 : v; dstdata[i + 3] = 255; } } return dstimg; }, /* box blur filter */ blur: function(radius) { var kernel = [], size = (2*radius + 1)*(2*radius + 1); for(var i=0; i255 ? 255 : value; } return dstImg; }; // jitter filter self.jitter = function(amount) { var p = self.constructor.prototype, w = p.bitmapData.width, h = p.bitmapData.height, srcData = p.bitmapData.data, dstImg = p.ctx.createImageData(w, h), dstData = dstImg.data, len = dstData.length, dx, dy, step, i, k; for(var y=0; y> l & 1) && (src[i + j] === cmp)) { c = 0; break; } } } features[k] += c; } } } return features; }; self.stardetect = function(param) { // TODO }; }; /* utilities module */ /* image histogram */ CV.modules.histogram = function(self) { // draw histogram on the canvas self.drawHistogram = function(bins, context, style) { var h = context.canvas.height, max = self.getMax(bins), len = bins.length, step = context.canvas.width/len, k = 2; style = style || {}; context.globalAlpha = style['globalAlpha'] || 0.8; context.strokeStyle = style['strokeStyle'] || '#00cc66'; context.lineWidth = style['lineWidth'] || 4; for(var i=0; i sum2) ? minsum/sum1 : minsum/sum2; }; self.addHistogram = function(hist1, hist2) { if(hist1.length != hist2.length) { throw new Error('invalid histogram pair'); } var newhist = [], len = hist1.length; for(var i=0; i max) max = bins[i]; } return max; }; }; /* web storage */ CV.modules.storage = function(self) { // thin wrapper self.getData = function(key) { return JSON.parse(window.localStorage[key]); }; self.setData = function(key, value) { window.localStorage[key] = JSON.stringify(value); }; }; ================================================ FILE: cv/lsd/.babelrc ================================================ { "presets": ["react", "es2015", "stage-0"], "plugins": [ ], "env": { "development": { "presets": ["react-hmre"], "plugins": [["react-transform", { "transforms": [{ "transform": "react-transform-hmr", "imports": ["react"], "locals": ["module"] }] }]] }, "production": { } } } ================================================ FILE: cv/lsd/.eslintignore ================================================ webpack.config.js dst/ ================================================ FILE: cv/lsd/.eslintrc.json ================================================ { "env": { "browser": true, "node": true, "commonjs": true, "es6": true }, "extends": "eslint:recommended", "ecmaFeatures": { "jsx": true, "modules": true }, "parserOptions": { "sourceType": "module" }, "rules": { "indent": [ "error", 4 ], "linebreak-style": [ "error", "unix" ], "quotes": [ "warn", "single" ], "semi": [ "error", "always" ], "no-unreachable": [ "warn" ], "no-console":"off" }, "plugins": [ "react" ] } ================================================ FILE: cv/lsd/README.md ================================================ # Line Segment Detector Module ## description Line detection using Line Segment Detector ([LSD][IPOL]) algorithm ![lsd](https://raw.github.com/wiki/wellflat/imageprocessing-labs/images/lsd_apply.jpg) see also [blog entry][entry] ### demo [Line Segment Detector Demo](http://rest-term.com/labs/html5/lsd/index.html) [(mirror)](https://secret-nether-01.herokuapp.com/lsd/) [![lsd_demo](https://raw.github.com/wiki/wellflat/imageprocessing-labs/images/lsd_demo.jpg)](http://rest-term.com/labs/html5/lsd/index.html) ## usage in JavaScript (ES2015) ```js import LSD from './lsd'; // input 8bit gray scale image import SampleImage from './sample.jpg'; const image = new Image(); image.src = SampleImage; image.onload = () => { const width = image.width; const height = image.height; const canvas = document.createElement('canvas'); const context = canvas.getContext('2d'); canvas.width = width; canvas.height = height; context.drawImage(image, 0, 0, width, height); document.getElementById('content').appendChild(canvas); const imageData = context.getImageData(0, 0, width, height); const detector = new LSD(); const lines = detector.detect(imageData); console.log('lines: ' + lines.length.toString()); detector.drawSegments(context, lines); }; ``` ### React Component ```js import React from 'react'; import ReactDOM from 'react-dom'; import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'; import LSDComponent from './lsd_component'; import SampleImage from './sample.jpg'; ReactDOM.render( , document.getElementById('content') ); ``` license ---------- Copyright © 2017 wellflat Licensed under the [MIT License][MIT] [MIT]: http://www.opensource.org/licenses/mit-license.php [IPOL]: http://www.ipol.im/pub/art/2012/gjmr-lsd/ [entry]: http://rest-term.com/archives/3393/ ================================================ FILE: cv/lsd/jsconfig.json ================================================ { "compilerOptions": { "target": "es6", "module": "commonjs" } } ================================================ FILE: cv/lsd/line-segment-detector/.gitignore ================================================ node_modules dst ================================================ FILE: cv/lsd/line-segment-detector/README.md ================================================ # Line Segment Detector Module ## description Line detection using Line Segment Detector ([LSD][IPOL]) algorithm ![lsd](https://raw.github.com/wiki/wellflat/imageprocessing-labs/images/lsd_apply.jpg) see also [blog entry][entry] ## install ``` npm install line-segment-detector.js ``` - Todo npm publish ready: add package.json, unit test ### demo [Line Segment Detector Demo](http://rest-term.com/labs/html5/lsd/index.html) [(mirror)](https://secret-nether-5891.herokuapp.com/lsd/) [![lsd_demo](https://raw.github.com/wiki/wellflat/imageprocessing-labs/images/lsd_demo.jpg)](http://rest-term.com/labs/html5/lsd/index.html) ## usage in JavaScript (ES2015) ```js import LSD from './lsd'; // input 8bit gray scale image import SampleImage from './sample.jpg'; const image = new Image(); image.src = SampleImage; image.onload = () => { const width = image.width; const height = image.height; const canvas = document.createElement('canvas'); const context = canvas.getContext('2d'); canvas.width = width; canvas.height = height; context.drawImage(image, 0, 0, width, height); document.getElementById('content').appendChild(canvas); const imageData = context.getImageData(0, 0, width, height); const detector = new LSD(); const lines = detector.detect(imageData); console.log('lines: ' + lines.length.toString()); detector.drawSegments(context, lines); }; ``` ### React Component ```js import React from 'react'; import ReactDOM from 'react-dom'; import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'; import LSDComponent from './lsd_component'; import SampleImage from './sample.jpg'; ReactDOM.render( , document.getElementById('content') ); ``` license ---------- Copyright © 2017 wellflat Licensed under the [MIT License][MIT] [MIT]: http://www.opensource.org/licenses/mit-license.php [IPOL]: http://www.ipol.im/pub/art/2012/gjmr-lsd/ [entry]: http://rest-term.com/archives/3393/ ================================================ FILE: cv/lsd/line-segment-detector/jsconfig.json ================================================ { "compilerOptions": { "target": "es6", "module": "commonjs" } } ================================================ FILE: cv/lsd/line-segment-detector/package.json ================================================ { "name": "line-segment-detector.js", "version": "1.0.0", "description": "Line Segment Detector", "main": "./dst/lsd.js", "types": "./dst/lsd.d.ts", "files": [ "dst" ], "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "prod": "rimraf dst && webpack -p", "dev": "rimraf dst && webpack -d", "prepublish": "npm run prod" }, "keywords": [], "author": "wellflat", "license": "MIT", "dependencies": {}, "devDependencies": { "eslint": "^4.5.0", "rimraf": "^2.6.1", "ts-loader": "^2.3.3", "typescript": "^2.4.2", "webpack": "^3.5.5" } } ================================================ FILE: cv/lsd/line-segment-detector/src/funcs.ts ================================================ import { Edge } from './types'; /** * constants and utility math functions */ const M_3_2_PI = (3 * Math.PI) / 2; const M_2__PI = (2 * Math.PI); const M_LN10 = 2.30258509299404568402; const NOT_DEF = -1024.0; const USED = 1; const NOT_USED = 0; const RELATIVE_ERROR_FACTOR = 100.0; const DEG_TO_RADS = Math.PI / 180; const REFINE_NONE = 0; const REFINE_STD = 1; const REFINE_ADV = 2; const logGamma = (x: number) => x > 15.0 ? logGammaWindschitl(x) : logGammaLanczos(x); const logGammaWindschitl = (x: number) => 0.918938533204673 + (x - 0.5) * Math.log(x) - x + 0.5 * x * Math.log(x * Math.sinh(1 / x) + 1 / (810.0 * Math.pow(x, 6.0))); function logGammaLanczos(x: number) { const q = [ 75122.6331530, 80916.6278952, 36308.2951477, 8687.24529705, 1168.92649479, 83.8676043424, 2.50662827511 ]; let a = (x + 0.5) * Math.log(x + 5.5) - (x + 5.5); let b = 0; for(let n = 0; n < 7; ++n) { a -= Math.log(x + n); b += q[n] * Math.pow(x, n); } return a + Math.log(b); } const distSq = (x1: number, x2: number, y1: number, y2: number) => (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1); const dist = (x1: number, x2: number, y1: number, y2: number) => Math.sqrt(distSq(x1, y1, x2, y2)); function angleDiffSigned(a: number, b: number) { let diff = a - b; const PI = Math.PI; while (diff <= -PI) { diff += M_2__PI; } while (diff > PI) { diff -= M_2__PI; } return diff; } function angleDiff(a: number, b: number) { const value = angleDiffSigned(a, b); return value >= 0 ? value : -value; } function doubleEqual(a: number, b: number) { if (a == b) { return true; } const diff = a - b; const absDiff = diff >= 0 ? diff : -diff; const aa = a >= 0 ? a : - a; const bb = b >= 0 ? b : - b; let absMax = (aa > bb) ? aa : bb; const MIN_VALUE = Number.MIN_VALUE; if (absMax < MIN_VALUE) { absMax = MIN_VALUE; } return (absDiff / absMax) <= (RELATIVE_ERROR_FACTOR * Number.EPSILON); } function AsmallerB_XoverY(a: Edge, b: Edge) { if (a.p.x == b.p.x) { return Number(a.p.y < b.p.y); } else { return Number(a.p.x < b.p.x); } } export { M_3_2_PI, M_2__PI, M_LN10, NOT_DEF, USED, NOT_USED, RELATIVE_ERROR_FACTOR, DEG_TO_RADS, REFINE_NONE, REFINE_STD, REFINE_ADV, logGamma, dist, distSq, angleDiff, doubleEqual, AsmallerB_XoverY }; ================================================ FILE: cv/lsd/line-segment-detector/src/lsd.ts ================================================ /** * Line Segment Detector (LSD) module * @author https://github.com/wellflat */ import * as funcs from './funcs'; import { Vec4, Point, CoorList, RegionPoint, Rect, Edge } from './types'; /** * Create a LineSegmentDetector object. * Specifying scale, number of subdivisions for the image, * should the lines be refined and other constants as follows: * * @param refine How should the lines found be refined? * REFINE_NONE - No refinement applied. * REFINE_STD - Standard refinement is applied. E.g. breaking arches into smaller line approximations. * REFINE_ADV - Advanced refinement. Number of false alarms is calculated, * lines are refined through increase of precision, decrement in size, etc. * @param scale The scale of the image that will be used to find the lines. Range (0..1]. * @param sigmaScale Sigma for Gaussian filter is computed as sigma = _sigma_scale/_scale. * @param quant Bound to the quantization error on the gradient norm. * @param angTh Gradient angle tolerance in degrees. * @param logEps Detection threshold: -log10(NFA) > _log_eps * @param densityTh Minimal density of aligned region points in rectangle. * @param nBins Number of bins in pseudo-ordering of gradient modulus. */ export default class LSD { image?: ImageData; private imageData?: Uint8ClampedArray; width = 0; height = 0; list: CoorList[] = []; angles?: Float64Array; modgrad?: Float64Array; used?: Uint8Array; constructor( public refineType = funcs.REFINE_NONE, public scale = 0.8, public sigmaScale = 0.6, public quant = 2.0, public angTh = 22.5, public logEps = 0.0, public densityTh = 0.7, public nBins = 1024 ) { } /** * Detect lines in the input image. * @param {ImageData} image * @return {Vec4[]} */ detect(image: ImageData) { this.image = image; this.width = image.width; this.height = image.height; const lines = this.lsd(); return lines; } /** * Draws the line segments on a given image. * @param {CanvasRenderingContext2D} context * @param {Vec4[]} lines * @param {string} color */ drawSegments(context: CanvasRenderingContext2D, lines: Vec4[], color = '#ff0000') { context.strokeStyle = color; context.lineWidth = 1; lines.forEach(v => { context.beginPath(); context.moveTo(v.x1, v.y1); context.lineTo(v.x2, v.y2); context.stroke(); context.closePath(); }); } /** * for debug * @param {CanvasRenderingContext2D} context */ putImageData(context: CanvasRenderingContext2D) { let src = this.imageData, image = context.createImageData(this.width, this.height), dst = image.data, len = image.data.length; if (!src) throw new Error('imageData required'); // type guard for (let i = 0; i < len; i += 4) { dst[i] = dst[i + 1] = dst[i + 2] = src[i/4]; dst[i + 3] = 255; } context.putImageData(image, 0, 0); } /** * @return {Vec4[]} Return: A vector of Vec4f elements specifying the beginning and ending point of a line. * Where Vec4f is (x1, y1, x2, y2), point 1 is the start, point 2 - end. * Returned lines are strictly oriented depending on the gradient. */ lsd() { if (!this.image || !this.angles) throw new Error('image and angles required'); // type guard /** @type {Vec4[]} */ let lines = []; const prec = Math.PI * this.angTh / 180; const p = this.angTh / 180; const rho = this.quant / Math.sin(prec); if (this.scale != 1) { const sigma = this.scale < 1 ? this.sigmaScale / this.scale : this.sigmaScale; const sprec = 3; const h = Math.ceil(sigma * Math.sqrt(2 * sprec * Math.log(10.0))); const kSize = 1 + 2 * h; const reshaped = this.reshape(this.image); this.imageData = this.gaussianBlur(reshaped, kSize, sigma); this.computeLevelLineAngles(rho, this.nBins); } else { this.imageData = this.reshape(this.image); this.computeLevelLineAngles(rho, this.nBins); } const LOG_NT = 5 * (Math.log10(this.width) + Math.log10(this.height)) / 2 + Math.log10(11.0); const minRegSize = -LOG_NT / Math.log10(p); this.used = new Uint8Array(this.imageData.length); for (let i = 0, listSize = this.list.length; i < listSize; i++) { const point = this.list[i].p; if ((this.at(this.used, point) === funcs.NOT_USED) && (this.at(this.angles, point) !== funcs.NOT_DEF)) { let regAngle = 0.0; let reg: RegionPoint[] = []; regAngle = this.regionGrow(this.list[i].p, reg, regAngle, prec); if (reg.length < minRegSize) { continue; } let rect = new Rect(); this.region2Rect(reg, regAngle, prec, p, rect); let logNfa = -1; if (this.refineType > funcs.REFINE_NONE) { if (!this.refine(reg, regAngle, prec, p, rect, this.densityTh)) { continue; } if (this.refineType >= funcs.REFINE_ADV) { logNfa = this.improveRect(rect); if (logNfa <= this.logEps) { continue; } } } rect.x1 += 0.5; rect.y1 += 0.5; rect.x2 += 0.5; rect.y2 += 0.5; /* if (this.scale != 1) { rect.x1 /= this.scale; rect.y1 /= this.scale; rect.x2 /= this.scale; rect.y2 /= this.scale; rect.width /= this.scale; } */ lines.push(new Vec4(rect.x1, rect.y1, rect.x2, rect.y2)); } } return lines; } /** * @param {number} threshold The minimum value of the angle that is considered defined, otherwise NOTDEF * @param {number} nBins The number of bins with which gradients are ordered by, using bucket sort. */ computeLevelLineAngles(threshold: number, nBins: number) { const imageData = this.imageData; if (!imageData) throw new Error('imageData required'); // type guard const width = this.width; const height = this.height; this.angles = new Float64Array(imageData.length); this.modgrad = new Float64Array(imageData.length); this.angles = this.setRow(this.angles, height - 1, funcs.NOT_DEF); this.angles = this.setCol(this.angles, width - 1, funcs.NOT_DEF); let maxGrad = -1.0; for (let y = 0; y < height - 1; y++) { const step = y * width; const nextStep = (y + 1) * width; for (let x = 0; x < width - 1; x++) { const DA = imageData[x + 1 + nextStep] - imageData[x + step]; const BC = imageData[x + 1 + step] - imageData[x + nextStep]; const gx = DA + BC; const gy = DA - BC; const norm = Math.sqrt((gx * gx + gy * gy) / 4.0); this.modgrad[x + step] = norm; if (norm <= threshold) { this.angles[x + step] = funcs.NOT_DEF; } else { this.angles[x + step] = Math.atan2(gx, -gy); if (norm > maxGrad) { maxGrad = norm; } } } } /** @type {CoorList[]} */ let rangeS = []; rangeS.length = nBins; /** @type {CoorList[]} */ let rangeE = []; rangeE.length = nBins; let count = 0; const binCoef = (maxGrad > 0) ? (nBins - 1) / maxGrad : 0; for (let y = 0; y < height - 1; y++) { let step = y * width; for (let x = 0; x < width - 1; x++) { let i = Math.floor(this.modgrad[x + step] * binCoef); if (!rangeE[i]) { this.list[count] = new CoorList(); rangeE[i] = rangeS[i] = this.list[count]; count++; } else { this.list[count] = new CoorList(); rangeE[i] = this.list[count]; rangeE[i].next = this.list[count]; count++; } rangeE[i].p = new Point(x, y); rangeE[i].next = null; } } let idx = nBins - 1; for (; idx > 0 && !rangeS[idx]; idx--) { // do nothing. } let start = rangeS[idx]; let endIdx = idx; if (start) { while (idx > 0) { idx--; if (rangeS[idx]) { rangeE[endIdx].next = rangeS[idx]; rangeE[endIdx] = rangeE[idx]; endIdx = idx; } } } } regionGrow(s: Point, reg: RegionPoint[], regAngle: number, prec: number) { if (!this.used || !this.angles || !this.modgrad) throw new Error('used, angles and modgrad required'); // type guard let seed = new RegionPoint(); seed.x = s.x; seed.y = s.y; seed.used = this.at(this.used, s); regAngle = this.at(this.angles, s); seed.angle = regAngle; seed.modgrad = this.at(this.modgrad, s); seed.used = funcs.USED; reg.push(seed); let sumdx = Math.cos(regAngle); let sumdy = Math.sin(regAngle); for (let i = 0; i < reg.length; i++) { const rpoint = reg[i], xxMin = Math.max(rpoint.x - 1, 0), xxMax = Math.min(rpoint.x + 1, this.width - 1), yyMin = Math.max(rpoint.y - 1, 0), yyMax = Math.min(rpoint.y + 1, this.height - 1); for (let yy = yyMin; yy <= yyMax; yy++) { const step = yy * this.width; for (let xx = xxMin; xx <= xxMax; xx++) { let isUsed = this.used[xx + step]; if (isUsed != funcs.USED && this.isAligned(xx, yy, regAngle, prec)) { const angle = this.angles[xx + step]; isUsed = funcs.USED; this.used[xx + step] = funcs.USED; let regionPoint = new RegionPoint( xx, yy, angle, this.modgrad[xx + step], isUsed ); reg.push(regionPoint); sumdx += Math.cos(angle); sumdy += Math.sin(angle); regAngle = Math.atan2(sumdy, sumdx); } } } } return regAngle; } isAligned(x: number, y: number, theta: number, prec: number) { if (x < 0 || y < 0 || x >= this.width || y >= this.height) { return false; } const a = this.angles![x + y * this.width]; if (a === funcs.NOT_DEF) { return false; } let nTheta = theta - a; if (nTheta < 0) { nTheta = -nTheta; } if (nTheta > funcs.M_3_2_PI) { nTheta -= funcs.M_2__PI; if (nTheta < 0) { nTheta = -nTheta; } } return nTheta <= prec; } region2Rect(reg: RegionPoint[], regAngle: number, prec: number, p: number, rect: Rect) { let x = 0, y = 0, sum = 0; for (let i = 0; i < reg.length; i++) { const pnt = reg[i]; const weight = pnt.modgrad; x += pnt.x * weight; y += pnt.y * weight; sum += weight; } if (sum <= 0) { throw new Error('weighted sum must differ from 0'); } x /= sum; y /= sum; const theta = this.getTheta(reg, x, y, regAngle, prec); const dx = Math.cos(theta); const dy = Math.sin(theta); let lMin = 0, lMax = 0, wMin = 0, wMax = 0; for (let i = 0; i < reg.length; i++) { let regdx = reg[i].x - x; let regdy = reg[i].y - y; let l = regdx * dx + regdy * dy; let w = -regdx * dy + regdy * dx; if (l > lMax) { lMax = l; } else if (l < lMin) { lMin = l; } if (w > wMax) { wMax = w; } else if (w < wMin) { wMin = w; } } rect.x1 = x + lMin * dx; rect.y1 = y + lMin * dy; rect.x2 = x + lMax * dx; rect.y2 = y + lMax * dy; rect.width = wMax - wMin; rect.x = x; rect.y = y; rect.theta = theta; rect.dx = dx; rect.dy = dy; rect.prec = prec; rect.p = p; if (rect.width < 1.0) { rect.width = 1.0; } } getTheta(reg: RegionPoint[], x: number, y: number, regAngle: number, prec: number) { let ixx = 0.0, iyy = 0.0, ixy = 0.0; for (let i = 0; i < reg.length; i++) { const regx = reg[i].x; const regy = reg[i].y; const weight = reg[i].modgrad; let dx = regx - x; let dy = regy - y; ixx += dy * dy * weight; iyy += dx * dx * weight; ixy -= dx * dy * weight; } let check = (funcs.doubleEqual(ixx, 0) && funcs.doubleEqual(iyy, 0) && funcs.doubleEqual(ixy, 0)); if (check) { throw new Error('check if inertia matrix is null'); } let lambda = 0.5 * (ixx + iyy - Math.sqrt((ixx - iyy) * (ixx - iyy) + 4.0 * ixy * ixy)); let theta = (Math.abs(ixx) > Math.abs(iyy)) ? Math.atan2(lambda - ixx, ixy) : Math.atan2(ixy, lambda - iyy); if (funcs.angleDiff(theta, regAngle) > prec) { theta += Math.PI; } return theta; } refine(reg: RegionPoint[], regAngle: number, prec: number, p: number, rect: Rect, densityTh: number) { let density = reg.length / (funcs.dist(rect.x1, rect.y1, rect.x2, rect.y2) * rect.width); if (density >= densityTh) { return true; } let xc = reg[0].x; let yc = reg[0].y; const angC = reg[0].angle; let sum = 0, sSum = 0, n = 0; for (let i = 0; i < reg.length; i++) { reg[i].used = funcs.NOT_USED; if (funcs.dist(xc, yc, reg[i].x, reg[i].y) < rect.width) { const angle = reg[i].angle; let angD = funcs.angleDiff(angle, angC); sum += angD; sSum += angD * angD; n++; } let meanAngle = sum / n; let tau = 2.0 * Math.sqrt((sSum - 2.0 * meanAngle * sum) / n + meanAngle * meanAngle); this.regionGrow(new Point(reg[0].x, reg[0].y), reg, regAngle, tau); if (reg.length < 2) { return false; } this.region2Rect(reg, regAngle, prec, p, rect); density = reg.length / (funcs.dist(rect.x1, rect.y1, rect.x2, rect.y2) * rect.width); if (density < densityTh) { return this.reduceRegionRadius(reg, regAngle, prec, p, rect, density, densityTh); } else { return true; } } return false; // type guard (unreachable) } reduceRegionRadius(reg: RegionPoint[], regAngle: number, prec: number, p: number, rect: Rect, density: number, densityTh: number) { let xc = reg[0].x; let yc = reg[0].y; let radSq1 = funcs.distSq(xc, yc, rect.x1, rect.y1); let radSq2 = funcs.distSq(xc, yc, rect.x2, rect.y2); let radSq = radSq1 > radSq2 ? radSq1 : radSq2; while (density < densityTh) { radSq *= 0.75 * 0.75; // reduce region's radius to 75% for (let i = 0; i < reg.length; i++) { if (funcs.distSq(xc, yc, reg[i].x, reg[i].y) > radSq) { // remove point from the region reg[i].used = funcs.NOT_USED; const tmp = reg[i]; reg[i] = reg[reg.length - 1]; reg[reg.length - 1] = tmp; reg.pop(); --i; } } if (reg.length < 2) { return false; } this.region2Rect(reg, regAngle, prec, p, rect); density = reg.length / (funcs.dist(rect.x1, rect.y1, rect.x2, rect.y2) * rect.width); } return true; } improveRect(rect: Rect) { let delta = 0.5; let delta2 = delta / 2.0; let logNfa = this.rectNfa(rect); if (logNfa > this.logEps) { return logNfa; } let r = new Rect(); r.copy(rect); for (let n = 0; n < 5; n++) { r.p /= 2; r.prec = r.p * Math.PI; let logNfaNew = this.rectNfa(rect); if (logNfaNew > logNfa) { logNfa = logNfaNew; rect.copy(r); } } if (logNfa > this.logEps) { return logNfa; } r.copy(rect); for (let n = 0; n < 5; n++) { if ((r.width - delta) >= 0.5) { r.width -= delta; let logNfaNew = this.rectNfa(r); if (logNfaNew > logNfa) { rect.copy(r); logNfa = logNfaNew; } } } if (logNfa > this.logEps) { return logNfa; } r.copy(rect); for (let n = 0; n < 5; n++) { if ((r.width - delta) >= 0.5) { r.x1 -= -r.dy * delta2; r.y1 -= r.dx * delta2; r.x2 -= -r.dy * delta2; r.y2 -= r.dx * delta2; r.width -= delta; let logNfaNew = this.rectNfa(r); if (logNfaNew > logNfa) { rect.copy(r); logNfa = logNfaNew; } } } if (logNfa > this.logEps) { return logNfa; } r.copy(rect); for (let n = 0; n < 5; n++) { if ((r.width - delta) >= 0.5) { r.p /= 2; r.prec = r.p * Math.PI; let logNfaNew = this.rectNfa(r); if (logNfaNew > logNfa) { rect.copy(r); logNfa = logNfaNew; } } } return logNfa; } rectNfa(rect: Rect) { let totalPts = 0, algPts = 0, halfWidth = rect.width / 2.0, dyhw = rect.dy * halfWidth, dxhw = rect.dx * halfWidth, orderedX = [ new Edge(), new Edge(), new Edge(), new Edge() ], minY = orderedX[0], maxY = orderedX[0]; orderedX[0].p.x = rect.x1 - dyhw; orderedX[0].p.y = rect.y1 + dxhw; orderedX[1].p.x = rect.x2 - dyhw; orderedX[1].p.y = rect.y2 + dxhw; orderedX[2].p.x = rect.x2 + dyhw; orderedX[2].p.y = rect.y2 - dxhw; orderedX[3].p.x = rect.x1 + dyhw; orderedX[3].p.y = rect.y1 - dxhw; orderedX.sort(funcs.AsmallerB_XoverY); for (let i = 1; i < 4; i++) { if (minY.p.y > orderedX[i].p.y) { minY = orderedX[i]; } if (maxY.p.y < orderedX[i].p.y) { maxY = orderedX[i]; } } minY.taken = true; let leftmost = null; for (let i = 0; i < 4; i++) { if (!orderedX[i].taken) { if (!leftmost) { leftmost = orderedX[i]; } else if (leftmost.p.x > orderedX[i].p.x) { leftmost = orderedX[i]; } } } if (!leftmost) throw new Error('leftmost error'); // type guard leftmost.taken = true; let rightmost = null; for (let i = 0; i < 4; i++) { if (!orderedX[i].taken) { if (!rightmost) { rightmost = orderedX[i]; } else if (rightmost.p.x < orderedX[i].p.x) { rightmost = orderedX[i]; } } } if (!rightmost) throw new Error('rightmost error'); // type guard rightmost.taken = true; let tailp = null; for (let i = 0; i < 4; i++) { if (!orderedX[i].taken) { if (!tailp) { tailp = orderedX[i]; } else if (tailp.p.x > orderedX[i].p.x) { tailp = orderedX[i]; } } } if (!tailp) throw new Error('tailp error'); // type guard tailp.taken = true; let flstep = (minY.p.y != leftmost.p.y) ? (minY.p.x + leftmost.p.x) / (minY.p.y - leftmost.p.y) : 0; let slstep = (leftmost.p.y != tailp.p.x) ? (leftmost.p.x = tailp.p.x) / (leftmost.p.y - tailp.p.x) : 0; let frstep = (minY.p.y != rightmost.p.y) ? (minY.p.x - rightmost.p.x) / (minY.p.y - rightmost.p.y) : 0; let srstep = (rightmost.p.y != tailp.p.x) ? (rightmost.p.x - tailp.p.x) / (rightmost.p.y - tailp.p.x) : 0; let lstep = flstep, rstep = frstep; let leftX = minY.p.x, rightX = minY.p.x; let minIter = minY.p.y; let maxIter = maxY.p.y; for (let y = minIter; y <= maxIter; y++) { if (y < 0 || y >= this.height) { continue; } for (let x = leftX; x <= rightX; x++) { if (x < 0 || x >= this.width) { continue; } totalPts++; if (this.isAligned(x, y, rect.theta, rect.prec)) { algPts++; } } if (y >= leftmost.p.y) { lstep = slstep; } if (y >= rightmost.p.y) { rstep = srstep; } leftX += lstep; rightX += rstep; } return this.nfa(totalPts, algPts, rect.p); } nfa(n: number, k: number, p: number) { const LOG_NT = 5 * (Math.log10(this.width) + Math.log10(this.height)) / 2 + Math.log10(11.0); if (n == 0 || k == 0) { return -LOG_NT; } if (n == k) { return -LOG_NT - n * Math.log10(p); } let pTerm = p / (1 - p); let log1Term = (n + 1) - funcs.logGamma(k + 1) - funcs.logGamma(n - k + 1) + k * Math.log(p) + (n - k) * Math.log(1.0 - p); let term = Math.exp(log1Term); if (funcs.doubleEqual(term, 0)) { if (k > n * p) { return -log1Term / funcs.M_LN10 - LOG_NT; } else { return -LOG_NT; } } let binTail = term; let tolerance = 0.1; for (let i = k + 1; i <= n; i++) { let binTerm = (n - i + 1) / i; let multTerm = binTerm * pTerm; term *= multTerm; binTail += term; if (binTerm < 1) { let err = term * ((1 - Math.pow(multTerm, (n - i + 1))) / (1 - multTerm) - 1); if (err < tolerance * Math.abs(-Math.log10(binTail) - LOG_NT) * binTail) { break; } } } return -Math.log10(binTail) - LOG_NT; } gaussianBlur(imageData: Uint8ClampedArray, kSize: number, sigma: number) { let width = this.width, height = this.height, src = imageData, ctx = document.createElement('canvas').getContext('2d'), tmp = ctx!.createImageData(width, height), dst = null, kernel = this.getGaussianKernel(kSize, sigma), r = (kSize - 1) / 2; const tmp2 = this.reshape(tmp); dst = new Uint8ClampedArray(tmp2.length); // separate 2d-filter for (let y = 0; y < height; y++) { let step = y * width; for (let x = 0; x < width; x++) { let buff = 0; let i = x + step; let k = 0; for (let kx = -r; kx <= r; kx++) { let px = x + kx; if (px <= 0 || width <= px) { px = x; } let j = px + step; buff += src[j] * kernel[k]; k++; } tmp2[i] = buff; } } for (let x = 0; x < width; x++) { for (let y = 0; y < height; y++) { let step = y * width; let buff = 0; let i = x + step; let k = 0; for (let ky = -r; ky <= r; ky++) { let py = y + ky; let kStep = ky * width; if (py <= 0 || height <= py) { py = y; kStep = 0; } let j = i + kStep; buff += tmp2[j] * kernel[k]; k++; } dst[i] = buff; } } return dst; } getGaussianKernel(kSize: number, sigma: number) { // 1d-kernel let kernel = []; let sigmaX = sigma > 0 ? sigma : ((kSize - 1) * 0.5 - 1) * 0.3 + 0.8; let scale2X = -0.5 / (sigmaX * sigmaX); let sum = 0.0; for (let i = 0; i < kSize; i++) { let x = i - (kSize - 1) * 0.5; kernel[i] = Math.exp(scale2X * x * x); sum += kernel[i]; } sum = 1. / sum; for (let i = 0; i < kSize; i++) { kernel[i] *= sum; } return kernel; } reshape(image: ImageData) { let src = image.data; let reshaped = new Uint8ClampedArray(src.length / 4); let len = reshaped.length; for (let i = 0; i < len; i++) { reshaped[i] = src[i * 4]; } return reshaped; } at(data: Uint8Array|Float64Array, p: Point) { return data[p.x + (p.y * this.width)]; } row(data: Float64Array, rowIndex: number) { let i = rowIndex * this.width; return data.subarray(i, i + this.width); } setRow(data: Float64Array, index: number, value: number) { let from = index * this.width; let to = from + this.width; for (let i = from; i < to; i++) { data[i] = value; } return data; } setCol(data: Float64Array, index: number, value: number) { let to = this.height * this.width; let step = this.width; for (let i = index; i < to; i += step) { data[i] = value; } return data; } } ================================================ FILE: cv/lsd/line-segment-detector/src/types.ts ================================================ class Vec4 { constructor(public x1 = 0, public y1 = 0, public x2 = 0, public y2 = 0) { } } class Point { constructor(public x = 0.0, public y = 0.0) { } } class CoorList { p: Point; next: CoorList | null; constructor() { this.p = new Point(); } } class RegionPoint { constructor(public x = 0, public y = 0, public angle = 0.0, public modgrad = 0.0, public used?: number) { } } class Rect { constructor( public x1 = 0, public y1 = 0, public x2 = 0, public y2 = 0, public width = 0, public height = 0, public x = 0, public y = 0, public theta = 0, public dx = 0, public dy = 0, public prec = 0, public p = 0 ) { } copy(rect: Rect) { this.x1 = rect.x1; this.y1 = rect.y1; this.x2 = rect.x2; this.y2 = rect.y2; this.width = rect.width; this.height = rect.height; this.x = rect.x; this.y = rect.y; this.theta = rect.theta; this.dx = rect.dx; this.dy = rect.dy; this.prec = rect.prec; this.p = rect.p; } } class Edge { p: Point; taken?: boolean; constructor() { this.p = new Point(); } } export { Vec4, Point, CoorList, RegionPoint, Rect, Edge }; ================================================ FILE: cv/lsd/line-segment-detector/tsconfig.json ================================================ { "compileOnSave": true, "compilerOptions": { "module": "commonjs", "target": "es5", "declaration": true, "noImplicitAny": true, "noImplicitThis": true, "noImplicitReturns": true, "noImplicitUseStrict": true, "noUnusedLocals": true, "noUnusedParameters": true, "preserveConstEnums": true, "sourceMap": true, "strictNullChecks": true, "outDir": "", "lib": [ "dom", "es2015" ] }, "exclude": [ "node_modules", "dst" ] } ================================================ FILE: cv/lsd/line-segment-detector/webpack.config.js ================================================ const webpack = require('webpack'); const path = require('path'); module.exports = { entry: path.join(__dirname, './src/lsd.ts'), output: { path: path.join(__dirname, './dst'), filename: 'lsd.js', library: 'lineSegmentDetector', libraryTarget: 'umd' }, module: { rules: [ { test: /\.ts$/, exclude: /(node_modules|bower_components)/, loader: 'ts-loader' } ] }, resolve: { extensions: ['.js', '.ts'] }, devtool: 'source-map' }; ================================================ FILE: cv/lsd/package.json ================================================ { "name": "lsd", "version": "1.0.0", "description": "LSD: Line Segment Detector", "main": "src/app.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "prod": "node_modules/.bin/webpack -p", "dev": "node_modules/.bin/webpack -d", "start": "node_modules/.bin/webpack-dev-server --hot --inline" }, "keywords": [], "author": "wellflat", "license": "ISC", "babel": { "presets": [ "es2015", "react" ] }, "dependencies": { "material-ui": "^0.17.4", "react": "^15.4.2", "react-dom": "^15.4.2", "react-tap-event-plugin": "^2.0.1" }, "devDependencies": { "babel": "^6.23.0", "babel-core": "^6.21.0", "babel-loader": "^9.1.2", "babel-preset-es2015": "^6.18.0", "babel-preset-react": "^6.16.0", "babel-preset-react-hmre": "^1.1.1", "babel-preset-stage-0": "^6.24.1", "babel-register": "^6.18.0", "eslint": "^4.18.2", "eslint-plugin-react": "^6.9.0", "file-loader": "^0.11.0", "html-loader": "^0.4.4", "html-webpack-plugin": "^5.5.0", "url-loader": "^0.5.7", "webpack": "^5.76.0", "webpack-dev-server": ">=4.11.1" } } ================================================ FILE: cv/lsd/src/app.js ================================================ import React from 'react'; import ReactDOM from 'react-dom'; import injectTapEventPlugin from 'react-tap-event-plugin'; import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'; import AppBar from 'material-ui/AppBar'; import Paper from 'material-ui/Paper'; import { RadioButton, RadioButtonGroup } from 'material-ui/RadioButton'; import LSDComponent from './lsd_component'; import GardenImage from './gardenterrace_gray.jpg'; import MidtownImage from './midtown_gray.jpg'; import BuildingImage from './building_gray.jpg'; import TokyoImage from './tokyostation_gray.jpg'; injectTapEventPlugin(); const style = { appBar: { margin: '10px 0 10px 0' }, paper: { display: 'inline', margin: '15px' }, radio: { fontSize: '1em', } }; const App = props => (
changeImage(e)} style={style.radio}>
); ReactDOM.render( , document.getElementById('content') ); const changeImage = e => { const target = e.target.value; let image = null; switch (target) { case 'image1': image = BuildingImage; break; case 'image2': image = MidtownImage; break; case 'image3': image = GardenImage; break; case 'image4': image = TokyoImage; break; } ReactDOM.render( , document.getElementById('content') ); } /* const image = new Image(); image.src = BuildingImage; image.onload = () => { const width = image.width; const height = image.height; const canvas = document.createElement('canvas'); const context = canvas.getContext('2d'); canvas.width = width; canvas.height = height; context.drawImage(image, 0, 0, width, height); document.getElementById('content').appendChild(canvas); const imageData = context.getImageData(0, 0, width, height); let scale = 0.8; const detector = new LSD(0, scale); const lines = detector.detect(imageData); console.log('lines: ' + lines.length.toString()); detector.drawSegments(context, lines); }; */ ================================================ FILE: cv/lsd/src/funcs.js ================================================ /** * constants and utility math functions */ const M_3_2_PI = (3 * Math.PI) / 2; const M_2__PI = (2 * Math.PI); const M_LN10 = 2.30258509299404568402; const NOT_DEF = -1024.0; const USED = 1; const NOT_USED = 0; const RELATIVE_ERROR_FACTOR = 100.0; const DEG_TO_RADS = Math.PI / 180; const REFINE_NONE = 0; const REFINE_STD = 1; const REFINE_ADV = 2; const logGamma = x => x > 15.0 ? logGammaWindschitl(x) : logGammaLanczos(x); const logGammaWindschitl = x => 0.918938533204673 + (x - 0.5) * Math.log(x) - x + 0.5 * x * Math.log(x * Math.sinh(1 / x) + 1 / (810.0 * Math.pow(x, 6.0))); function logGammaLanczos(x) { const q = [ 75122.6331530, 80916.6278952, 36308.2951477, 8687.24529705, 1168.92649479, 83.8676043424, 2.50662827511 ]; let a = (x + 0.5) * Math.log(x + 5.5) - (x + 5.5); let b = 0; for(let n = 0; n < 7; ++n) { a -= Math.log(x + n); b += q[n] * Math.pow(x, n); } return a + Math.log(b); } const distSq = (x1, x2, y1, y2) => (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1); const dist = (x1, x2, y1, y2) => Math.sqrt(distSq(x1, y1, x2, y2)); /** * @param {number} a * @param {number} b * @return {number} */ function angleDiffSigned(a, b) { let diff = a - b; const PI = Math.PI; while (diff <= -PI) { diff += M_2__PI; } while (diff > PI) { diff -= M_2__PI; } return diff; } /** * @param {number} a * @param {number} b * @return {number} */ function angleDiff(a, b) { const value = angleDiffSigned(a, b); return value >= 0 ? value : -value; } /** * @param {number} a * @param {number} b * @return {number} */ function doubleEqual(a, b) { if (a == b) { return true; } const diff = a - b; const absDiff = diff >= 0 ? diff : -diff; const aa = a >= 0 ? a : - a; const bb = b >= 0 ? b : - b; let absMax = (aa > bb) ? aa : bb; const MIN_VALUE = Number.MIN_VALUE; if (absMax < MIN_VALUE) { absMax = MIN_VALUE; } return (absDiff / absMax) <= (RELATIVE_ERROR_FACTOR * Number.EPSILON); } /** * * @param {Edge} a * @param {Edge} b */ function AsmallerB_XoverY(a, b) { if (a.p.x == b.p.x) { return a.p.y < b.p.y; } else { return a.p.x < b.p.x; } } export { M_3_2_PI, M_2__PI, M_LN10, NOT_DEF, USED, NOT_USED, RELATIVE_ERROR_FACTOR, DEG_TO_RADS, REFINE_NONE, REFINE_STD, REFINE_ADV, logGamma, dist, distSq, angleDiff, doubleEqual, AsmallerB_XoverY }; ================================================ FILE: cv/lsd/src/index.html ================================================ Line Segment Detector Demo
================================================ FILE: cv/lsd/src/lsd.js ================================================ /** * Line Segment Detector (LSD) module * @author https://github.com/wellflat */ import * as funcs from './funcs'; import { Vec4, Point, CoorList, RegionPoint, Rect, Edge } from './types'; /** * Create a LineSegmentDetector object. * Specifying scale, number of subdivisions for the image, * should the lines be refined and other constants as follows: * * @param refine How should the lines found be refined? * REFINE_NONE - No refinement applied. * REFINE_STD - Standard refinement is applied. E.g. breaking arches into smaller line approximations. * REFINE_ADV - Advanced refinement. Number of false alarms is calculated, * lines are refined through increase of precision, decrement in size, etc. * @param scale The scale of the image that will be used to find the lines. Range (0..1]. * @param sigmaScale Sigma for Gaussian filter is computed as sigma = _sigma_scale/_scale. * @param quant Bound to the quantization error on the gradient norm. * @param angTh Gradient angle tolerance in degrees. * @param logEps Detection threshold: -log10(NFA) > _log_eps * @param densityTh Minimal density of aligned region points in rectangle. * @param nBins Number of bins in pseudo-ordering of gradient modulus. */ export default class LSD { constructor( refineType = funcs.REFINE_NONE, scale = 0.8, sigmaScale = 0.6, quant = 2.0, angTh = 22.5, logEps = 0.0, densityTh = 0.7, nBins = 1024 ) { this.refineType = refineType; this.scale = scale; this.sigmaScale = sigmaScale; this.quant = quant; this.angTh = angTh; this.logEps = logEps; this.densityTh = densityTh; this.nBins = nBins; /** @type {ImageData} */ this.image = null; /** @private @type {Uint8ClampedArray} */ this.imageData = null; /** @type {number} */ this.width = 0; /** @type {number} */ this.height = 0; /** @type {CoorList[]} */ this.list = []; /** @type {Float64Array} */ this.angles = null; /** @type {Float64Array} */ this.modgrad = null; /** @type {Uint8Array} */ this.used = null; } /** * Detect lines in the input image. * @param {ImageData} image * @return {Vec4[]} */ detect(image) { this.image = image; this.width = image.width; this.height = image.height; const lines = this.lsd(); return lines; } /** * Draws the line segments on a given image. * @param {CanvasRenderingContext2D} context * @param {Vec4[]} lines * @param {string} color */ drawSegments(context, lines, color = '#ff0000') { context.strokeStyle = color; context.lineWidth = 1; lines.forEach(v => { context.beginPath(); context.moveTo(v.x1, v.y1); context.lineTo(v.x2, v.y2); context.stroke(); context.closePath(); }); } /** * for debug * @param {CanvasRenderingContext2D} context */ putImageData(context) { let src = this.imageData, image = context.createImageData(this.width, this.height), dst = image.data, len = image.data.length; for (let i = 0; i < len; i += 4) { dst[i] = dst[i + 1] = dst[i + 2] = src[i/4]; dst[i + 3] = 255; } context.putImageData(image, 0, 0); } /** * @return {Vec4[]} Return: A vector of Vec4f elements specifying the beginning and ending point of a line. * Where Vec4f is (x1, y1, x2, y2), point 1 is the start, point 2 - end. * Returned lines are strictly oriented depending on the gradient. */ lsd() { /** @type {Vec4[]} */ let lines = []; const prec = Math.PI * this.angTh / 180; const p = this.angTh / 180; const rho = this.quant / Math.sin(prec); if (this.scale != 1) { const sigma = this.scale < 1 ? this.sigmaScale / this.scale : this.sigmaScale; const sprec = 3; const h = Math.ceil(sigma * Math.sqrt(2 * sprec * Math.log(10.0))); const kSize = 1 + 2 * h; const reshaped = this.reshape(this.image); this.imageData = this.gaussianBlur(reshaped, kSize, sigma); this.computeLevelLineAngles(rho, this.nBins); } else { this.imageData = this.reshape(this.image); this.computeLevelLineAngles(rho, this.nBins); } const LOG_NT = 5 * (Math.log10(this.width) + Math.log10(this.height)) / 2 + Math.log10(11.0); const minRegSize = -LOG_NT / Math.log10(p); this.used = new Uint8Array(this.imageData.length); for (let i = 0, listSize = this.list.length; i < listSize; i++) { const point = this.list[i].p; if ((this.at(this.used, point) === funcs.NOT_USED) && (this.at(this.angles, point) !== funcs.NOT_DEF)) { let regAngle = 0.0; /** @type {RegionPoint[]} */ let reg = []; regAngle = this.regionGrow(this.list[i].p, reg, regAngle, prec); if (reg.length < minRegSize) { continue; } let rect = new Rect(); this.region2Rect(reg, regAngle, prec, p, rect); let logNfa = -1; if (this.refineType > funcs.REFINE_NONE) { if (!this.refine(reg, regAngle, prec, p, rect, this.densityTh)) { continue; } if (this.refineType >= funcs.REFINE_ADV) { logNfa = this.improveRect(rect); if (logNfa <= this.logEps) { continue; } } } rect.x1 += 0.5; rect.y1 += 0.5; rect.x2 += 0.5; rect.y2 += 0.5; /* if (this.scale != 1) { rect.x1 /= this.scale; rect.y1 /= this.scale; rect.x2 /= this.scale; rect.y2 /= this.scale; rect.width /= this.scale; } */ lines.push(new Vec4(rect.x1, rect.y1, rect.x2, rect.y2)); } } return lines; } /** * @param {number} threshold The minimum value of the angle that is considered defined, otherwise NOTDEF * @param {number} nBins The number of bins with which gradients are ordered by, using bucket sort. */ computeLevelLineAngles(threshold, nBins) { const imageData = this.imageData; const width = this.width; const height = this.height; this.angles = new Float64Array(imageData.length); this.modgrad = new Float64Array(imageData.length); this.angles = this.setRow(this.angles, height - 1, funcs.NOT_DEF); this.angles = this.setCol(this.angles, width - 1, funcs.NOT_DEF); let maxGrad = -1.0; for (let y = 0; y < height - 1; y++) { const step = y * width; const nextStep = (y + 1) * width; for (let x = 0; x < width - 1; x++) { const DA = imageData[x + 1 + nextStep] - imageData[x + step]; const BC = imageData[x + 1 + step] - imageData[x + nextStep]; const gx = DA + BC; const gy = DA - BC; const norm = Math.sqrt((gx * gx + gy * gy) / 4.0); this.modgrad[x + step] = norm; if (norm <= threshold) { this.angles[x + step] = funcs.NOT_DEF; } else { this.angles[x + step] = Math.atan2(gx, -gy); if (norm > maxGrad) { maxGrad = norm; } } } } /** @type {CoorList[]} */ let rangeS = []; rangeS.length = nBins; /** @type {CoorList[]} */ let rangeE = []; rangeE.length = nBins; let count = 0; const binCoef = (maxGrad > 0) ? (nBins - 1) / maxGrad : 0; for (let y = 0; y < height - 1; y++) { let step = y * width; for (let x = 0; x < width - 1; x++) { let i = Math.floor(this.modgrad[x + step] * binCoef); if (!rangeE[i]) { this.list[count] = new CoorList(); rangeE[i] = rangeS[i] = this.list[count]; count++; } else { this.list[count] = new CoorList(); rangeE[i] = this.list[count]; rangeE[i].next = this.list[count]; count++; } rangeE[i].p = new Point(x, y); rangeE[i].next = null; } } let idx = nBins - 1; for (; idx > 0 && !rangeS[idx]; idx--) { // do nothing. } let start = rangeS[idx]; let endIdx = idx; if (start) { while (idx > 0) { idx--; if (rangeS[idx]) { rangeE[endIdx].next = rangeS[idx]; rangeE[endIdx] = rangeE[idx]; endIdx = idx; } } } } /** * @param {Point} s * @param {RegionPoint[]} reg * @param {number} regAngle * @param {number} prec */ regionGrow(s, reg, regAngle, prec) { let seed = new RegionPoint(); seed.x = s.x; seed.y = s.y; seed.used = this.at(this.used, s); regAngle = this.at(this.angles, s); seed.angle = regAngle; seed.modgrad = this.at(this.modgrad, s); seed.used = funcs.USED; reg.push(seed); let sumdx = Math.cos(regAngle); let sumdy = Math.sin(regAngle); for (let i = 0; i < reg.length; i++) { const rpoint = reg[i], xxMin = Math.max(rpoint.x - 1, 0), xxMax = Math.min(rpoint.x + 1, this.width - 1), yyMin = Math.max(rpoint.y - 1, 0), yyMax = Math.min(rpoint.y + 1, this.height - 1); for (let yy = yyMin; yy <= yyMax; yy++) { const step = yy * this.width; for (let xx = xxMin; xx <= xxMax; xx++) { let isUsed = this.used[xx + step]; if (isUsed != funcs.USED && this.isAligned(xx, yy, regAngle, prec)) { const angle = this.angles[xx + step]; isUsed = funcs.USED; this.used[xx + step] = funcs.USED; let regionPoint = new RegionPoint( xx, yy, angle, this.modgrad[xx + step], isUsed ); reg.push(regionPoint); sumdx += Math.cos(angle); sumdy += Math.sin(angle); regAngle = Math.atan2(sumdy, sumdx); } } } } return regAngle; } /** * @param {number} x * @param {number} y * @param {number} theta * @param {number} prec * @return {boolean} */ isAligned(x, y, theta, prec) { if (x < 0 || y < 0 || x >= this.width || y >= this.height) { return false; } const a = this.angles[x + y * this.width]; if (a === funcs.NOT_DEF) { return false; } let nTheta = theta - a; if (nTheta < 0) { nTheta = -nTheta; } if (nTheta > funcs.M_3_2_PI) { nTheta -= funcs.M_2__PI; if (nTheta < 0) { nTheta = -nTheta; } } return nTheta <= prec; } /** * @param {RegionPoint[]} reg * @param {number} regAngle * @param {number} prec * @param {number} p * @param {Rect} rect */ region2Rect(reg, regAngle, prec, p, rect) { let x = 0, y = 0, sum = 0; for (let i = 0; i < reg.length; i++) { const pnt = reg[i]; const weight = pnt.modgrad; x += pnt.x * weight; y += pnt.y * weight; sum += weight; } if (sum <= 0) { throw new Error('weighted sum must differ from 0'); } x /= sum; y /= sum; const theta = this.getTheta(reg, x, y, regAngle, prec); const dx = Math.cos(theta); const dy = Math.sin(theta); let lMin = 0, lMax = 0, wMin = 0, wMax = 0; for (let i = 0; i < reg.length; i++) { let regdx = reg[i].x - x; let regdy = reg[i].y - y; let l = regdx * dx + regdy * dy; let w = -regdx * dy + regdy * dx; if (l > lMax) { lMax = l; } else if (l < lMin) { lMin = l; } if (w > wMax) { wMax = w; } else if (w < wMin) { wMin = w; } } rect.x1 = x + lMin * dx; rect.y1 = y + lMin * dy; rect.x2 = x + lMax * dx; rect.y2 = y + lMax * dy; rect.width = wMax - wMin; rect.x = x; rect.y = y; rect.theta = theta; rect.dx = dx; rect.dy = dy; rect.prec = prec; rect.p = p; if (rect.width < 1.0) { rect.width = 1.0; } } /** * @param {RegionPoint[]} reg * @param {number} x * @param {number} y * @param {number} regAngle * @param {number} prec * @return {number} */ getTheta(reg, x, y, regAngle, prec) { let ixx = 0.0, iyy = 0.0, ixy = 0.0; for (let i = 0; i < reg.length; i++) { const regx = reg[i].x; const regy = reg[i].y; const weight = reg[i].modgrad; let dx = regx - x; let dy = regy - y; ixx += dy * dy * weight; iyy += dx * dx * weight; ixy -= dx * dy * weight; } let check = (funcs.doubleEqual(ixx, 0) && funcs.doubleEqual(iyy, 0) && funcs.doubleEqual(ixy, 0)); if (check) { throw new Error('check if inertia matrix is null'); } let lambda = 0.5 * (ixx + iyy - Math.sqrt((ixx - iyy) * (ixx - iyy) + 4.0 * ixy * ixy)); let theta = (Math.abs(ixx) > Math.abs(iyy)) ? Math.atan2(lambda - ixx, ixy) : Math.atan2(ixy, lambda - iyy); if (funcs.angleDiff(theta, regAngle) > prec) { theta += Math.PI; } return theta; } /** * @param {RegionPoint[]} reg * @param {number} regAngle * @param {number} prec * @param {number} p * @param {Rect} rect * @param {number} densityTh * @return {boolean} */ refine(reg, regAngle, prec, p, rect, densityTh) { let density = reg.length / (funcs.dist(rect.x1, rect.y1, rect.x2, rect.y2) * rect.width); if (density >= densityTh) { return true; } let xc = reg[0].x; let yc = reg[0].y; const angC = reg[0].angle; let sum = 0, sSum = 0, n = 0; for (let i = 0; i < reg.length; i++) { reg[i].used = funcs.NOT_USED; if (funcs.dist(xc, yc, reg[i].x, reg[i].y) < reg.width) { const angle = reg[i].angle; let angD = funcs.angleDiff(angle, angC); sum += angD; sSum += angD * angD; n++; } let meanAngle = sum / n; let tau = 2.0 * Math.sqrt((sSum - 2.0 * meanAngle * sum) / n + meanAngle * meanAngle); this.regionGrow(new Point(reg[0].x, reg[0].y), reg, regAngle, tau); if (reg.length < 2) { return false; } this.region2Rect(reg, regAngle, prec, p, rect); density = reg.length / (funcs.dist(rect.x1, rect.y1, rect.x2, rect.y2) * rect.width); if (density < densityTh) { return this.reduceRegionRadius(reg, regAngle, prec, p, rect, density, densityTh); } else { return true; } } } /** * @param {RegionPoint[]} reg * @param {number} regAngle * @param {number} prec * @param {number} p * @param {Rect} rect * @param {number} density * @param {number} densityTh * @return {boolean} */ reduceRegionRadius(reg, regAngle, prec, p, rect, density, densityTh) { let xc = reg[0].x; let yc = reg[0].y; let radSq1 = funcs.distSq(xc, yc, rect.x1, rect.y1); let radSq2 = funcs.distSq(xc, yc, rect.x2, rect.y2); let radSq = radSq1 > radSq2 ? radSq1 : radSq2; while (density < densityTh) { radSq *= 0.75 * 0.75; // reduce region's radius to 75% for (let i = 0; i < reg.length; i++) { if (funcs.distSq(xc, yc, reg[i].x, reg[i].y) > radSq) { // remove point from the region reg[i].used = funcs.NOT_USED; const tmp = reg[i]; reg[i] = reg[reg.length - 1]; reg[reg.length - 1] = tmp; reg.pop(); --i; } } if (reg.length < 2) { return false; } this.region2Rect(reg, regAngle, prec, p, rect); density = reg.length / (funcs.dist(rect.x1, rect.y1, rect.x2, rect.y2) * rect.width); } return true; } /** * @param {Rect} rect * @return {number} */ improveRect(rect) { let delta = 0.5; let delta2 = delta / 2.0; let logNfa = this.rectNfa(rect); if (logNfa > this.logEps) { return logNfa; } let r = new Rect(); r.copy(rect); for (let n = 0; n < 5; n++) { r.p /= 2; r.prec = r.p * Math.PI; let logNfaNew = this.rectNfa(rect); if (logNfaNew > logNfa) { logNfa = logNfaNew; rect.copy(r); } } if (logNfa > this.logEps) { return logNfa; } r.copy(rect); for (let n = 0; n < 5; n++) { if ((r.width - delta) >= 0.5) { r.width -= delta; let logNfaNew = this.rectNfa(r); if (logNfaNew > logNfa) { rect.copy(r); logNfa = logNfaNew; } } } if (logNfa > this.logEps) { return logNfa; } r.copy(rect); for (let n = 0; n < 5; n++) { if ((r.width - delta) >= 0.5) { r.x1 -= -r.dy * delta2; r.y1 -= r.dx * delta2; r.x2 -= -r.dy * delta2; r.y2 -= r.dx * delta2; r.width -= delta; let logNfaNew = this.rectNfa(r); if (logNfaNew > logNfa) { rect.copy(r); logNfa = logNfaNew; } } } if (logNfa > this.logEps) { return logNfa; } r.copy(rect); for (let n = 0; n < 5; n++) { if ((r.width - delta) >= 0.5) { r.p /= 2; r.prec = r.p * Math.PI; let logNfaNew = this.rectNfa(r); if (logNfaNew > logNfa) { rect.copy(r); logNfa = logNfaNew; } } } return logNfa; } /** * @param {Rect} rect * @return {number} */ rectNfa(rect) { let totalPts = 0, algPts = 0, halfWidth = rect.width / 2.0, dyhw = rect.dy * halfWidth, dxhw = rect.dx * halfWidth, orderedX = [ new Edge(), new Edge(), new Edge(), new Edge() ], minY = orderedX[0], maxY = orderedX[0]; orderedX[0].p.x = rect.x1 - dyhw; orderedX[0].p.y = rect.y1 + dxhw; orderedX[1].p.x = rect.x2 - dyhw; orderedX[1].p.y = rect.y2 + dxhw; orderedX[2].p.x = rect.x2 + dyhw; orderedX[2].p.y = rect.y2 - dxhw; orderedX[3].p.x = rect.x1 + dyhw; orderedX[3].p.y = rect.y1 - dxhw; orderedX.sort(funcs.AsmallerB_XoverY); for (let i = 1; i < 4; i++) { if (minY.p.y > orderedX[i].p.y) { minY = orderedX[i]; } if (maxY.p.y < orderedX[i].p.y) { maxY = orderedX[i]; } } minY.taken = true; let leftmost = null; for (let i = 0; i < 4; i++) { if (!orderedX[i].taken) { if (!leftmost) { leftmost = orderedX[i]; } else if (leftmost.p.x > orderedX[i].p.x) { leftmost = orderedX[i]; } } } leftmost.taken = true; let rightmost = null; for (let i = 0; i < 4; i++) { if (!orderedX[i].taken) { if (!rightmost) { rightmost = orderedX[i]; } else if (rightmost.p.x < orderedX[i].p.x) { rightmost = orderedX[i]; } } } rightmost.taken = true; let tailp = null; for (let i = 0; i < 4; i++) { if (!orderedX[i].taken) { if (!tailp) { tailp = orderedX[i]; } else if (tailp.p.x > orderedX[i].p.x) { tailp = orderedX[i]; } } } tailp.taken = true; let flstep = (minY.p.y != leftmost.p.y) ? (minY.p.x + leftmost.p.x) / (minY.p.y - leftmost.p.y) : 0; let slstep = (leftmost.p.y != tailp.p.x) ? (leftmost.p.x = tailp.p.x) / (leftmost.p.y - tailp.p.x) : 0; let frstep = (minY.p.y != rightmost.p.y) ? (minY.p.x - rightmost.p.x) / (minY.p.y - rightmost.p.y) : 0; let srstep = (rightmost.p.y != tailp.p.x) ? (rightmost.p.x - tailp.p.x) / (rightmost.p.y - tailp.p.x) : 0; let lstep = flstep, rstep = frstep; let leftX = minY.p.x, rightX = minY.p.x; let minIter = minY.p.y; let maxIter = maxY.p.y; for (let y = minIter; y <= maxIter; y++) { if (y < 0 || y >= this.height) { continue; } for (let x = leftX; x <= rightX; x++) { if (x < 0 || x >= this.width) { continue; } totalPts++; if (this.isAligned(x, y, rect.theta, rect.prec)) { algPts++; } } if (y >= leftmost.p.y) { lstep = slstep; } if (y >= rightmost.p.y) { rstep = srstep; } leftX += lstep; rightX += rstep; } return this.nfa(totalPts, algPts, rect.p); } /** * @param {number} n * @param {number} k * @param {number} p */ nfa(n, k, p) { const LOG_NT = 5 * (Math.log10(this.width) + Math.log10(this.height)) / 2 + Math.log10(11.0); if (n == 0 || k == 0) { return -LOG_NT; } if (n == k) { return -LOG_NT - n * Math.log10(p); } let pTerm = p / (1 - p); let log1Term = (n + 1) - funcs.logGamma(k + 1) - funcs.logGamma(n - k + 1) + k * Math.log(p) + (n - k) * Math.log(1.0 - p); let term = Math.exp(log1Term); if (funcs.doubleEqual(term, 0)) { if (k > n * p) { return -log1Term / funcs.M_LN10 - LOG_NT; } else { return -LOG_NT; } } let binTail = term; let tolerance = 0.1; for (let i = k + 1; i <= n; i++) { let binTerm = (n - i + 1) / i; let multTerm = binTerm * pTerm; term *= multTerm; binTail += term; if (binTerm < 1) { let err = term * ((1 - Math.pow(multTerm, (n - i + 1))) / (1 - multTerm) - 1); if (err < tolerance * Math.abs(-Math.log10(binTail) - LOG_NT) * binTail) { break; } } } return -Math.log10(binTail) - LOG_NT; } /** * @param {Uint8ClampedArray} imageData * @param {number} kSize * @param {number} sigma * @return {Uint8ClampedArray} */ gaussianBlur(imageData, kSize, sigma) { let width = this.width, height = this.height, src = imageData, ctx = document.createElement('canvas').getContext('2d'), tmp = ctx.createImageData(width, height), dst = null, kernel = this.getGaussianKernel(kSize, sigma), r = (kSize - 1) / 2; tmp = this.reshape(tmp); dst = new Uint8ClampedArray(tmp.length); // separate 2d-filter for (let y = 0; y < height; y++) { let step = y * width; for (let x = 0; x < width; x++) { let buff = 0; let i = x + step; let k = 0; for (let kx = -r; kx <= r; kx++) { let px = x + kx; if (px <= 0 || width <= px) { px = x; } let j = px + step; buff += src[j] * kernel[k]; k++; } tmp[i] = buff; } } for (let x = 0; x < width; x++) { for (let y = 0; y < height; y++) { let step = y * width; let buff = 0; let i = x + step; let k = 0; for (let ky = -r; ky <= r; ky++) { let py = y + ky; let kStep = ky * width; if (py <= 0 || height <= py) { py = y; kStep = 0; } let j = i + kStep; buff += tmp[j] * kernel[k]; k++; } dst[i] = buff; } } return dst; } /** * @param {number} kSize * @param {number} sigma * @return {number[]} */ getGaussianKernel(kSize, sigma) { // 1d-kernel let kernel = []; let sigmaX = sigma > 0 ? sigma : ((kSize - 1) * 0.5 - 1) * 0.3 + 0.8; let scale2X = -0.5 / (sigmaX * sigmaX); let sum = 0.0; for (let i = 0; i < kSize; i++) { let x = i - (kSize - 1) * 0.5; kernel[i] = Math.exp(scale2X * x * x); sum += kernel[i]; } sum = 1. / sum; for (let i = 0; i < kSize; i++) { kernel[i] *= sum; } return kernel; } /** * @param {ImageData} image * @return {Uint8ClampedArray} */ reshape(image) { let src = image.data; let reshaped = new Uint8ClampedArray(src.length / 4); let len = reshaped.length; for (let i = 0; i < len; i++) { reshaped[i] = src[i * 4]; } return reshaped; } /** * @param {Uint8Array|Float64Array} data * @param {Point} p */ at(data, p) { return data[p.x + (p.y * this.width)]; } /** * @param {Float64Array} data * @param {number} rowIndex */ row(data, rowIndex) { let i = rowIndex * this.width; return data.subarray(i, i + this.width); } /** * @param {Float64Array} data * @param {number} index * @param {number} value * @return {null} */ setRow(data, index, value) { let from = index * this.width; let to = from + this.width; for (let i = from; i < to; i++) { data[i] = value; } return data; } /** * @param {Float64Array} data * @param {number} index * @param {number} value * @return {null} */ setCol(data, index, value) { let to = this.height * this.width; let step = this.width; for (let i = index; i < to; i += step) { data[i] = value; } return data; } } ================================================ FILE: cv/lsd/src/lsd_component.js ================================================ import React from 'react'; import RaisedButton from 'material-ui/RaisedButton'; import LSD from './lsd'; export default class LSDComponent extends React.Component { constructor(props) { super(props); this.onLoad = this.onLoad.bind(this); } componentDidMount() { this.image = new Image(); this.image.onload = this.onLoad; this.image.src = this.props.src; this.canvas = this.refs.canvas; this.width = 0; this.height = 0; } componentDidUpdate() { this.image.src = this.props.src; } apply(e) { e.preventDefault(); const ctx = this.canvas.getContext('2d'), imageData = ctx.getImageData(0, 0, this.width, this.height), scale = 0.8, detector = new LSD(0, scale), lines = detector.detect(imageData); console.log('lines: ' + lines.length.toString()); detector.drawSegments(ctx, lines); } reset(e) { e.preventDefault(); const ctx = this.canvas.getContext('2d'); ctx.drawImage(this.image, 0, 0, this.width, this.height); } onLoad() { const ctx = this.canvas.getContext('2d'); this.width = this.image.width; this.height = this.image.height; this.canvas.width = this.width; this.canvas.height = this.height; ctx.drawImage(this.image, 0, 0, this.width, this.height); } render() { return (
this.apply(e)} /> this.reset(e)} />
) } } ================================================ FILE: cv/lsd/src/types.js ================================================ class Vec4 { constructor(x1 = 0, y1 = 0, x2 = 0, y2 = 0) { this.x1 = x1; this.y1 = y1; this.x2 = x2; this.y2 = y2; } } class Point { constructor(x = 0.0, y = 0.0) { this.x = x; this.y = y; } } class CoorList { constructor() { this.p = new Point(); /** @type {CoorList} */ this.next = null; } } class RegionPoint { constructor(x = 0, y = 0, angle = 0.0, modgrad = 0.0, used = null) { this.x = x; this.y = y; this.angle = angle; this.modgrad = modgrad; /** @type {number} */ this.used = used; } } class Rect { constructor( x1 = 0, y1 = 0, x2 = 0, y2 = 0, width = 0, height = 0, x = 0, y = 0, theta = 0, dx = 0, dy = 0, prec = 0, p = 0 ) { this.x1 = x1; this.y1 = y1; this.x2 = x2; this.y2 = y2; this.width = width; this.height = height; this.x = x; this.y = y; this.theta = theta; this.dx = dx; this.dy = dy; this.prec = prec; this.p = p; } /** * @param {Rect} rect */ copy(rect) { this.x1 = rect.x1; this.y1 = rect.y1; this.x2 = rect.x2; this.y2 = rect.y2; this.width = rect.width; this.height = rect.height; this.x = rect.x; this.y = rect.y; this.theta = rect.theta; this.dx = rect.dx; this.dy = rect.dy; this.prec = rect.prec; this.p = rect.p; } } class Edge { constructor() { this.p = new Point(); this.taken = null; } } export { Vec4, Point, CoorList, RegionPoint, Rect, Edge }; ================================================ FILE: cv/lsd/webpack.config.js ================================================ const HtmlWebpackPlugin = require('html-webpack-plugin'); const webpack = require('webpack'); module.exports = { entry: __dirname + '/src/app.js', output: { path: __dirname + '/dst', filename: 'bundle.js', }, module: { loaders: [ { test: /\.js[x]?$/, exclude: /(node_modules|bower_components)/, loader: 'babel-loader', query: { presets: ['es2015', 'react'],//, 'react-hmre'] } }, { test: /\.html$/, loader: 'html-loader' }, { test: /\.(jpg|png)$/, loader: 'url-loader' } ] }, resolve: { extensions: ['.js', '.jsx'] }, devServer: { contentBase: '/dst', port: 8888 }, devtool: 'source-map', plugins: [ new HtmlWebpackPlugin({ title: "React Sample", filename: 'index.html', template: __dirname + '/src/index.html' }) ] }; ================================================ FILE: cv/ort_web/HelloONNX.vue ================================================ ================================================ FILE: cv/ort_web/README.md ================================================ ## Image Classification using ONNX Runtime for Web (ORT Web) this example is the [CIFAR-10](https://www.cs.toronto.edu/~kriz/cifar.html) classification build by [Vue.js](https://vuejs.org/index.html) component see also: [ONNX Runtime Web](https://github.com/microsoft/onnxruntime/tree/master/js/web#readme) ================================================ FILE: cv/pixel_clustering/README.md ================================================ # Pixel Clustering Module ## description Pixel clustering using k-means++ algorithm see also [blog entry][entry] ### sample [![pixel_clustering](https://raw.github.com/wiki/wellflat/imageprocessing-labs/images/pixel_clustering.jpg)](http://rest-term.com/labs/html5/pixelclustering.html) (Tests: IE10, Firefox23.0, Chrome28.0, Safari6.0) ## usage ```js var ctx = document.querySelector('#Canvas').getContext('2d'); // source image, callback function PixelCluster.load(srcImgName, function() { // performs k-means on a set of observation vectors forming k clusters var division = 100; var k = 4; var method = PixelCluster.KMEANS_PP; // KMEANS_PP or KMEANS_RANDOM PixelCluster.perform(division, k, method, function(result) { // render the result to canvas element PixelCluster.render(ctx, division, result); }); }); ``` license ---------- Copyright © 2014 wellflat Licensed under the [MIT License][MIT] [MIT]: http://www.opensource.org/licenses/mit-license.php [entry]: http://rest-term.com/archives/3073/ ================================================ FILE: cv/pixel_clustering/kmeans.js ================================================ /** * provides routines for k-means clustering, generating code books, * and quantizing vectors by comparing them with centroids. * (specified CanvasPixelArray [[R,G,B],[R,G,B],...]) */ /* performs k-means on a set of observation vectors forming k clusters */ function kmeans(samples, ncluster, method) { var centroids = [], previous = [], clusters = [], code = [], len = samples.length, eps = 1.0e-8, maxIter = 1000, iter = 0, distance = function(a, b) { var dr = a[0] - b[0], dg = a[1] - b[1], db = a[2] - b[2]; return dr*dr + dg*dg + db*db; }, nearest = function(sample, centroids) { var minIndex = 0, minDistance = Number.MAX_VALUE, clusterCount = centroids.length; for(var k=0; k d) { minDistance = d; minIndex = k; } } return [minIndex, minDistance]; }, initialize = function(samples, ncluster, method, centroids, clusters) { var len = samples.length; if(method === 'kmeans_pp') { // kmeans++ var d = [], sumDistance = 0.0, label, k, i, r = Math.floor(Math.random()*len); centroids[0] = []; centroids[0][0] = samples[r][0]; centroids[0][1] = samples[r][1]; centroids[0][2] = samples[r][2]; previous[0] = [0.0, 0.0, 0.0]; clusters[0] = []; for(k=1; k 0) continue; centroids[k] = []; centroids[k][0] = samples[i][0]; centroids[k][1] = samples[i][1]; centroids[k][2] = samples[i][2]; break; } previous[k] = [0.0, 0.0, 0.0]; clusters[k] = []; } for(i=0; i d) { minDistance = d; minIndex = k; } } return [minIndex, minDistance]; }, initialize = function(samples, ncluster, method, centroids, clusters) { var len = samples.length; if(method === 'kmeans_pp') { // kmeans++ var d = [], sumDistance = 0.0, label, k, i, r = Math.floor(Math.random()*len); centroids[0] = []; centroids[0][0] = samples[r][0]; centroids[0][1] = samples[r][1]; centroids[0][2] = samples[r][2]; previous[0] = [0.0, 0.0, 0.0]; clusters[0] = []; for(k=1; k 0) continue; centroids[k] = []; centroids[k][0] = samples[i][0]; centroids[k][1] = samples[i][1]; centroids[k][2] = samples[i][2]; break; } previous[k] = [0.0, 0.0, 0.0]; clusters[k] = []; } for(i=0; i threashold) { // on the mask sumf = [0.0, 0.0, 0.0]; sumfstar = [0.0, 0.0, 0.0]; sumvq = [0.0, 0.0, 0.0]; edge = false; for(var n=0; n<4; n++) { if(maskData[naddr[n]] <= threashold) { edge = true; break; } } if(!edge) { if(y + offsetY >= 0 && x + offsetX >= 0 && y + offsetY < h && x + offsetX < w) { for(n=0; n<4; n++) { for(var c=0; c<3; c++) { sumf[c] += blendData[naddr[n] + m + c]; sumvq[c] += srcData[l + c] - srcData[naddr[n] + c]; } } } } else { if(y + offsetY >= 0 && x + offsetX >= 0 && y + offsetY < h && x + offsetX < w) { fp[0] = dstData[l + m]; fp[1] = dstData[l + m + 1]; fp[2] = dstData[l + m + 2]; gp[0] = srcData[l]; gp[1] = srcData[l + 1]; gp[2] = srcData[l + 2]; for(n=0; n<4; n++) { for(c=0; c<3; c++) { fq[c] = dstData[naddr[n] + m + c]; gq[c] = srcData[naddr[n] + c]; sumfstar[c] += fq[c]; subf[c] = fp[c] - fq[c]; subf[c] = subf[c] > 0 ? subf[c] : -subf[c]; subg[c] = gp[c] - gq[c]; subg[c] = subg[c] > 0 ? subg[c] : -subg[c]; if(subf[c] > subg[c]) { sumvq[c] += subf[c]; } else { sumvq[c] += subg[c]; } } } } } for(c=0; c<3; c++) { fp[c] = (sumf[c] + sumfstar[c] + sumvq[c])*0.25; // division 4 error = Math.floor(fp[c] - blendData[l + m + c]); error = error > 0 ? error : -error; if(terminate[c] && error > EPS*(1 + (fp[c] > 0 ? fp[c] : -fp[c]))) { terminate[c] = false; } blendData[l + m + c] = fp[c]; } } // end mask } // end x loop } // end y loop if(terminate[0] && terminate[1] && terminate[2]) break; } // end iteration return data[3]; } }; // aliases (public APIs) Poisson.load = core.load; Poisson.reset = core.reset; Poisson.blend = core.blend; }).call(this); ================================================ FILE: cv/stereo_matching/README.md ================================================ # Stereo Matching Module ## description computes the disparity map using naive block matching see also [blog entry][entry] ### sample [![stereo_matching](https://raw.github.com/wiki/wellflat/imageprocessing-labs/images/stereo_matching.jpg)](http://rest-term.com/labs/html5/stereo.html) (Tests: IE10, Firefox7.0, Chrome14.0, Opera11.51) license ---------- Copyright © 2014 wellflat Licensed under the [MIT License][MIT] [MIT]: http://www.opensource.org/licenses/mit-license.php [entry]: http://rest-term.com/archives/2940/ ================================================ FILE: cv/stereo_matching/stereo-core.js ================================================ /** * computes the disparity for the rectified stereo pair, * naive implementation without using dynamic programming. */ function findStereoCorrespondence(pair, state) { var w = pair[0].width, h = pair[0].height, data = [pair[0].data, pair[1].data], disparity = [], step = w << 2, winSize = state.SADWindowSize, threshold = state.textureThreshold, minDisparity = state.minDisparity, maxDisparity = state.numberOfDisparities - minDisparity, maxDiff = 255 * winSize * winSize, localMin = maxDiff, sad = 0, diff, cur, ptr; for(var y=0, cur=step; y threshold || y + ly > h) break; for(var lx=0, j=ly*step; lx w) break; diff = data[0][i + j] - data[1][ptr + j]; if(isNaN(diff)) diff = 0; sad += (diff < 0 ? -diff : diff); } } if(localMin > sad) { localMin = sad; disparity[i] = disparity[i + 1] = disparity[i + 2] = d << 4; disparity[i + 3] = 255; } sad = 0; } localMin = maxDiff; } } return {'width':w, 'height':h, 'disparity':disparity}; } /* message handler */ self.addEventListener('message', function(e) { var message = e.data, data = message.pair, // CanvasPixelArray pair state = message.state; postMessage(findStereoCorrespondence(data, state)); }, false); self.addEventListener('error', function(e) { postMessage(e); }, false); ================================================ FILE: cv/stereo_matching/stereo.js ================================================ /** * Stereo Matching module using Web Workers */ (function() { var Stereo; // top-level namespace var _root = this; // reference to 'window' or 'global' if(typeof exports !== 'undefined') { Stereo = exports; // for CommonJS } else { Stereo = _root.Stereo = {}; } var version = { release: '0.1.0', date: '2011-01' }; Stereo.toString = function() { return "version " + version.release + ", released " + version.date; }; // core operations var ctx = null, left = new Image(), right = new Image(), data = [null, null], state = {}, worker = new Worker('lib/js/stereo-core.js'); var core = { /* load stereo pair images */ _load : function(leftSource, rightSource) { ctx = document.createElement('canvas').getContext('2d'); left.src = leftSource; right.src = rightSource; // load complete handler left.addEventListener('load', function() { ctx.canvas.width = left.width; ctx.canvas.height = left.height; ctx.drawImage(left, 0, 0); data[0] = ctx.getImageData(0, 0, left.width, left.height); }, false); right.addEventListener('load', function() { ctx.canvas.width = right.width; ctx.canvas.height = right.height; ctx.drawImage(right, 0, 0); data[1] = ctx.getImageData(0, 0, right.width, right.height); }, false); // load error handler left.addEventListener('error', function() { alert('can\'t load left image'); }, false); right.addEventListener('error', function() { alert('can\'t load right image'); }, false); }, /* draw disparity map on the canvas */ _draw : function(data, context) { var img = context.getImageData(0, 0, data.width, data.height), pixels = img.data, disparity = data.disparity, len = disparity.length; for(var i=0; i max ? this.bins[ch][i] : max; } } return max; }, normalize : function(factor) { for(var ch in this.bins) { for(var i=0; i<256; i++) { this.bins[ch][i] /= factor; } } }, backProject : function() { // TODO }, compare : function(hist, method) { // TODO switch(method) { case 'correl': break; case 'chisqr': break; case 'intersect': break; case 'bhattacharyya': break; default: break; } }, convertColor : function(type) { // TODO switch(type) { case 'rgb': break; case 'gray': break; case 'hsv': break; default: break; } } }); self.addEventListener('message', function(e) { var hist = new Histogram(e.data); hist.calculate(); hist.bins.max = hist.getMax(); postMessage(hist.bins); }, false); self.addEventListener('error', function(e) { postMessage(e); }, false); ================================================ FILE: draw/README.md ================================================ # 3D Shapes Drawing ## description Drawing 3D shapes (pseudo 3D) [![3d_drawing](https://raw.github.com/wiki/wellflat/imageprocessing-labs/images/3dshape_drawing.png)](http://rest-term.com/labs/html5/) ### demo [Heart Surface][Heart] [Mobius Strip][Mobius] [Klein Bottle][Klein] (Tests: IE10, Firefox8.0, Safari5.0, Chrome16.0, Opera11.60) license ---------- Copyright © 2014 wellflat Licensed under the [MIT License][MIT] [Heart]: http://rest-term.com/labs/html5/heart.html [Mobius]: http://rest-term.com/labs/html5/mobius.html [Klein]: http://rest-term.com/labs/html5/klein.html [MIT]: http://www.opensource.org/licenses/mit-license.php [entry]: http://rest-term.com/archives/2986/ ================================================ FILE: draw/heart.js ================================================ /** * Heart Surface Maker */ var Heart = (function() { var _context = null, _width = null, _height = null, _scale = 25, _fov = 250, _points = [], _numPoints = 0, _isPlay = false, _timerID = null, // public methods _init = function(context, n) { _context = context; _width = context.canvas.width; _height = context.canvas.height; _points = []; _setStyle(_context.createLinearGradient(0, 0, 0, 300)); _setPoints(n); }, _play = function(t, angle) { if(!_isPlay) { _timerID = setInterval(function() { _render(angle) }, t); _isPlay = true; } }, _stop = function() { if(_isPlay) { clearInterval(_timerID); _isPlay = false; } }, // private methods _setStyle = function(grad) { grad.addColorStop(0, 'rgb(255, 255, 255)'); grad.addColorStop(0.7, 'rgb(196, 196, 196)'); grad.addColorStop(0.8, 'rgb(160, 160, 160)'); grad.addColorStop(0.9, 'rgb(128, 128, 128)'); grad.addColorStop(1, 'rgb(96, 96, 96)'); _context.fillStyle = grad; _context.strokeStyle = 'rgb(255, 0, 160)'; _context.fillRect(0, 0, _width, _height); }, _setPoints = function(num) { var r, theta, z, n = 0, point; for(var i=-Math.PI*num; i<=Math.PI*num; i++) { for(var j=-num; j<=num; j++) { theta = i/num; z = j/num; r = 4*Math.sqrt(1 - z*z)*Math.pow(Math.sin(Math.abs(theta)), Math.abs(theta)); point = [_scale*r*Math.sin(theta), _scale*r*Math.cos(theta), _scale*z]; _points.push(point); n++; } } _numPoints = n; }, _render = function(angle) { _context.fillRect(0, 0, _width, _height); for(var i=0; i<_numPoints; i++) { _rotateY(_points[i], angle); _draw(_points[i]); } }, _draw = function(point3d) { var scale = _fov/(_fov + point3d[2]), x = (point3d[0]*scale) + (_width >> 1), y = (point3d[1]*scale) + (_height >> 1); _context.lineWidth= scale << 1; _context.beginPath(); _context.moveTo(x, y); _context.lineTo(x + scale, y); _context.stroke(); }, _rotateY = function(point3d, angle) { var x = point3d[0], z = point3d[2], c = Math.cos(angle), s = Math.sin(angle), tz = z, tx = x; x = tx*c + tz*s; z = tz*c - tx*s; point3d[0] = x; point3d[2] = z; }; // public APIs return { init: _init, play: _play, stop: _stop, }; })(); ================================================ FILE: draw/klein.js ================================================ /** * Klein's Bottle Maker */ var KleinBottle = (function() { var _context = null, _width = null, _height = null, _scale = 6, _fov = 250, _points = [], _numPoints = 0, _isPlay = false, _timerID = null, // public methods _init = function(context, n) { _context = context; _width = context.canvas.width; _height = context.canvas.height; _points = []; _setStyle(context.createLinearGradient(0, 0, 0, 300)); _setPoints(n); }, _play = function(t, angle) { if(!_isPlay) { _timerID = setInterval(function() { _render(angle) }, t); _isPlay = true; } }, _stop = function() { if(_isPlay) { clearInterval(_timerID); _isPlay = false; } }, // private methods _setStyle = function(grad) { grad.addColorStop(0, 'rgb(0, 0, 0)'); grad.addColorStop(0.7, 'rgb(16, 16, 16)'); grad.addColorStop(0.8, 'rgb(32, 32, 32)'); grad.addColorStop(0.9, 'rgb(48, 48, 48)'); grad.addColorStop(1, 'rgb(64, 64, 64)'); _context.fillStyle = grad; _context.strokeStyle = 'rgb(0, 128, 255)'; _context.fillRect(0, 0, _width, _height); }, _setPoints = function(num) { var x, y, u, v, r, n = 0, point; for(var i=0; i<=2*Math.PI*num; i++) { for(var j=0; j<=2*Math.PI*4.8; j++) { u = i/num; v = j/4.8; r = 4*(1 - Math.cos(u)/2); if(u> 1), y = (point3d[1]*scale) + (_height >> 1); _context.lineWidth= scale << 1; _context.beginPath(); _context.moveTo(x, y); _context.lineTo(x + scale, y); _context.stroke(); }, _rotateX = function(point3d, angle) { var y = point3d[1], z = point3d[2], c = Math.cos(angle), s = Math.sin(angle), ty = y, tz = z; y = ty*c - tz*s; z = ty*s + tz*c; point3d[1] = y; point3d[2] = z; }, _rotateY = function(point3d, angle) { var x = point3d[0], z = point3d[2], c = Math.cos(angle), s = Math.sin(angle), tz = z, tx = x; x = tx*c + tz*s; z = tz*c - tx*s; point3d[0] = x; point3d[2] = z; }, _rotateZ = function(point3d, angle) { var x = point3d[0], y = point3d[1], c = Math.cos(angle), s = Math.sin(angle), tx = x, ty = y; x = tx*c - ty*s; y = tx*s + ty*c; point3d[0] = x; point3d[1] = y; }; // public APIs return { init: _init, play: _play, stop: _stop, }; })(); ================================================ FILE: draw/lib3d.js ================================================ /** * My 3D Library v.0.0.2 * */ var Class = { create : function() { var properties = arguments[0]; function self() { this.initialize.apply(this, arguments); } for(var i in properties) { self.prototype[i] = properties[i]; } if(!self.prototype.initialize) { self.prototype.initialize = function() {}; } return self; } }; var Point = Class.create({ initialize : function(x, y) { this.x = x || 0.0; this.y = y || 0.0; } }); var Vertex3D = Class.create({ initialize : function(x, y, z) { this.x = x || 0.0; this.y = y || 0.0; this.z = z || 0.0; this.length = Math.sqrt(this.x*this.x + this.y*this.y + this.z*this.z); }, clone : function() { return new Vertex3D(this.x, this.y, this.z); }, add : function(v) { return new Vertex3D(this.x + v.x, this.y + v.y, this.z + v.z); }, sub : function(v) { return new Vertex3D(this.x - v.x, this.y - v.y, this.z - v.z); }, dot : function(v) { return this.x*v.x + this.y*v.y + this.z*v.z; }, cross : function(v) { return new Vertex3D(v.y*this.z - v.z * this.y, v.z*this.x - v.x*this.z, v.x*this.y - v.y*this.x); }, negate : function() { this.x *= -1; this.y *= -1; this.z *= -1; }, normalize : function() { if(this.length != 0 && this.length != 1) { var mod = 1/this.length; this.x *= mod; this.y *= mod; this.z *= mod; } }, project : function(screen) { var scale = screen.focalLength/(screen.focalLength + this.z); return new Point( this.x*scale + screen.projectionCenter.x, -this.y*scale + screen.projectionCenter.y ); }, scaleBy : function(s) { this.x *= s; this.y *= s; this.z *= s; }, rotateX : function(angle) { var s = Math.sin(angle); var c = Math.cos(angle); return new Vertex3D( this.x, c*this.y - s*this.z, s*this.y + c*this.z ); }, rotateY : function(angle) { var s = Math.sin(angle); var c = Math.cos(angle); return new Vertex3D( s*this.z + c*this.x, this.y, c*this.z - s*this.x ); }, rotateZ : function(angle) { var s = Math.sin(angle); var c = Math.cos(angle); return new Vertex3D( c*this.x - s*this.y, s*this.x + c*this.y, this.z ); } }); var TriangleMesh3D = Class.create({ initialize : function(material, vertices, faces) { this.material = material; if(vertices && vertices.length == 3) { this.v0 = vertices[0]; this.v1 = vertices[1]; this.v2 = vertices[2]; }else { this.v0 = new Vertex3D(1, 0, 0); this.v1 = new Vertex3D(0, 1, 0); this.v2 = new Vertex3D(0, 0, 1); } this.faces = faces || []; }, draw : function(screen) { this.material.drawTriangle(this, screen); }, rotateX : function(angle) { this.v0 = this.v0.rotateX(angle); this.v1 = this.v1.rotateX(angle); this.v2 = this.v2.rotateX(angle); }, rotateY : function(angle) { this.v0 = this.v0.rotateY(angle); this.v1 = this.v1.rotateY(angle); this.v2 = this.v2.rotateY(angle); }, rotateZ : function(angle) { this.v0 = this.v0.rotateZ(angle); this.v1 = this.v1.rotateZ(angle); this.v2 = this.v2.rotateZ(angle); }, translate : function(v) { this.v0 = this.v0.add(v); this.v1 = this.v1.add(v); this.v2 = this.v2.add(v); } }); var Quaternion = Class.create({ initialize : function(x, y, z, w) { this.x = x || 0.0; this.y = y || 0.0; this.z = z || 0.0; this.w = w || 1.0; this.EPSILON = 0.000001; this.DEGTORAD = Math.PI/180.0; this.RADTODEG = 180.0/Math.PI; }, clone : function() { return new Quaternion(this.x, this.y, this.z, this.w); }, calculateMultiply : function(a, b) { this.x = a.w*b.x + a.x*b.w + a.y*b.z - a.z*b.y; this.y = a.w*b.y - a.x*b.z + a.y*b.w + a.z*b.x; this.z = a.w*b.z + a.x*b.y - a.y*b.x + a.z*b.w; this.w = a.w*b.w - a.x*b.x - a.y*b.y - a.z*b.z; }, setFromAxisAngle : function(x, y, z, angle) { var s = Math.sin(angle*0.5); var c = Math.cos(angle*0.5); this.x = x*s; this.y = y*s; this.z = z*s; this.w = c; this.normalize(); }, setFromEuler : function(ax, ay, az, useDegrees) { if(useDegrees) { ax *= DEGTORAD; ay *= DEGTORAD; az *= DEGTORAD; } var fSinPitch = Math.sin(ax*0.5); var fCosPitch = Math.cos(ax*0.5); var fSinYaw = Math.sin(ay*0.5); var fCosYaw = Math.cos(ay*0.5); var fSinRoll = Math.sin(az*0.5); var fCosRoll = Math.cos(az*0.5); var fCosPitchCosYaw = fCosPitch*fCosYaw; var fSinPitchSinYaw = fSinPitch*fSinYaw; this.x = fSinRoll*fCosPitchCosYaw - fCosRoll*fSinPitchSinYaw; this.y = fCosRoll*fSinPitch*fCosYaw + fSinRoll*fCosPitch*fSinYaw; this.z = fCosRoll*fCosPitch*fSinYaw - fSinRoll*fSinPitch*fCosYaw; this.w = fCosRoll*fCosPitchCosYaw + fSinRoll*fSinPitchSinYaw; }, modulo : function() { return Math.sqrt(this.x*this.x + this.y*this.y + this.z*this.z * this.w*this.w); }, conjugate : function(a) { return new Quaternion(-a.x, -a.y, -a.z, a.w); }, createFromAxisAngle : function(ax, ay, az, useDegrees) { if(useDegrees) { ax *= DEGTORAD; ay *= DEGTORAD; az *= DEGTORAD; } var fSinPitch = Math.sin(ax*0.5); var fCosPitch = Math.cos(ax*0.5); var fSinYaw = Math.sin(ay*0.5); var fCosYaw = Math.cos(ay*0.5); var fSinRoll = Math.sin(az*0.5); var fCosRoll = Math.cos(az*0.5); var fCosPitchCosYaw = fCosPitch*fCosYaw; var fSinPitchSinYaw = fSinPitch*fSinYaw; var q = new Quaternion(); q.x = fSinRoll*fCosPitchCosYaw - fCosRoll*fSinPitchSinYaw; q.y = fCosRoll*fSinPitch*fCosYaw + fSinRoll*fCosPitch*fSinYaw; q.z = fCosRoll*fCosPitch*fSinYaw - fSinRoll*fSinPitch*fCosYaw; q.w = fCosRoll*fCosPitchCosYaw + fSinRoll*fSinPitchSinYaw; return q; }, add : function(a, b) { return new Quaternion(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w); }, sub : function(a, b) { return new Quaternion(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w); }, dot : function(a, b) { return (a.x*b.x) + (a.y*b.y) + (a.z*b.z) + (a.w*b.w); }, multiply : function(a, b) { var c = new Quaternion(); c.x = a.w*b.x + a.x*b.w + a.y*b.z - a.z*b.y; c.y = a.w*b.y - a.x*b.z + a.y*b.w + a.z*b.x; c.z = a.w*b.z + a.x*b.y - a.y*b.x + a.z*b.w; c.w = a.w*b.w - a.x*b.x - a.y*b.y - a.z*b.z; return c; }, mult : function(b) { var ax = this.x; var ay = this.y; var az = this.z; var aw = this.w; this.x = aw*b.x + ax*b.w + ay*b.z - az*b.y; this.y = aw*b.y - ax*b.z + ay*b.w + az*b.x; this.z = aw*b.z + ax*b.y - ay*b.x + az*b.w; this.w = aw*b.w - ax*b.x - ay*b.y - az*b.z; }, normalize : function() { var len = this.modulo(); if(Math.abs(len) < this.EPSILON) { this.x = this.y = this.z = 0.0; this.w = 1.0; }else { var m = 1/len; this.x *= m; this.y *= m; this.z *= m; this.w *= m; } }, slerp : function(qa, qb, alpha) { var angle = qa.w*qb.w + qa.x*qb.x + qa.y*qb.y + qa.z*qb.z; if(angle < 0.0) { qa.x *= -1.0; qa.y *= -1.0; qa.z *= -1.0; qa.w *= -1.0; angle *= -1.0; } var scale; var invscale; if((angle + 1.0) > this.EPSILON) { if((1.0 - angle) >= this.EPSILON) { var theta = Math.acos(angle); var invsintheta = 1.0/Math.sin(theta); scale = Math.sin(theta*(1.0 - alpha))*invsintheta; invscale = Math.sin(theta*alpha)*invsintheta; }else { scale = 1.0 - alpha; invscale = alpha; } }else { qb.y = -qa.y; qb.x = qa.x; qb.w = -qa.w; qb.z = qa.z; scale = Math.sin(Math.PI*(0.5 - alpha)); invscale = Math.sin(Math.PI*alpha); } return new Quaternion(scale*qa.x + invscale*qb.x, scale*qa.y + invscale*qb.y, scale*qa.z + invscale*qb.z, scale*qa.w + invscale*qb.w ); }, toEuler : function() { var euler = {x:0, y:0, z:0}; var q = this; var test = q.x*q.y + q.z*q.w; if(test > 0.499) { euler.x = 2*Math.atan2(q.x, q.w); euler.y = Math.PI/2; euler.z = 0; return euler; } if(test < -0.499) { euler.x = -2*Math.atan2(q.x, q.w); euler.y = -Math.PI/2; euler.z = 0; return euler; } var sqx = q.x*q.x; var sqy = q.y*q*y; var sqz = q.z*q.z; euler.x = Math.atan2(2*q.y*q.w - 2*q.x*q.z, 1 - 2*sqy - 2*sqz); euler.y = Math.asin(2*test); euler.z = Math.atan2(2*q.x*q.w - 2*q.y*q.z , 1 - 2*sqx - 2*sqz); return euler; } }); var Scene3D = Class.create({ initialize : function() { this.objects = []; this.materials = []; }, addObject : function(object) { this.objects.push(object); }, removeObject : function(object) { for(var i=0; i> 1), y = (point3d[1]*scale) + (_height >> 1); _context.lineWidth= scale << 1; _context.beginPath(); _context.moveTo(x, y); _context.lineTo(x + scale, y); _context.stroke(); }, _rotateX = function(point3d, angle) { var y = point3d[1], z = point3d[2], c = Math.cos(angle), s = Math.sin(angle), ty = y, tz = z; y = ty*c - tz*s; z = ty*s + tz*c; point3d[1] = y; point3d[2] = z; }, _rotateY = function(point3d, angle) { var x = point3d[0], z = point3d[2], c = Math.cos(angle), s = Math.sin(angle), tz = z, tx = x; x = tx*c + tz*s; z = tz*c - tx*s; point3d[0] = x; point3d[2] = z; }, _rotateZ = function(point3d, angle) { var x = point3d[0], y = point3d[1], c = Math.cos(angle), s = Math.sin(angle), tx = x, ty = y; x = tx*c - ty*s; y = tx*s + ty*c; point3d[0] = x; point3d[1] = y; }; // public APIs return { init: _init, play: _play, stop: _stop, }; })(); ================================================ FILE: draw/webgl/glsample.js ================================================ /** * WebGL Tutorial * https://developer.mozilla.org/ja/docs/Web/API/WebGL_API */ var gl; // WebGL コンテキスト用のグローバル変数 function start() { var canvas = document.getElementById("Canvas"); // GL コンテキストを初期化 gl = initWebGL(canvas); // WebGL を使用できる場合に限り、処理を継続 if (gl) { // クリアカラーを黒色、不透明に設定する gl.clearColor(0.0, 0.0, 0.0, 1.0); // 深度テストを有効化 gl.enable(gl.DEPTH_TEST); // 近くにある物体は、遠くにある物体を覆い隠す gl.depthFunc(gl.LEQUAL); // カラーバッファや深度バッファをクリアする gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); } } function initWebGL(canvas) { gl = null; try { // 標準コンテキストの取得を試みる。失敗した場合は、experimental にフォールバックする。 gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl"); } catch(e) { console.log(e); } // GL コンテキストを取得できない場合は終了する if (!gl) { alert("WebGL を初期化できません。ブラウザはサポートしていないようです。"); gl = null; } return gl; } ================================================ FILE: draw/webgl/webglsample.html ================================================ WebGL Sample

WebGL Sample

================================================ FILE: ml/README.md ================================================ # Machine Learning Module ## description Machine Learning in JavaScript / TypeScript (+ AngularJS) ### list - [AROW](https://github.com/wellflat/imageprocessing-labs/tree/master/ml/arow) - [Decision Tree](https://github.com/wellflat/imageprocessing-labs/blob/master/ml/decisiontree/decisiontree.js) - [Denoising Autoencoders](https://github.com/wellflat/imageprocessing-labs/tree/master/ml/autoencoder) - [K-Means++](https://github.com/wellflat/imageprocessing-labs/blob/master/ml/kmeans/kmeans.js) - [Logistic Regression](https://github.com/wellflat/imageprocessing-labs/blob/master/ml/logistic_regression/logistic_regression.ts) - [SCW](https://github.com/wellflat/imageprocessing-labs/tree/master/ml/scw) - [Gradient Boosting(GBDT)](https://github.com/wellflat/imageprocessing-labs/tree/master/ml/gbdt) - [t-Distributed Stochastic Neighbor Embedding(t-SNE)](https://github.com/wellflat/imageprocessing-labs/tree/master/ml/t-sne) ### sample [![da](https://github.com/wellflat/imageprocessing-labs/blob/master/images/da_demo.png)](https://rest-term.com/labs/html5/da/) [![pixel_clustering](https://github.com/wellflat/imageprocessing-labs/blob/master/images/pixel_clustering.jpg)](https://rest-term.com/labs/html5/pixelclustering.html) [![decision_tree](https://github.com/wellflat/imageprocessing-labs/blob/master/images/decision_tree.png)](https://rest-term.com/labs/html5/dtree.html) [![t-sne](https://github.com/wellflat/imageprocessing-labs/blob/master/images/tsne.png)](https://rest-term.com/labs/html5/tsne/) (Tests: Firefox75.0+, Chrome81.0+) ### web sample [Denoising Autoencoders][DenoisingAutoencoders] [Pixel Clustering using K-Means++][PixelClustering] [Decision Tree Learning][DecisionTree] [t-Distributed Stochastic Neighbor Embedding(t-SNE)][tSNE] license ---------- Copyright © 2014 wellflat Licensed under the [MIT License][MIT] [DenoisingAutoencoders]: http://rest-term.com/labs/html5/da/ [PixelClustering]: http://rest-term.com/labs/html5/pixelclustering.html [DecisionTree]: http://rest-term.com/labs/html5/dtree.html [tSNE]: https://rest-term.com/labs/html5/tsne/ [MIT]: http://www.opensource.org/licenses/mit-license.php ================================================ FILE: ml/arow/README.md ================================================ # Adaptive Regularization of Weight Vectors (AROW) * AROW algorithm and LIBSVM format data loader modules (JavaScript codes in 'out' directory) * LIBSVM Data: Classification (Binary Class) [news20.binary](https://www.csie.ntu.edu.tw/~cjlin/libsvmtools/datasets/binary.html#news20.binary) ## usage in TypeScript ```typescript import {Feature, DataSet} from './types'; import {AROW} from './arow'; import {DataLoader} from './data_loader'; const featureSize: number = 1355191; const r: number = 0.1; const clf: AROW = new AROW(featureSize, r); const trainDataFile: string = 'data/news20_train'; const testDataFile: string = 'data/news20_test'; console.log('asynchronous stream data read training'); DataLoader.read(trainDataFile, (x: Feature, label: number) => { clf.update(x, label); }, () => { console.log('a iteration training complete.'); let size: number = 0; let error: number = 0; DataLoader.read(testDataFile, (x: Feature, label: number) => { size++; let predLabel: number = clf.predict(x); if (predLabel != label) { error++; } }, () => { console.log('error rate = ' + (error / size)); }); }); console.log('synchronous batch data read training'); const trainData: DataLoader = new DataLoader(trainDataFile); const testData: DataLoader = new DataLoader(testDataFile); console.log('load data complete.'); console.log(trainData.size, testData.size); const maxIter: number = 5; for (let i = 0; i < maxIter; i++) { trainData.data.forEach(e => { clf.update(e.x, e.label); }); var error: number = 0; testData.data.forEach(e => { var predLabel: number = clf.predict(e.x); if (predLabel != e.label) { error++; } }); console.log('iteration: ' + (i + 1) + ', error rate = ' + (error / testData.size)); } ``` ================================================ FILE: ml/arow/app.ts ================================================ import {Feature, DataSet} from './types'; import {AROW} from './arow'; import {DataLoader} from './data_loader'; const featureSize: number = 1355191; const r: number = 0.1; const clf: AROW = new AROW(featureSize, r); const trainDataFile: string = 'data/news20_train'; const testDataFile: string = 'data/news20_test'; console.log('asynchronous stream data read training'); DataLoader.read(trainDataFile, (x: Feature, label: number) => { clf.update(x, label); }, () => { console.log('a iteration training complete.'); let size: number = 0; let error: number = 0; DataLoader.read(testDataFile, (x: Feature, label: number) => { size++; let predLabel: number = clf.predict(x); if (predLabel != label) { error++; } }, () => { console.log('error rate = ' + (error / size)); }); }); console.log('synchronous batch data read training'); const trainData: DataLoader = new DataLoader(trainDataFile); const testData: DataLoader = new DataLoader(testDataFile); console.log('load data complete.'); console.log(trainData.size, testData.size); const maxIter: number = 5; for (let i = 0; i < maxIter; i++) { trainData.data.forEach(e => { clf.update(e.x, e.label); }); var error: number = 0; testData.data.forEach(e => { var predLabel: number = clf.predict(e.x); if (predLabel != e.label) { error++; } }); console.log('iteration: ' + (i + 1) + ', error rate = ' + (error / testData.size)); } ================================================ FILE: ml/arow/arow.ts ================================================ /** * AROW (Adaptive Regularization of Weight Vectors) class */ import {Feature, DataSet} from './types'; export class AROW { private mean: Float32Array; private cov: Float32Array; private f: number; private r: number; constructor(featureSize: number, r: number = 0.1) { this.f = featureSize; this.mean = new Float32Array(featureSize); this.cov = new Float32Array(featureSize); this.cov.fill(1.0); this.r = r; } public predict(x: Feature): number { return this.computeMargin(x) > 0 ? 1 : -1; } public clear(): void { this.mean = new Float32Array(this.f); this.cov = new Float32Array(this.f); this.cov.fill(1.0); } public update(x: Feature, label: number): number { let margin: number = this.computeMargin(x); let loss: number = margin * label < 0 ? 1 : 0; if (margin * label >= 1) { return 0; } let confidence: number = this.computeConfidence(x); let beta: number = 1.0 / (confidence + this.r); let alpha: number = (1.0 - label * margin) * beta; x.forEach(e => { this.mean[e.index] += alpha * label * this.cov[e.index] * e.value; }) x.forEach(e => { this.cov[e.index] = 1.0 / ((1.0 / this.cov[e.index]) + e.value * e.value / this.r); }); return loss; } private computeMargin(x: Feature): number { let res:number = 0.0; x.forEach(e => res += this.mean[e.index] * e.value); return res; } private computeConfidence(x: Feature): number { let res:number = 0.0; x.forEach(e => res += this.cov[e.index] * e.value * e.value); return res; } } ================================================ FILE: ml/arow/data_loader.ts ================================================ /** * Data loader for LIBSVM data format *