✨ blur and body segmentation tweaks, adding kernel presets
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "graphfx",
|
"name": "graphfx",
|
||||||
"version": "0.4.0",
|
"version": "0.4.1",
|
||||||
"description": "Graph image processing pipeline",
|
"description": "Graph image processing pipeline",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -15,28 +15,17 @@ import {
|
|||||||
} from '@tensorflow-models/body-segmentation'
|
} from '@tensorflow-models/body-segmentation'
|
||||||
import {canvasPool2D} from "../../canvas/CanvasPool";
|
import {canvasPool2D} from "../../canvas/CanvasPool";
|
||||||
import {waitForMedia} from "../../utils";
|
import {waitForMedia} from "../../utils";
|
||||||
|
import { mediaSize } from '../canvas';
|
||||||
|
|
||||||
const inputs = {
|
const inputs = {
|
||||||
image: {
|
image: {
|
||||||
type: 'Image'
|
type: 'Image'
|
||||||
} as ImageVar,
|
} as ImageVar,
|
||||||
width: {
|
|
||||||
type: 'Number',
|
|
||||||
default: 100,
|
|
||||||
step: 1,
|
|
||||||
min: 1
|
|
||||||
} as NumberVar,
|
|
||||||
height: {
|
|
||||||
type: 'Number',
|
|
||||||
default: 100,
|
|
||||||
step: 1,
|
|
||||||
min: 1,
|
|
||||||
} as NumberVar,
|
|
||||||
// blur mask edges by number of pixels
|
// blur mask edges by number of pixels
|
||||||
maskBlurAmount: {
|
maskBlurAmount: {
|
||||||
type: 'Number',
|
type: 'Number',
|
||||||
default: 0,
|
default: 1,
|
||||||
step: 1,
|
step: 0.1,
|
||||||
} as NumberVar,
|
} as NumberVar,
|
||||||
// Default to 0.5. The minimum probability to color a pixel as foreground rather than background. The alpha channel integer values will be taken as the probabilities (for more information refer to Segmentation type's documentation).
|
// Default to 0.5. The minimum probability to color a pixel as foreground rather than background. The alpha channel integer values will be taken as the probabilities (for more information refer to Segmentation type's documentation).
|
||||||
foregroundThreshold: {
|
foregroundThreshold: {
|
||||||
@@ -70,29 +59,6 @@ export default class BodySegmentation extends Node<typeof inputs, typeof outputs
|
|||||||
super('BodySegmentation', inputs, outputs);
|
super('BodySegmentation', inputs, outputs);
|
||||||
}
|
}
|
||||||
|
|
||||||
get width() {
|
|
||||||
return this.__in.width.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
get height() {
|
|
||||||
return this.__in.height.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
get image() {
|
|
||||||
if (!this.__in.image.value) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
const {width, height} = this.__in.image.value;
|
|
||||||
return {
|
|
||||||
image: this.__in.image.value,
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async initSegmenter() {
|
async initSegmenter() {
|
||||||
if (this.segmenter) {
|
if (this.segmenter) {
|
||||||
return;
|
return;
|
||||||
@@ -108,13 +74,14 @@ export default class BodySegmentation extends Node<typeof inputs, typeof outputs
|
|||||||
async segmentPeople() {
|
async segmentPeople() {
|
||||||
await this.initSegmenter();
|
await this.initSegmenter();
|
||||||
await waitForMedia(this.in.image.value);
|
await waitForMedia(this.in.image.value);
|
||||||
|
const {width, height} = mediaSize(this.__in.image.value);
|
||||||
if (!this.in.image.value) {
|
if (!this.in.image.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const originalImageCanvas = canvasPool2D.createCanvas();
|
const originalImageCanvas = canvasPool2D.createCanvas();
|
||||||
originalImageCanvas.width = this.in.width.value;
|
originalImageCanvas.width = width;
|
||||||
originalImageCanvas.height = this.in.height.value
|
originalImageCanvas.height = height;
|
||||||
originalImageCanvas.acquire();
|
originalImageCanvas.acquire();
|
||||||
const originalCanvasCtx = originalImageCanvas.getContext('2d');
|
const originalCanvasCtx = originalImageCanvas.getContext('2d');
|
||||||
|
|
||||||
@@ -137,16 +104,15 @@ export default class BodySegmentation extends Node<typeof inputs, typeof outputs
|
|||||||
const canvas = canvasPool2D.createCanvas();
|
const canvas = canvasPool2D.createCanvas();
|
||||||
canvas.acquire();
|
canvas.acquire();
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
canvas.width = this.in.width.value;
|
canvas.width = width;
|
||||||
canvas.height = this.in.height.value
|
canvas.height = height;
|
||||||
|
|
||||||
const maskBlurAmount = this.in.maskBlurAmount.value;
|
const maskBlurAmount = this.in.maskBlurAmount.value;
|
||||||
await drawMask(canvas, canvas as CanvasImageSource, backgroundDarkeningMask, 1, maskBlurAmount);
|
await drawMask(canvas, canvas as CanvasImageSource, backgroundDarkeningMask, 1, maskBlurAmount);
|
||||||
|
|
||||||
this.out.image.value = canvas;
|
this.out.image.value = canvas;
|
||||||
this.out.originalImage.value = originalImageCanvas;
|
this.out.originalImage.value = originalImageCanvas;
|
||||||
this.out.width.value = this.in.width.value;
|
this.out.width.value = width;
|
||||||
this.out.height.value = this.in.height.value;
|
this.out.height.value = height;
|
||||||
}
|
}
|
||||||
|
|
||||||
async _update(){
|
async _update(){
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import Canvas2d from './Canvas2d';
|
import Canvas2d from './Canvas2d';
|
||||||
import {createCanvas, mediaSize, paintToCanvas} from '../canvas';
|
import {createCanvas, mediaSize, paintToCanvas} from '../canvas';
|
||||||
import {
|
import {
|
||||||
BooleanVar
|
BooleanVar, ImageVar
|
||||||
} from '../io/AbstractIOSet';
|
} from '../io/AbstractIOSet';
|
||||||
|
|
||||||
const inputs = {
|
const inputs = {
|
||||||
|
|||||||
@@ -10,11 +10,14 @@ const inputs = {
|
|||||||
} as ImageVar,
|
} as ImageVar,
|
||||||
radius: {
|
radius: {
|
||||||
type: 'Number',
|
type: 'Number',
|
||||||
default: 0,
|
default: 8,
|
||||||
|
min: 0,
|
||||||
} as NumberVar,
|
} as NumberVar,
|
||||||
sigma: {
|
sigma: {
|
||||||
type: 'Number',
|
type: 'Number',
|
||||||
default: 0,
|
default: 1,
|
||||||
|
step: 0.1,
|
||||||
|
min: 0,
|
||||||
} as NumberVar,
|
} as NumberVar,
|
||||||
passes: {
|
passes: {
|
||||||
type: 'Number',
|
type: 'Number',
|
||||||
@@ -105,7 +108,6 @@ export default class Blur extends WebGL<typeof inputs> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
gl_FragColor = colorSum / totalWeight;
|
gl_FragColor = colorSum / totalWeight;
|
||||||
gl_FragColor.a = 1.0;
|
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|||||||
216
src/nodes/WebGL/KernelPreset.ts
Normal file
216
src/nodes/WebGL/KernelPreset.ts
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
import WebGL from './WebGl';
|
||||||
|
import {
|
||||||
|
ImageVar,
|
||||||
|
NumberVar,
|
||||||
|
StringVar
|
||||||
|
} from '../io/AbstractIOSet';
|
||||||
|
|
||||||
|
const kernels = {
|
||||||
|
'sobel': {
|
||||||
|
size: 3,
|
||||||
|
kernel: [
|
||||||
|
-1.0, -2.0, -1.0,
|
||||||
|
0.0, 0.0, 0.0,
|
||||||
|
1.0, 2.0, 1.0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'laplacian': {
|
||||||
|
size: 3,
|
||||||
|
kernel: [
|
||||||
|
0.0, 1.0, 0.0,
|
||||||
|
1.0, -4.0, 1.0,
|
||||||
|
0.0, 1.0, 0.0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'laplacianOfGaussian': {
|
||||||
|
size: 5,
|
||||||
|
kernel: [
|
||||||
|
0.0, 0.0, -1.0, 0.0, 0.0,
|
||||||
|
0.0, -1.0, -2.0, -1.0, 0.0,
|
||||||
|
-1.0, -2.0, 16.0, -2.0, -1.0,
|
||||||
|
0.0, -1.0, -2.0, -1.0, 0.0,
|
||||||
|
0.0, 0.0, -1.0, 0.0, 0.0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'emboss': {
|
||||||
|
size: 3,
|
||||||
|
kernel: [
|
||||||
|
-2.0, -1.0, 0.0,
|
||||||
|
-1.0, 1.0, 1.0,
|
||||||
|
0.0, 1.0, 2.0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'sharpen': {
|
||||||
|
size: 3,
|
||||||
|
kernel: [
|
||||||
|
0.0, -1.0, 0.0,
|
||||||
|
-1.0, 5.0, -1.0,
|
||||||
|
0.0, -1.0, 0.0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'boxBlur': {
|
||||||
|
size: 3,
|
||||||
|
kernel: [
|
||||||
|
1.0/9.0, 1.0/9.0, 1.0/9.0,
|
||||||
|
1.0/9.0, 1.0/9.0, 1.0/9.0,
|
||||||
|
1.0/9.0, 1.0/9.0, 1.0/9.0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'outline': {
|
||||||
|
size: 3,
|
||||||
|
kernel: [
|
||||||
|
-2.0, -1.0, 0.0,
|
||||||
|
-1.0, 1.0, 1.0,
|
||||||
|
0.0, 1.0, 2.0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'erosion': {
|
||||||
|
size: 3,
|
||||||
|
kernel: [
|
||||||
|
0.0, 1.0, 0.0,
|
||||||
|
1.0, 1.0, 1.0,
|
||||||
|
0.0, 1.0, 0.0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'dilation': {
|
||||||
|
size: 3,
|
||||||
|
kernel: [
|
||||||
|
1.0, 1.0, 1.0,
|
||||||
|
1.0, 0.0, 1.0,
|
||||||
|
1.0, 1.0, 1.0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'unsharpMask': {
|
||||||
|
size: 5,
|
||||||
|
kernel: [
|
||||||
|
-1.0/256.0, -4.0/256.0, -6.0/256.0, -4.0/256.0, -1.0/256.0,
|
||||||
|
-4.0/256.0, 1.0/256.0, 13.0/256.0, 1.0/256.0, -4.0/256.0,
|
||||||
|
-6.0/256.0, 13.0/256.0, 269.0/256.0, 13.0/256.0, -6.0/256.0,
|
||||||
|
-4.0/256.0, 1.0/256.0, 13.0/256.0, 1.0/256.0, -4.0/256.0,
|
||||||
|
-1.0/256.0, -4.0/256.0, -6.0/256.0, -4.0/256.0, -1.0/256.0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const inputs = {
|
||||||
|
image: {
|
||||||
|
type: 'Image',
|
||||||
|
} as ImageVar,
|
||||||
|
preset: {
|
||||||
|
type: 'String',
|
||||||
|
default: 'sobel',
|
||||||
|
enum: Object.keys(kernels),
|
||||||
|
} as StringVar,
|
||||||
|
passes: {
|
||||||
|
type: 'Number',
|
||||||
|
default: 1,
|
||||||
|
} as NumberVar,
|
||||||
|
multiplier: {
|
||||||
|
type: 'Number',
|
||||||
|
default: 1,
|
||||||
|
} as NumberVar,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const outputs = {
|
||||||
|
image: {
|
||||||
|
type: 'Image',
|
||||||
|
} as ImageVar,
|
||||||
|
width: {
|
||||||
|
type: 'Number',
|
||||||
|
} as NumberVar,
|
||||||
|
height: {
|
||||||
|
type: 'Number',
|
||||||
|
} as NumberVar
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class KernelPreset extends WebGL<typeof inputs> {
|
||||||
|
|
||||||
|
_lastKernelSize?: number;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super('KernelPreset', inputs)
|
||||||
|
}
|
||||||
|
|
||||||
|
_passes() {
|
||||||
|
|
||||||
|
const passes = Array(Math.max(this.in.passes.value, 0))
|
||||||
|
.fill(null)
|
||||||
|
.flatMap((x, i) => {
|
||||||
|
return [
|
||||||
|
(gl, program) => {
|
||||||
|
if (i === 0) {
|
||||||
|
this._setParams(gl, program)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
return passes;
|
||||||
|
}
|
||||||
|
|
||||||
|
get currentPreset() {
|
||||||
|
return kernels[this.in.preset.value];
|
||||||
|
}
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
if (this.currentPreset.size !== this._lastKernelSize) {
|
||||||
|
this.program = undefined;
|
||||||
|
}
|
||||||
|
super.setup();
|
||||||
|
this._lastKernelSize = this.currentPreset.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
get frag() {
|
||||||
|
const KERNEL_SIZE = Math.ceil(this.currentPreset.size);
|
||||||
|
const shader = `
|
||||||
|
precision mediump float;
|
||||||
|
|
||||||
|
// our texture
|
||||||
|
uniform sampler2D u_image;
|
||||||
|
uniform vec2 u_textureSize;
|
||||||
|
uniform float u_kernel[${KERNEL_SIZE * KERNEL_SIZE}];
|
||||||
|
|
||||||
|
// the texCoords passed in from the vertex shader.
|
||||||
|
varying vec2 v_texCoord;
|
||||||
|
|
||||||
|
const int RADIUS = ${KERNEL_SIZE};
|
||||||
|
|
||||||
|
vec4 applyKernel(in vec2 texCoord) {
|
||||||
|
vec2 onePixel = vec2(1.0, 1.0) / u_textureSize;
|
||||||
|
vec4 colorSum = vec4(0.0);
|
||||||
|
|
||||||
|
for (int y = 0; y < RADIUS; y++) {
|
||||||
|
for (int x = 0; x < RADIUS; x++) {
|
||||||
|
vec2 offset = vec2(float(x - RADIUS/2), float(y - RADIUS/2));
|
||||||
|
colorSum += texture2D(u_image, texCoord + onePixel * offset) * u_kernel[y * RADIUS + x];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return colorSum;
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
gl_FragColor = applyKernel(v_texCoord);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
console.log(shader);
|
||||||
|
return shader;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {WebGLRenderingContext} gl
|
||||||
|
* @param {*} program
|
||||||
|
*/
|
||||||
|
_setParams(gl, program) {
|
||||||
|
const kernel = this.currentPreset.kernel;
|
||||||
|
if (kernel) {
|
||||||
|
gl.uniform1fv(
|
||||||
|
gl.getUniformLocation(program, 'u_kernel'),
|
||||||
|
kernel.map((x) => x * this.in.multiplier.value)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ export {default as ColorLookupTable} from './WebGL/ColorLookupTable';
|
|||||||
export {default as Vignette} from './WebGL/Vignette';
|
export {default as Vignette} from './WebGL/Vignette';
|
||||||
export {default as Blur} from './WebGL/Blur';
|
export {default as Blur} from './WebGL/Blur';
|
||||||
export {default as Edge} from './WebGL/Edge';
|
export {default as Edge} from './WebGL/Edge';
|
||||||
|
export {default as KernelPreset} from './WebGL/KernelPreset';
|
||||||
// Math
|
// Math
|
||||||
export {default as Numbers} from './Math/Numbers';
|
export {default as Numbers} from './Math/Numbers';
|
||||||
export {default as NumberBinaryOperation} from './Math/NumberBinaryOperation';
|
export {default as NumberBinaryOperation} from './Math/NumberBinaryOperation';
|
||||||
|
|||||||
Reference in New Issue
Block a user