Files
image-sequencer/src/modules/EdgeDetect/EdgeUtils.js
2019-05-11 10:39:15 -04:00

203 lines
5.6 KiB
JavaScript

// Define kernels for the sobel filter
const kernelx = [
[-1, 0, 1],
[-2, 0, 2],
[-1, 0, 1]
],
kernely = [
[-1,-2,-1],
[ 0, 0, 0],
[ 1, 2, 1]
];
let pixelsToBeSupressed = [];
module.exports = function(pixels, highThresholdRatio, lowThresholdRatio, useHysteresis) {
let angles = [], grads = [], strongEdgePixels = [], weakEdgePixels = [];
for (var x = 0; x < pixels.shape[0]; x++) {
grads.push([]);
angles.push([]);
for (var y = 0; y < pixels.shape[1]; y++) {
var result = sobelFilter(
pixels,
x,
y
);
let pixel = result.pixel;
grads.slice(-1)[0].push(pixel[3]);
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);
strongEdgePixels.forEach(pixel => preserve(pixels, pixel));
weakEdgePixels.forEach(pixel => supress(pixels, pixel));
pixelsToBeSupressed.forEach(pixel => supress(pixels, pixel));
return pixels;
};
function supress(pixels, pixel) {
pixels.set(pixel[0], pixel[1], 0, 0);
pixels.set(pixel[0], pixel[1], 1, 0);
pixels.set(pixel[0], pixel[1], 2, 0);
pixels.set(pixel[0], pixel[1], 3, 255);
}
function preserve(pixels, pixel) {
pixels.set(pixel[0], pixel[1], 0, 255);
pixels.set(pixel[0], pixel[1], 1, 255);
pixels.set(pixel[0], pixel[1], 2, 255);
pixels.set(pixel[0], pixel[1], 3, 255);
}
// sobelFilter function that convolves sobel kernel over every pixel
function sobelFilter(pixels, x, y) {
let val = pixels.get(x, y, 0),
gradX = 0.0,
gradY = 0.0;
for (let a = 0; a < 3; a++) {
for (let b = 0; b < 3; b++) {
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];
gradY += pixels.get(xn+1, yn+1, 0) * kernely[a][b];
}
else {
gradX += pixels.get(xn, yn, 0) * kernelx[a][b];
gradY += pixels.get(xn, yn, 0) * kernely[a][b];
}
}
}
const grad = Math.sqrt(Math.pow(gradX, 2) + Math.pow(gradY, 2)),
angle = Math.atan2(gradY, gradX);
return {
pixel: [val, val, val, grad],
angle: 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;
else if ((angle >= 67.5 && angle <= 112.5) || (angle < -67.5 && angle >= -112.5)) return 3;
else if ((angle >= 112.5 && angle <= 157.5) || (angle < -22.5 && angle >= -67.5)) return 4;
/* Category Map
* 1 => E-W
* 2 => NE-SW
* 3 => N-S
* 4 => NW-SE
*/
}
function isOutOfBounds(pixels, x, y){
return ((x < 0) || (y < 0) || (x >= pixels.shape[0]) || (y >= pixels.shape[1]));
}
const removeElem = (arr = [], elem) => {
return arr = arr.filter((arrelem) => {
return arrelem !== elem;
});
};
// Non Maximum Supression without interpolation
function nonMaxSupress(pixels, grads, angles) {
angles = angles.map((arr) => arr.map(convertToDegrees));
for (let x = 0; x < pixels.shape[0]; x++) {
for (let y = 0; y < pixels.shape[1]; y++) {
let angleCategory = categorizeAngle(angles[x][y]);
if (!isOutOfBounds(pixels, x - 1, y - 1) && !isOutOfBounds(pixels, x+1, y+1)){
switch (angleCategory){
case 1:
if (!((grads[x][y] >= grads[x][y + 1]) && (grads[x][y] >= grads[x][y - 1]))) {
pixelsToBeSupressed.push([x, y]);
}
break;
case 2:
if (!((grads[x][y] >= grads[x + 1][y + 1]) && (grads[x][y] >= grads[x - 1][y - 1]))){
pixelsToBeSupressed.push([x, y]);
}
break;
case 3:
if (!((grads[x][y] >= grads[x + 1][y]) && (grads[x][y] >= grads[x - 1][y]))) {
pixelsToBeSupressed.push([x, y]);
}
break;
case 4:
if (!((grads[x][y] >= grads[x + 1][y - 1]) && (grads[x][y] >= grads[x - 1][y + 1]))) {
pixelsToBeSupressed.push([x, y]);
}
break;
}
}
}
}
}
// Converts radians to degrees
var convertToDegrees = radians => (radians * 180) / Math.PI;
// 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
function doubleThreshold(pixels, highThresholdRatio, lowThresholdRatio, grads, strongEdgePixels, weakEdgePixels) {
const highThreshold = findMaxInMatrix(grads) * highThresholdRatio,
lowThreshold = highThreshold * lowThresholdRatio;
for (let x = 0; x < pixels.shape[0]; x++) {
for (let y = 0; y < pixels.shape[1]; y++) {
let pixelPos = [x, y];
if (grads[x][y] > lowThreshold){
if (grads[x][y] > highThreshold) {
strongEdgePixels.push(pixelPos);
}
else {
weakEdgePixels.push(pixelPos);
}
}
else {
pixelsToBeSupressed.push(pixelPos);
}
}
}
}
function hysteresis(strongEdgePixels, weakEdgePixels){
strongEdgePixels.forEach(pixel => {
let x = pixel[0],
y = pixel[1];
if (weakEdgePixels.includes([x+1, y])) {
removeElem(weakEdgePixels, [x+1, y]);
}
else if (weakEdgePixels.includes([x-1, y])) {
removeElem(weakEdgePixels, [x-1, y]);
}
else if (weakEdgePixels.includes([x, y+1])) {
removeElem(weakEdgePixels, [x, y+1]);
}
else if(weakEdgePixels.includes([x, y-1])) {
removeElem(weakEdgePixels, [x, y-1]);
}
});
}