From bef106de9fa827d983fa319cecdf688c2822efb9 Mon Sep 17 00:00:00 2001 From: Jules Laplace Date: Wed, 22 Apr 2015 05:21:11 -0400 Subject: smartblur convolution experiment --- convolve.html | 67 ++++++++++++++++++++++ convolve.js | 79 ++++++++++++++++++++++++++ fetch.js | 30 ++++++++++ img/orange-01.jpg | Bin 0 -> 20218 bytes img/oranges.jpg | Bin 0 -> 16277 bytes smartblur.java | 162 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ smartblur.js | 138 ++++++++++++++++++++++++++++++++++++++++++++++ test.html | 21 +++++++ webcam.html | 55 ++++++++++++++++++ 9 files changed, 552 insertions(+) create mode 100644 convolve.html create mode 100644 convolve.js create mode 100644 fetch.js create mode 100644 img/orange-01.jpg create mode 100644 img/oranges.jpg create mode 100644 smartblur.java create mode 100644 smartblur.js create mode 100644 test.html create mode 100644 webcam.html diff --git a/convolve.html b/convolve.html new file mode 100644 index 0000000..24e3590 --- /dev/null +++ b/convolve.html @@ -0,0 +1,67 @@ + + + + + + diff --git a/convolve.js b/convolve.js new file mode 100644 index 0000000..2e59c6c --- /dev/null +++ b/convolve.js @@ -0,0 +1,79 @@ +Kernel = function (w, h, a) { + this.w = w + this.h = h + this.a = a +} +Kernel.prototype.normalize = function(){ + var n = 0; + for (var i = 0; i < this.a.length; i++) + n += this.a[i]; + for (var i = 0; i < this.a.length; i++) + this.a[i] /= n; + return this +} +ConvolveOp = function (kernel, edge_zero_fill, idk) { + var k = kernel.a + var klen = k.length + var kw = kernel.w + var kh = kernel.h + var kx = -((kw / 2)|0) + var ky = -((kh / 2)|0) + this.filter = function(srcImageData, destImageData){ + // assume these are both identically sized canvas objects.. + var src = srcImageData.data + var dest = destImageData.data + var w = srcImageData.width + var h = srcImageData.height + var ix, iy, it, jx, jy, jt + var r, g, b, a + + for (var i = 0, len = w*h; i < len; i++) { + ix = i%w + iy = (i/w)|0 + it = i * 4 + r = g = b = a = 0 + for (var j = 0; j < klen; j++) { + jx = ix + j%kw + kx + jy = iy + ((j/kh)|0) + ky + jt = 4 * (jx + jy*w) + if (0 > jx || jx > w || 0 > jy || jy > h) { + if (edge_zero_fill) { + continue + } + jt = it + } + r += src[jt] * k[j] + g += src[jt+1] * k[j] + b += src[jt+2] * k[j] + a += src[jt+3] * k[j] + } + dest[it] = r + dest[it+1] = g + dest[it+2] = b + dest[it+3] = a + } + return destImageData + } + this.convolveCanvas = function(image){ + var target = document.createElement("canvas") + var w = target.width = image.width + var h = target.height = image.height + var imagectx = image.getContext('2d') + var targetctx = target.getContext('2d') + + targetctx.drawImage(image, 0, 0); + + // get source pixels + var src = imagectx.getImageData(0,0,w,h) + var dest = targetctx.getImageData(0,0,w,h) + + // convolve the cloned image + dest = this.filter(src, dest); + + targetctx.putImageData(dest, 0,0) + + return target + } +} +ConvolveOp.prototype.EDGE_NO_OP = 0 +ConvolveOp.prototype.EDGE_ZERO_FILL = 1 diff --git a/fetch.js b/fetch.js new file mode 100644 index 0000000..c6e0cdb --- /dev/null +++ b/fetch.js @@ -0,0 +1,30 @@ +function getNaturalDimensions (img) { + if (img.naturalWidth) { + return { naturalWidth: img.naturalWidth, naturalHeight: img.naturalHeight } + } + if (img.videoWidth) { + return { naturalWidth: img.videoWidth, naturalHeight: img.videoHeight } + } + return { naturalWidth: img.width, naturalHeight: img.height } +} +function fromImage (url, cb) { + var loaded = false + var img = new Image () + img.onload = function(){ + if (loaded) return + loaded = true + fromCanvas(img, cb) + } + if (img.src == url) { return img.onload() } + img.src = url + if (img.complete) { return img.onload() } +} +function fromCanvas (img, cb) { + var canvas = document.createElement("canvas") + var ctx = canvas.getContext('2d') + var dims = getNaturalDimensions(img) + canvas.width = dims.naturalWidth + canvas.height = dims.naturalHeight + ctx.drawImage(img,0,0,dims.naturalWidth,dims.naturalHeight,0,0,canvas.width,canvas.height) + cb(canvas) +} diff --git a/img/orange-01.jpg b/img/orange-01.jpg new file mode 100644 index 0000000..735c0d8 Binary files /dev/null and b/img/orange-01.jpg differ diff --git a/img/oranges.jpg b/img/oranges.jpg new file mode 100644 index 0000000..443f157 Binary files /dev/null and b/img/oranges.jpg differ diff --git a/smartblur.java b/smartblur.java new file mode 100644 index 0000000..f62f2fa --- /dev/null +++ b/smartblur.java @@ -0,0 +1,162 @@ +import java.awt.image.Kernel; +import java.awt.image.BufferedImage; +import java.awt.image.ConvolveOp; +import java.awt.Graphics; + +// http://asserttrue.blogspot.ca/2010/08/implementing-smart-blur-in-java.html +public class SmartBlurFilter +{ + double SENSITIVITY = 10; + int REGION_SIZE = 5; + + float[] kernelArray = + { + 1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1 + }; + + Kernel kernel = new Kernel(9, 9, normalizeKernel(kernelArray)); + + float[] normalizeKernel(float[] ar) + { + int n = 0; + for (int i = 0; i < ar.length; i++) + n += ar[i]; + for (int i = 0; i < ar.length; i++) + ar[i] /= n; + + return ar; + } + + public double lerp(double a,double b, double amt) + { + return a + amt * (b - a); + } + + public double getLerpAmount(double a, double cutoff) + { + if (a > cutoff) + return 1.0; + + return a / cutoff; + } + + public double rmsError(int[] pixels) + { + double ave = 0; + + for (int i = 0; i < pixels.length; i++) + ave += (pixels[ i ] >> 8) & 255; + + ave /= pixels.length; + + double diff = 0; + double accumulator = 0; + + for (int i = 0; i < pixels.length; i++) + { + diff = ((pixels[ i ] >> 8) & 255) - ave; + diff *= diff; + accumulator += diff; + } + + double rms = accumulator / pixels.length; + + rms = Math.sqrt(rms); + + return rms; + } + + int[] getSample(BufferedImage image, int x, int y, int size) + { + int[] pixels = {}; + + try + { + BufferedImage subimage = image.getSubimage(x,y, size, size); + pixels = subimage.getRGB(0,0,size,size,null,0,size); + } + catch(Exception e) + { + // will arrive here if we requested + // pixels outside the image bounds + } + return pixels; + } + + int lerpPixel(int oldpixel, int newpixel, double amt) + { + int oldRed = (oldpixel >> 16) & 255; + int newRed = (newpixel >> 16) & 255; + int red = (int) lerp((double)oldRed, (double)newRed, amt) & 255; + + int oldGreen = (oldpixel >> 8) & 255; + int newGreen = (newpixel >> 8) & 255; + int green = (int) lerp((double)oldGreen, (double)newGreen, amt) & 255; + + int oldBlue = oldpixel & 255; + int newBlue = newpixel & 255; + int blue = (int) lerp((double)oldBlue, (double)newBlue, amt) & 255; + + return (red << 16) | (green << 8) | blue; + } + + int[] blurImage(BufferedImage image, int[] orig, int[] blur, double sensitivity) + { + int newPixel = 0; + double amt = 0; + int size = REGION_SIZE; + + for (int i = 0; i < orig.length; i++) + { + int w = image.getWidth(); + int[] pix = getSample(image, i % w, i / w, size); + if (pix.length == 0) + continue; + + amt = getLerpAmount (rmsError(pix), sensitivity); + newPixel = lerpPixel(blur[ i ], orig[ i ], amt); + orig[ i ] = newPixel; + } + + return orig; + } + + + public BufferedImage filter(BufferedImage image) + { + ConvolveOp convolver = new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, null); + + // clone image into target + BufferedImage target = new BufferedImage(image.getWidth(), image.getHeight(), image.getType()); + Graphics g = target.createGraphics(); + g.drawImage(image, 0, 0, null); + g.dispose(); + + int w = target.getWidth(); + int h = target.getHeight(); + + // get source pixels + int[] pixels = image.getRGB(0, 0, w, h, null, 0, w); + + // blur the cloned image + target = convolver.filter(target, image); + + // get the blurred pixels + int[] blurryPixels = target.getRGB(0, 0, w, h, null, 0, w); + + // go thru the image and interpolate values + pixels = blurImage(image, pixels, blurryPixels, SENSITIVITY); + + // replace original pixels with new ones + image.setRGB(0, 0, w, h, pixels, 0, w); + return image; + } +} \ No newline at end of file diff --git a/smartblur.js b/smartblur.js new file mode 100644 index 0000000..181b1b4 --- /dev/null +++ b/smartblur.js @@ -0,0 +1,138 @@ + +// http://asserttrue.blogspot.ca/2010/08/implementing-smart-blur-in-java.html +SmartBlurFilter = function () { + var SENSITIVITY = 10 + var REGION_SIZE = 5 + var edge_zero_fill = true + + var kx = -((REGION_SIZE/2)|0), ky = -((REGION_SIZE/2)|0), klen = REGION_SIZE*REGION_SIZE + + var kernelArray = [ + 1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1 + ] + + var kernel = new Kernel(9, 9, kernelArray) + kernel.normalize() + + function lerp(a,b,amt) { + return a + amt * (b - a); + } + + function rmsError(src, i, size) { + var ave = 0; + + var len = size*size, data = src.data + + var ix, iy + var r,g,b,a + var w = src.width, h = src.height + ix = i%w + iy = (i/w)|0 + it = i * 4 + r = g = b = a = 0 + for (var j = 0; j < klen; j++) { + jx = ix + j%size + kx + jy = iy + ((j/size)|0) + ky + jt = 4 * (jx + jy*w) + if (0 > jx || jx > w || 0 > jy || jy > h) { + if (edge_zero_fill) { + continue + } + jt = it + } + r += data[jt] +// g += data[jt+1] +// b += data[jt+2] +// a += data[jt+3] + } + r /= klen +// g /= klen +// b /= klen +// a /= klen + + var diff = 0 + var accumulator = 0 + + for (var j = 0; j < klen; j++) { + jx = ix + j%size + kx + jy = iy + ((j/size)|0) + ky + jt = 4 * (jx + jy*w) + if (0 > jx || jx > w || 0 > jy || jy > h) { + if (edge_zero_fill) { + continue + } + jt = it + } + diff = data[ jt ] - ave + diff *= diff + accumulator += diff + } + + var rms = accumulator / klen + rms = Math.sqrt(rms) / 255 + return rms + } + + function lerpPixel( src, dest, blur, i, amt) { + dest[i] = lerp(src[i], blur[i], amt) + dest[i+1] = lerp(src[i+1], blur[i+1], amt) + dest[i+2] = lerp(src[i+2], blur[i+2], amt) + dest[i+3] = lerp(src[i+3], blur[i+3], amt) + } + + function blurImage( src, dest, blur, sensitivity ) { + var newPixel = 0 + var amt = 0 + var size = REGION_SIZE + var w = src.width, rms + + var srcData = src.data + var destData = dest.data + var blurData = blur.data + for (var i = 0, len = srcData.length/4; i < len; i++) { + + rms = rmsError(src, i, size) + +// amt = rms < sensitivity ? rms/sensitivity : 1.0 + lerpPixel( srcData, destData, blurData, i*4, rms) + } + + return dest + } + + this.blur = function (src_canvas, dest_canvas) { + var convolver = new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, null); + + // clone image into target + if (! dest_canvas) dest_canvas = document.createElement("canvas") + var w = dest_canvas.width = src_canvas.width + var h = dest_canvas.height = src_canvas.height + var srcctx = src_canvas.getContext('2d') + var destctx = dest_canvas.getContext('2d') + + destctx.drawImage(src_canvas, 0, 0); + + // get source pixels + var src = srcctx.getImageData(0,0,w,h) + var dest = destctx.getImageData(0,0,w,h) + + // blur the cloned image + dest = convolver.filter(src, dest); + + destctx.putImageData(dest, 0,0) + var blurred = destctx.getImageData(0,0,w,h) + var dest = srcctx.getImageData(0,0,w,h) + dest = blurImage(src, dest, blurred, SENSITIVITY) + destctx.putImageData(dest, 0,0) + + return dest_canvas + } +} \ No newline at end of file diff --git a/test.html b/test.html new file mode 100644 index 0000000..38aac26 --- /dev/null +++ b/test.html @@ -0,0 +1,21 @@ + + + + + + + diff --git a/webcam.html b/webcam.html new file mode 100644 index 0000000..a2b9519 --- /dev/null +++ b/webcam.html @@ -0,0 +1,55 @@ + + + + + + + -- cgit v1.2.3-70-g09d2