mirror of
https://github.com/publiclab/image-sequencer.git
synced 2025-12-09 17:59:59 +01:00
[GCI] Standardised Edge Detect module code comments (#1346)
* standardise comments * more fixes * fix * Apply suggestions from code review Co-authored-by: Jeffrey Warren <jeff@unterbahn.com>
This commit is contained in:
committed by
Jeffrey Warren
parent
1af9655bca
commit
51ce1c52f4
@@ -1,6 +1,8 @@
|
||||
// Define kernels for the sobel filter
|
||||
// Read More: https://en.wikipedia.org/wiki/Canny_edge_detector
|
||||
|
||||
const pixelSetter = require('../../util/pixelSetter.js');
|
||||
|
||||
// Define kernels for the sobel filter.
|
||||
const kernelx = [
|
||||
[-1, 0, 1],
|
||||
[-2, 0, 2],
|
||||
@@ -21,7 +23,7 @@ module.exports = function(pixels, highThresholdRatio, lowThresholdRatio, useHyst
|
||||
grads.push([]);
|
||||
angles.push([]);
|
||||
for (var y = 0; y < pixels.shape[1]; y++) {
|
||||
var result = sobelFilter(
|
||||
var result = sobelFilter( // Convolves the sobel filter on every pixel
|
||||
pixels,
|
||||
x,
|
||||
y
|
||||
@@ -32,28 +34,47 @@ module.exports = function(pixels, highThresholdRatio, lowThresholdRatio, useHyst
|
||||
angles.slice(-1)[0].push(result.angle);
|
||||
}
|
||||
}
|
||||
nonMaxSupress(pixels, grads, angles);
|
||||
doubleThreshold(pixels, highThresholdRatio, lowThresholdRatio, grads, strongEdgePixels, weakEdgePixels);
|
||||
if(useHysteresis.toLowerCase() == 'true') hysteresis(strongEdgePixels, weakEdgePixels);
|
||||
nonMaxSupress(pixels, grads, angles); // Non Maximum Suppression: Filter fine edges.
|
||||
doubleThreshold(pixels, highThresholdRatio, lowThresholdRatio, grads, strongEdgePixels, weakEdgePixels); // Double Threshold: Categorizes edges into strong and weak edges based on two thresholds.
|
||||
if(useHysteresis.toLowerCase() == 'true') hysteresis(strongEdgePixels, weakEdgePixels); // Optional Hysteresis (very slow) to minimize edges generated due to noise.
|
||||
|
||||
strongEdgePixels.forEach(pixel => preserve(pixels, pixel));
|
||||
weakEdgePixels.forEach(pixel => supress(pixels, pixel));
|
||||
pixelsToBeSupressed.forEach(pixel => supress(pixels, pixel));
|
||||
strongEdgePixels.forEach(pixel => preserve(pixels, pixel)); // Makes the strong edges White.
|
||||
weakEdgePixels.forEach(pixel => supress(pixels, pixel)); // Makes the weak edges black(bg color) after filtering.
|
||||
pixelsToBeSupressed.forEach(pixel => supress(pixels, pixel)); // Makes the rest of the image black.
|
||||
|
||||
return pixels;
|
||||
};
|
||||
|
||||
/**
|
||||
* @method supress
|
||||
* @description Supresses (fills with background color) the specified (non-edge)pixel.
|
||||
* @param {Object} pixels ndarry of pixels
|
||||
* @param {Float32Array} pixel Pixel coordinates
|
||||
* @returns {Null}
|
||||
*/
|
||||
function supress(pixels, pixel) {
|
||||
pixelSetter(pixel[0], pixel[1], [0, 0, 0, 255], pixels);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @method preserve
|
||||
* @description Preserve the specified pixel(of an edge).
|
||||
* @param {Object} pixels ndarray of pixels
|
||||
* @param {*} pixel Pixel coordinates
|
||||
* @returns {Null}
|
||||
*/
|
||||
function preserve(pixels, pixel) {
|
||||
pixelSetter(pixel[0], pixel[1], [255, 255, 255, 255], pixels);
|
||||
|
||||
}
|
||||
|
||||
// sobelFilter function that convolves sobel kernel over every pixel
|
||||
/**
|
||||
* @method sobelFiler
|
||||
* @description Runs the sobel filter on the specified and neighbouring pixels.
|
||||
* @param {Object} pixels ndarray of pixels
|
||||
* @param {Number} x x-coordinate of the pixel
|
||||
* @param {Number} y y-coordinate of the pixel
|
||||
* @returns {Object} Object containing the gradient and angle.
|
||||
*/
|
||||
function sobelFilter(pixels, x, y) {
|
||||
let val = pixels.get(x, y, 0),
|
||||
gradX = 0.0,
|
||||
@@ -65,8 +86,8 @@ function sobelFilter(pixels, x, y) {
|
||||
let xn = x + a - 1,
|
||||
yn = y + b - 1;
|
||||
|
||||
if (isOutOfBounds(pixels, xn, yn)) {
|
||||
gradX += pixels.get(xn + 1, yn + 1, 0) * kernelx[a][b];
|
||||
if (isOutOfBounds(pixels, xn, yn)) { // Fallback for coordinates which lie outside the image.
|
||||
gradX += pixels.get(xn + 1, yn + 1, 0) * kernelx[a][b]; // Fallback to nearest pixel
|
||||
gradY += pixels.get(xn + 1, yn + 1, 0) * kernely[a][b];
|
||||
}
|
||||
else {
|
||||
@@ -84,6 +105,12 @@ function sobelFilter(pixels, x, y) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @method categorizeAngle
|
||||
* @description Categorizes the given angle into 4 catagories according to the Category Map given below.
|
||||
* @param {Number} angle Angle in degrees
|
||||
* @returns {Number} Category number of the given angle
|
||||
*/
|
||||
function categorizeAngle(angle){
|
||||
if ((angle >= -22.5 && angle <= 22.5) || (angle < -157.5 && angle >= -180)) return 1;
|
||||
else if ((angle >= 22.5 && angle <= 67.5) || (angle < -112.5 && angle >= -157.5)) return 2;
|
||||
@@ -98,17 +125,25 @@ function categorizeAngle(angle){
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
* @method isOutOfBounds
|
||||
* @description Checks whether the given coordinates lie outside the bounds of the image. Used for error handling in convolution.
|
||||
* @param {Object} pixels ndarray of pixels
|
||||
* @param {*} x x-coordinate of the pixel
|
||||
* @param {*} y y-coordinate of the pixel
|
||||
* @returns {Boolean} True if the given coordinates are out of bounds.
|
||||
*/
|
||||
function isOutOfBounds(pixels, x, y){
|
||||
return ((x < 0) || (y < 0) || (x >= pixels.shape[0]) || (y >= pixels.shape[1]));
|
||||
}
|
||||
|
||||
const removeElem = (arr = [], elem) => {
|
||||
const removeElem = (arr = [], elem) => { // Removes the specified element from the given array.
|
||||
return arr = arr.filter((arrelem) => {
|
||||
return arrelem !== elem;
|
||||
});
|
||||
};
|
||||
|
||||
// Non Maximum Supression without interpolation
|
||||
// Non Maximum Supression without interpolation.
|
||||
function nonMaxSupress(pixels, grads, angles) {
|
||||
angles = angles.map((arr) => arr.map(convertToDegrees));
|
||||
|
||||
@@ -118,7 +153,7 @@ function nonMaxSupress(pixels, grads, angles) {
|
||||
let angleCategory = categorizeAngle(angles[x][y]);
|
||||
|
||||
if (!isOutOfBounds(pixels, x - 1, y - 1) && !isOutOfBounds(pixels, x + 1, y + 1)){
|
||||
switch (angleCategory){
|
||||
switch (angleCategory){ // Non maximum suppression according to angle category
|
||||
case 1:
|
||||
if (!((grads[x][y] >= grads[x][y + 1]) && (grads[x][y] >= grads[x][y - 1]))) {
|
||||
pixelsToBeSupressed.push([x, y]);
|
||||
@@ -147,17 +182,24 @@ function nonMaxSupress(pixels, grads, angles) {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Converts radians to degrees
|
||||
|
||||
|
||||
/**
|
||||
* @method convertToDegrees
|
||||
* @description Converts the given angle(in radians) to degrees.
|
||||
* @param {Number} radians Angle in radians
|
||||
* @returns {Number} Angle in degrees
|
||||
*/
|
||||
var convertToDegrees = radians => (radians * 180) / Math.PI;
|
||||
|
||||
// Finds the max value in a 2d array like grads
|
||||
// Finds the max value in a 2d array like grads.
|
||||
var findMaxInMatrix = arr => Math.max(...arr.map(el => el.map(val => val ? val : 0)).map(el => Math.max(...el)));
|
||||
|
||||
// Applies the double threshold to the image
|
||||
// Applies the double threshold to the image.
|
||||
function doubleThreshold(pixels, highThresholdRatio, lowThresholdRatio, grads, strongEdgePixels, weakEdgePixels) {
|
||||
|
||||
const highThreshold = findMaxInMatrix(grads) * highThresholdRatio,
|
||||
lowThreshold = highThreshold * lowThresholdRatio;
|
||||
const highThreshold = findMaxInMatrix(grads) * highThresholdRatio, // High Threshold relative to the strongest edge
|
||||
lowThreshold = highThreshold * lowThresholdRatio; // Low threshold relative to high threshold
|
||||
|
||||
for (let x = 0; x < pixels.shape[0]; x++) {
|
||||
for (let y = 0; y < pixels.shape[1]; y++) {
|
||||
@@ -178,6 +220,12 @@ function doubleThreshold(pixels, highThresholdRatio, lowThresholdRatio, grads, s
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @method hysteresis
|
||||
* @description Filters weak edge pixels that are not connected to a strong edge pixel.
|
||||
* @param {Float32array} strongEdgePixels 2D array of strong edge pixel coordinates
|
||||
* @param {*} weakEdgePixels 2D array of weak edge pixel coordinated
|
||||
*/
|
||||
function hysteresis(strongEdgePixels, weakEdgePixels){
|
||||
strongEdgePixels.forEach(pixel => {
|
||||
let x = pixel[0],
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/*
|
||||
/**
|
||||
* Detect Edges in an Image
|
||||
* Uses Canny method for the same
|
||||
* Read more: https://en.wikipedia.org/wiki/Canny_edge_detector
|
||||
*/
|
||||
module.exports = function edgeDetect(options, UI) {
|
||||
|
||||
@@ -19,17 +21,17 @@ module.exports = function edgeDetect(options, UI) {
|
||||
|
||||
var step = this;
|
||||
|
||||
// Blur the image
|
||||
// Blur the image.
|
||||
const internalSequencer = ImageSequencer({ inBrowser: false, ui: false });
|
||||
return internalSequencer.loadImage(input.src, function() {
|
||||
internalSequencer.importJSON([{ 'name': 'blur', 'options': { blur: options.blur } }]);
|
||||
internalSequencer.importJSON([{ 'name': 'blur', 'options': { blur: options.blur } }]); // Blurs the image before detecting edges to reduce noise.
|
||||
return internalSequencer.run(function onCallback(internalOutput) {
|
||||
require('get-pixels')(internalOutput, function(err, blurPixels) {
|
||||
if (err) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Extra Manipulation function used as an enveloper for applying gaussian blur and Convolution
|
||||
// Extra Manipulation function used as an enveloper for applying gaussian blur and Convolution.
|
||||
function changePixel(r, g, b, a) {
|
||||
return [(r + g + b) / 3, (r + g + b) / 3, (r + g + b) / 3, a];
|
||||
}
|
||||
@@ -64,4 +66,4 @@ module.exports = function edgeDetect(options, UI) {
|
||||
output: output,
|
||||
UI: UI
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "edge-detect",
|
||||
"description": "This module detects edges using the Canny method, which first Gaussian blurs the image to reduce noise (amount of blur configurable in settings as `options.blur`), then applies a number of steps to highlight edges, resulting in a greyscale image where the brighter the pixel, the stronger the detected edge.<a href='https://en.wikipedia.org/wiki/Canny_edge_detector'> Read more. </a>",
|
||||
"description": "Edge Detect module detects edges using the Canny method, which first blurs the image to reduce noise (amount of blur configurable in settings as `options.blur`), then applies a number of steps to highlight edges, resulting in a greyscale image where the brighter the pixel, the stronger the detected edge. [Read more](https://en.wikipedia.org/wiki/Canny_edge_detector)",
|
||||
"inputs": {
|
||||
"blur": {
|
||||
"type": "float",
|
||||
|
||||
Reference in New Issue
Block a user