UNPKG

jsfeat

Version:

JavaScript Computer Vision library

1,168 lines (1,054 loc) 53.8 kB
/** * @author Eugene Zatepyakin / http://inspirit.ru/ */ (function(global) { "use strict"; // var imgproc = (function() { var _resample_u8 = function(src, dst, nw, nh) { var xofs_count=0; var ch=src.channel,w=src.cols,h=src.rows; var src_d=src.data,dst_d=dst.data; var scale_x = w / nw, scale_y = h / nh; var inv_scale_256 = (scale_x * scale_y * 0x10000)|0; var dx=0,dy=0,sx=0,sy=0,sx1=0,sx2=0,i=0,k=0,fsx1=0.0,fsx2=0.0; var a=0,b=0,dxn=0,alpha=0,beta=0,beta1=0; var buf_node = jsfeat.cache.get_buffer((nw*ch)<<2); var sum_node = jsfeat.cache.get_buffer((nw*ch)<<2); var xofs_node = jsfeat.cache.get_buffer((w*2*3)<<2); var buf = buf_node.i32; var sum = sum_node.i32; var xofs = xofs_node.i32; for (; dx < nw; dx++) { fsx1 = dx * scale_x, fsx2 = fsx1 + scale_x; sx1 = (fsx1 + 1.0 - 1e-6)|0, sx2 = fsx2|0; sx1 = Math.min(sx1, w - 1); sx2 = Math.min(sx2, w - 1); if(sx1 > fsx1) { xofs[k++] = (dx * ch)|0; xofs[k++] = ((sx1 - 1)*ch)|0; xofs[k++] = ((sx1 - fsx1) * 0x100)|0; xofs_count++; } for(sx = sx1; sx < sx2; sx++){ xofs_count++; xofs[k++] = (dx * ch)|0; xofs[k++] = (sx * ch)|0; xofs[k++] = 256; } if(fsx2 - sx2 > 1e-3) { xofs_count++; xofs[k++] = (dx * ch)|0; xofs[k++] = (sx2 * ch)|0; xofs[k++] = ((fsx2 - sx2) * 256)|0; } } for (dx = 0; dx < nw * ch; dx++) { buf[dx] = sum[dx] = 0; } dy = 0; for (sy = 0; sy < h; sy++) { a = w * sy; for (k = 0; k < xofs_count; k++) { dxn = xofs[k*3]; sx1 = xofs[k*3+1]; alpha = xofs[k*3+2]; for (i = 0; i < ch; i++) { buf[dxn + i] += src_d[a+sx1+i] * alpha; } } if ((dy + 1) * scale_y <= sy + 1 || sy == h - 1) { beta = (Math.max(sy + 1 - (dy + 1) * scale_y, 0.0) * 256)|0; beta1 = 256 - beta; b = nw * dy; if (beta <= 0) { for (dx = 0; dx < nw * ch; dx++) { dst_d[b+dx] = Math.min(Math.max((sum[dx] + buf[dx] * 256) / inv_scale_256, 0), 255); sum[dx] = buf[dx] = 0; } } else { for (dx = 0; dx < nw * ch; dx++) { dst_d[b+dx] = Math.min(Math.max((sum[dx] + buf[dx] * beta1) / inv_scale_256, 0), 255); sum[dx] = buf[dx] * beta; buf[dx] = 0; } } dy++; } else { for(dx = 0; dx < nw * ch; dx++) { sum[dx] += buf[dx] * 256; buf[dx] = 0; } } } jsfeat.cache.put_buffer(sum_node); jsfeat.cache.put_buffer(buf_node); jsfeat.cache.put_buffer(xofs_node); } var _resample = function(src, dst, nw, nh) { var xofs_count=0; var ch=src.channel,w=src.cols,h=src.rows; var src_d=src.data,dst_d=dst.data; var scale_x = w / nw, scale_y = h / nh; var scale = 1.0 / (scale_x * scale_y); var dx=0,dy=0,sx=0,sy=0,sx1=0,sx2=0,i=0,k=0,fsx1=0.0,fsx2=0.0; var a=0,b=0,dxn=0,alpha=0.0,beta=0.0,beta1=0.0; var buf_node = jsfeat.cache.get_buffer((nw*ch)<<2); var sum_node = jsfeat.cache.get_buffer((nw*ch)<<2); var xofs_node = jsfeat.cache.get_buffer((w*2*3)<<2); var buf = buf_node.f32; var sum = sum_node.f32; var xofs = xofs_node.f32; for (; dx < nw; dx++) { fsx1 = dx * scale_x, fsx2 = fsx1 + scale_x; sx1 = (fsx1 + 1.0 - 1e-6)|0, sx2 = fsx2|0; sx1 = Math.min(sx1, w - 1); sx2 = Math.min(sx2, w - 1); if(sx1 > fsx1) { xofs_count++; xofs[k++] = ((sx1 - 1)*ch)|0; xofs[k++] = (dx * ch)|0; xofs[k++] = (sx1 - fsx1) * scale; } for(sx = sx1; sx < sx2; sx++){ xofs_count++; xofs[k++] = (sx * ch)|0; xofs[k++] = (dx * ch)|0; xofs[k++] = scale; } if(fsx2 - sx2 > 1e-3) { xofs_count++; xofs[k++] = (sx2 * ch)|0; xofs[k++] = (dx * ch)|0; xofs[k++] = (fsx2 - sx2) * scale; } } for (dx = 0; dx < nw * ch; dx++) { buf[dx] = sum[dx] = 0; } dy = 0; for (sy = 0; sy < h; sy++) { a = w * sy; for (k = 0; k < xofs_count; k++) { sx1 = xofs[k*3]|0; dxn = xofs[k*3+1]|0; alpha = xofs[k*3+2]; for (i = 0; i < ch; i++) { buf[dxn + i] += src_d[a+sx1+i] * alpha; } } if ((dy + 1) * scale_y <= sy + 1 || sy == h - 1) { beta = Math.max(sy + 1 - (dy + 1) * scale_y, 0.0); beta1 = 1.0 - beta; b = nw * dy; if (Math.abs(beta) < 1e-3) { for (dx = 0; dx < nw * ch; dx++) { dst_d[b+dx] = sum[dx] + buf[dx]; sum[dx] = buf[dx] = 0; } } else { for (dx = 0; dx < nw * ch; dx++) { dst_d[b+dx] = sum[dx] + buf[dx] * beta1; sum[dx] = buf[dx] * beta; buf[dx] = 0; } } dy++; } else { for(dx = 0; dx < nw * ch; dx++) { sum[dx] += buf[dx]; buf[dx] = 0; } } } jsfeat.cache.put_buffer(sum_node); jsfeat.cache.put_buffer(buf_node); jsfeat.cache.put_buffer(xofs_node); } var _convol_u8 = function(buf, src_d, dst_d, w, h, filter, kernel_size, half_kernel) { var i=0,j=0,k=0,sp=0,dp=0,sum=0,sum1=0,sum2=0,sum3=0,f0=filter[0],fk=0; var w2=w<<1,w3=w*3,w4=w<<2; // hor pass for (; i < h; ++i) { sum = src_d[sp]; for (j = 0; j < half_kernel; ++j) { buf[j] = sum; } for (j = 0; j <= w-2; j+=2) { buf[j + half_kernel] = src_d[sp+j]; buf[j + half_kernel+1] = src_d[sp+j+1]; } for (; j < w; ++j) { buf[j + half_kernel] = src_d[sp+j]; } sum = src_d[sp+w-1]; for (j = w; j < half_kernel + w; ++j) { buf[j + half_kernel] = sum; } for (j = 0; j <= w-4; j+=4) { sum = buf[j] * f0, sum1 = buf[j+1] * f0, sum2 = buf[j+2] * f0, sum3 = buf[j+3] * f0; for (k = 1; k < kernel_size; ++k) { fk = filter[k]; sum += buf[k + j] * fk; sum1 += buf[k + j+1] * fk; sum2 += buf[k + j+2] * fk; sum3 += buf[k + j+3] * fk; } dst_d[dp+j] = Math.min(sum >> 8, 255); dst_d[dp+j+1] = Math.min(sum1 >> 8, 255); dst_d[dp+j+2] = Math.min(sum2 >> 8, 255); dst_d[dp+j+3] = Math.min(sum3 >> 8, 255); } for (; j < w; ++j) { sum = buf[j] * f0; for (k = 1; k < kernel_size; ++k) { sum += buf[k + j] * filter[k]; } dst_d[dp+j] = Math.min(sum >> 8, 255); } sp += w; dp += w; } // vert pass for (i = 0; i < w; ++i) { sum = dst_d[i]; for (j = 0; j < half_kernel; ++j) { buf[j] = sum; } k = i; for (j = 0; j <= h-2; j+=2, k+=w2) { buf[j+half_kernel] = dst_d[k]; buf[j+half_kernel+1] = dst_d[k+w]; } for (; j < h; ++j, k+=w) { buf[j+half_kernel] = dst_d[k]; } sum = dst_d[(h-1)*w + i]; for (j = h; j < half_kernel + h; ++j) { buf[j + half_kernel] = sum; } dp = i; for (j = 0; j <= h-4; j+=4, dp+=w4) { sum = buf[j] * f0, sum1 = buf[j+1] * f0, sum2 = buf[j+2] * f0, sum3 = buf[j+3] * f0; for (k = 1; k < kernel_size; ++k) { fk = filter[k]; sum += buf[k + j] * fk; sum1 += buf[k + j+1] * fk; sum2 += buf[k + j+2] * fk; sum3 += buf[k + j+3] * fk; } dst_d[dp] = Math.min(sum >> 8, 255); dst_d[dp+w] = Math.min(sum1 >> 8, 255); dst_d[dp+w2] = Math.min(sum2 >> 8, 255); dst_d[dp+w3] = Math.min(sum3 >> 8, 255); } for (; j < h; ++j, dp+=w) { sum = buf[j] * f0; for (k = 1; k < kernel_size; ++k) { sum += buf[k + j] * filter[k]; } dst_d[dp] = Math.min(sum >> 8, 255); } } } var _convol = function(buf, src_d, dst_d, w, h, filter, kernel_size, half_kernel) { var i=0,j=0,k=0,sp=0,dp=0,sum=0.0,sum1=0.0,sum2=0.0,sum3=0.0,f0=filter[0],fk=0.0; var w2=w<<1,w3=w*3,w4=w<<2; // hor pass for (; i < h; ++i) { sum = src_d[sp]; for (j = 0; j < half_kernel; ++j) { buf[j] = sum; } for (j = 0; j <= w-2; j+=2) { buf[j + half_kernel] = src_d[sp+j]; buf[j + half_kernel+1] = src_d[sp+j+1]; } for (; j < w; ++j) { buf[j + half_kernel] = src_d[sp+j]; } sum = src_d[sp+w-1]; for (j = w; j < half_kernel + w; ++j) { buf[j + half_kernel] = sum; } for (j = 0; j <= w-4; j+=4) { sum = buf[j] * f0, sum1 = buf[j+1] * f0, sum2 = buf[j+2] * f0, sum3 = buf[j+3] * f0; for (k = 1; k < kernel_size; ++k) { fk = filter[k]; sum += buf[k + j] * fk; sum1 += buf[k + j+1] * fk; sum2 += buf[k + j+2] * fk; sum3 += buf[k + j+3] * fk; } dst_d[dp+j] = sum; dst_d[dp+j+1] = sum1; dst_d[dp+j+2] = sum2; dst_d[dp+j+3] = sum3; } for (; j < w; ++j) { sum = buf[j] * f0; for (k = 1; k < kernel_size; ++k) { sum += buf[k + j] * filter[k]; } dst_d[dp+j] = sum; } sp += w; dp += w; } // vert pass for (i = 0; i < w; ++i) { sum = dst_d[i]; for (j = 0; j < half_kernel; ++j) { buf[j] = sum; } k = i; for (j = 0; j <= h-2; j+=2, k+=w2) { buf[j+half_kernel] = dst_d[k]; buf[j+half_kernel+1] = dst_d[k+w]; } for (; j < h; ++j, k+=w) { buf[j+half_kernel] = dst_d[k]; } sum = dst_d[(h-1)*w + i]; for (j = h; j < half_kernel + h; ++j) { buf[j + half_kernel] = sum; } dp = i; for (j = 0; j <= h-4; j+=4, dp+=w4) { sum = buf[j] * f0, sum1 = buf[j+1] * f0, sum2 = buf[j+2] * f0, sum3 = buf[j+3] * f0; for (k = 1; k < kernel_size; ++k) { fk = filter[k]; sum += buf[k + j] * fk; sum1 += buf[k + j+1] * fk; sum2 += buf[k + j+2] * fk; sum3 += buf[k + j+3] * fk; } dst_d[dp] = sum; dst_d[dp+w] = sum1; dst_d[dp+w2] = sum2; dst_d[dp+w3] = sum3; } for (; j < h; ++j, dp+=w) { sum = buf[j] * f0; for (k = 1; k < kernel_size; ++k) { sum += buf[k + j] * filter[k]; } dst_d[dp] = sum; } } } return { // TODO: add support for RGB/BGR order // for raw arrays grayscale: function(src, w, h, dst, code) { // this is default image data representation in browser if (typeof code === "undefined") { code = jsfeat.COLOR_RGBA2GRAY; } var x=0, y=0, i=0, j=0, ir=0,jr=0; var coeff_r = 4899, coeff_g = 9617, coeff_b = 1868, cn = 4; if(code == jsfeat.COLOR_BGRA2GRAY || code == jsfeat.COLOR_BGR2GRAY) { coeff_r = 1868; coeff_b = 4899; } if(code == jsfeat.COLOR_RGB2GRAY || code == jsfeat.COLOR_BGR2GRAY) { cn = 3; } var cn2 = cn<<1, cn3 = (cn*3)|0; dst.resize(w, h, 1); var dst_u8 = dst.data; for(y = 0; y < h; ++y, j+=w, i+=w*cn) { for(x = 0, ir = i, jr = j; x <= w-4; x+=4, ir+=cn<<2, jr+=4) { dst_u8[jr] = (src[ir] * coeff_r + src[ir+1] * coeff_g + src[ir+2] * coeff_b + 8192) >> 14; dst_u8[jr + 1] = (src[ir+cn] * coeff_r + src[ir+cn+1] * coeff_g + src[ir+cn+2] * coeff_b + 8192) >> 14; dst_u8[jr + 2] = (src[ir+cn2] * coeff_r + src[ir+cn2+1] * coeff_g + src[ir+cn2+2] * coeff_b + 8192) >> 14; dst_u8[jr + 3] = (src[ir+cn3] * coeff_r + src[ir+cn3+1] * coeff_g + src[ir+cn3+2] * coeff_b + 8192) >> 14; } for (; x < w; ++x, ++jr, ir+=cn) { dst_u8[jr] = (src[ir] * coeff_r + src[ir+1] * coeff_g + src[ir+2] * coeff_b + 8192) >> 14; } } }, // derived from CCV library resample: function(src, dst, nw, nh) { var h=src.rows,w=src.cols; if (h > nh && w > nw) { dst.resize(nw, nh, src.channel); // using the fast alternative (fix point scale, 0x100 to avoid overflow) if (src.type&jsfeat.U8_t && dst.type&jsfeat.U8_t && h * w / (nh * nw) < 0x100) { _resample_u8(src, dst, nw, nh); } else { _resample(src, dst, nw, nh); } } }, box_blur_gray: function(src, dst, radius, options) { if (typeof options === "undefined") { options = 0; } var w=src.cols, h=src.rows, h2=h<<1, w2=w<<1; var i=0,x=0,y=0,end=0; var windowSize = ((radius << 1) + 1)|0; var radiusPlusOne = (radius + 1)|0, radiusPlus2 = (radiusPlusOne+1)|0; var scale = options&jsfeat.BOX_BLUR_NOSCALE ? 1 : (1.0 / (windowSize*windowSize)); var tmp_buff = jsfeat.cache.get_buffer((w*h)<<2); var sum=0, dstIndex=0, srcIndex = 0, nextPixelIndex=0, previousPixelIndex=0; var data_i32 = tmp_buff.i32; // to prevent overflow var data_u8 = src.data; var hold=0; dst.resize(w, h, src.channel); // first pass // no need to scale //data_u8 = src.data; //data_i32 = tmp; for (y = 0; y < h; ++y) { dstIndex = y; sum = radiusPlusOne * data_u8[srcIndex]; for(i = (srcIndex+1)|0, end=(srcIndex+radius)|0; i <= end; ++i) { sum += data_u8[i]; } nextPixelIndex = (srcIndex + radiusPlusOne)|0; previousPixelIndex = srcIndex; hold = data_u8[previousPixelIndex]; for(x = 0; x < radius; ++x, dstIndex += h) { data_i32[dstIndex] = sum; sum += data_u8[nextPixelIndex]- hold; nextPixelIndex ++; } for(; x < w-radiusPlus2; x+=2, dstIndex += h2) { data_i32[dstIndex] = sum; sum += data_u8[nextPixelIndex]- data_u8[previousPixelIndex]; data_i32[dstIndex+h] = sum; sum += data_u8[nextPixelIndex+1]- data_u8[previousPixelIndex+1]; nextPixelIndex +=2; previousPixelIndex +=2; } for(; x < w-radiusPlusOne; ++x, dstIndex += h) { data_i32[dstIndex] = sum; sum += data_u8[nextPixelIndex]- data_u8[previousPixelIndex]; nextPixelIndex ++; previousPixelIndex ++; } hold = data_u8[nextPixelIndex-1]; for(; x < w; ++x, dstIndex += h) { data_i32[dstIndex] = sum; sum += hold- data_u8[previousPixelIndex]; previousPixelIndex ++; } srcIndex += w; } // // second pass srcIndex = 0; //data_i32 = tmp; // this is a transpose data_u8 = dst.data; // dont scale result if(scale == 1) { for (y = 0; y < w; ++y) { dstIndex = y; sum = radiusPlusOne * data_i32[srcIndex]; for(i = (srcIndex+1)|0, end=(srcIndex+radius)|0; i <= end; ++i) { sum += data_i32[i]; } nextPixelIndex = srcIndex + radiusPlusOne; previousPixelIndex = srcIndex; hold = data_i32[previousPixelIndex]; for(x = 0; x < radius; ++x, dstIndex += w) { data_u8[dstIndex] = sum; sum += data_i32[nextPixelIndex]- hold; nextPixelIndex ++; } for(; x < h-radiusPlus2; x+=2, dstIndex += w2) { data_u8[dstIndex] = sum; sum += data_i32[nextPixelIndex]- data_i32[previousPixelIndex]; data_u8[dstIndex+w] = sum; sum += data_i32[nextPixelIndex+1]- data_i32[previousPixelIndex+1]; nextPixelIndex +=2; previousPixelIndex +=2; } for(; x < h-radiusPlusOne; ++x, dstIndex += w) { data_u8[dstIndex] = sum; sum += data_i32[nextPixelIndex]- data_i32[previousPixelIndex]; nextPixelIndex ++; previousPixelIndex ++; } hold = data_i32[nextPixelIndex-1]; for(; x < h; ++x, dstIndex += w) { data_u8[dstIndex] = sum; sum += hold- data_i32[previousPixelIndex]; previousPixelIndex ++; } srcIndex += h; } } else { for (y = 0; y < w; ++y) { dstIndex = y; sum = radiusPlusOne * data_i32[srcIndex]; for(i = (srcIndex+1)|0, end=(srcIndex+radius)|0; i <= end; ++i) { sum += data_i32[i]; } nextPixelIndex = srcIndex + radiusPlusOne; previousPixelIndex = srcIndex; hold = data_i32[previousPixelIndex]; for(x = 0; x < radius; ++x, dstIndex += w) { data_u8[dstIndex] = sum*scale; sum += data_i32[nextPixelIndex]- hold; nextPixelIndex ++; } for(; x < h-radiusPlus2; x+=2, dstIndex += w2) { data_u8[dstIndex] = sum*scale; sum += data_i32[nextPixelIndex]- data_i32[previousPixelIndex]; data_u8[dstIndex+w] = sum*scale; sum += data_i32[nextPixelIndex+1]- data_i32[previousPixelIndex+1]; nextPixelIndex +=2; previousPixelIndex +=2; } for(; x < h-radiusPlusOne; ++x, dstIndex += w) { data_u8[dstIndex] = sum*scale; sum += data_i32[nextPixelIndex]- data_i32[previousPixelIndex]; nextPixelIndex ++; previousPixelIndex ++; } hold = data_i32[nextPixelIndex-1]; for(; x < h; ++x, dstIndex += w) { data_u8[dstIndex] = sum*scale; sum += hold- data_i32[previousPixelIndex]; previousPixelIndex ++; } srcIndex += h; } } jsfeat.cache.put_buffer(tmp_buff); }, gaussian_blur: function(src, dst, kernel_size, sigma) { if (typeof sigma === "undefined") { sigma = 0.0; } if (typeof kernel_size === "undefined") { kernel_size = 0; } kernel_size = kernel_size == 0 ? (Math.max(1, (4.0 * sigma + 1.0 - 1e-8)) * 2 + 1)|0 : kernel_size; var half_kernel = kernel_size >> 1; var w = src.cols, h = src.rows; var data_type = src.type, is_u8 = data_type&jsfeat.U8_t; dst.resize(w, h, src.channel); var src_d = src.data, dst_d = dst.data; var buf,filter,buf_sz=(kernel_size + Math.max(h, w))|0; var buf_node = jsfeat.cache.get_buffer(buf_sz<<2); var filt_node = jsfeat.cache.get_buffer(kernel_size<<2); if(is_u8) { buf = buf_node.i32; filter = filt_node.i32; } else if(data_type&jsfeat.S32_t) { buf = buf_node.i32; filter = filt_node.f32; } else { buf = buf_node.f32; filter = filt_node.f32; } jsfeat.math.get_gaussian_kernel(kernel_size, sigma, filter, data_type); if(is_u8) { _convol_u8(buf, src_d, dst_d, w, h, filter, kernel_size, half_kernel); } else { _convol(buf, src_d, dst_d, w, h, filter, kernel_size, half_kernel); } jsfeat.cache.put_buffer(buf_node); jsfeat.cache.put_buffer(filt_node); }, hough_transform: function( img, rho_res, theta_res, threshold ) { var image = img.data; var width = img.cols; var height = img.rows; var step = width; min_theta = 0.0; max_theta = Math.PI; numangle = Math.round((max_theta - min_theta) / theta_res); numrho = Math.round(((width + height) * 2 + 1) / rho_res); irho = 1.0 / rho_res; var accum = new Int32Array((numangle+2) * (numrho+2)); //typed arrays are initialized to 0 var tabSin = new Float32Array(numangle); var tabCos = new Float32Array(numangle); var n=0; var ang = min_theta; for(; n < numangle; n++ ) { tabSin[n] = Math.sin(ang) * irho; tabCos[n] = Math.cos(ang) * irho; ang += theta_res } // stage 1. fill accumulator for( var i = 0; i < height; i++ ) { for( var j = 0; j < width; j++ ) { if( image[i * step + j] != 0 ) { //console.log(r, (n+1) * (numrho+2) + r+1, tabCos[n], tabSin[n]); for(var n = 0; n < numangle; n++ ) { var r = Math.round( j * tabCos[n] + i * tabSin[n] ); r += (numrho - 1) / 2; accum[(n+1) * (numrho+2) + r+1] += 1; } } } } // stage 2. find local maximums //TODO: Consider making a vector class that uses typed arrays _sort_buf = new Array(); for(var r = 0; r < numrho; r++ ) { for(var n = 0; n < numangle; n++ ) { var base = (n+1) * (numrho+2) + r+1; if( accum[base] > threshold && accum[base] > accum[base - 1] && accum[base] >= accum[base + 1] && accum[base] > accum[base - numrho - 2] && accum[base] >= accum[base + numrho + 2] ) { _sort_buf.push(base); } } } // stage 3. sort the detected lines by accumulator value _sort_buf.sort(function(l1, l2) { return accum[l1] > accum[l2] || (accum[l1] == accum[l2] && l1 < l2); }); // stage 4. store the first min(total,linesMax) lines to the output buffer linesMax = Math.min(numangle*numrho, _sort_buf.length); scale = 1.0 / (numrho+2); lines = new Array(); for( var i = 0; i < linesMax; i++ ) { var idx = _sort_buf[i]; var n = Math.floor(idx*scale) - 1; var r = idx - (n+1)*(numrho+2) - 1; var lrho = (r - (numrho - 1)*0.5) * rho_res; var langle = n * theta_res; lines.push([lrho, langle]); } return lines; }, // assume we always need it for u8 image pyrdown: function(src, dst, sx, sy) { // this is needed for bbf if (typeof sx === "undefined") { sx = 0; } if (typeof sy === "undefined") { sy = 0; } var w = src.cols, h = src.rows; var w2 = w >> 1, h2 = h >> 1; var _w2 = w2 - (sx << 1), _h2 = h2 - (sy << 1); var x=0,y=0,sptr=sx+sy*w,sline=0,dptr=0,dline=0; dst.resize(w2, h2, src.channel); var src_d = src.data, dst_d = dst.data; for(y = 0; y < _h2; ++y) { sline = sptr; dline = dptr; for(x = 0; x <= _w2-2; x+=2, dline+=2, sline += 4) { dst_d[dline] = (src_d[sline] + src_d[sline+1] + src_d[sline+w] + src_d[sline+w+1] + 2) >> 2; dst_d[dline+1] = (src_d[sline+2] + src_d[sline+3] + src_d[sline+w+2] + src_d[sline+w+3] + 2) >> 2; } for(; x < _w2; ++x, ++dline, sline += 2) { dst_d[dline] = (src_d[sline] + src_d[sline+1] + src_d[sline+w] + src_d[sline+w+1] + 2) >> 2; } sptr += w << 1; dptr += w2; } }, // dst: [gx,gy,...] scharr_derivatives: function(src, dst) { var w = src.cols, h = src.rows; var dstep = w<<1,x=0,y=0,x1=0,a,b,c,d,e,f; var srow0=0,srow1=0,srow2=0,drow=0; var trow0,trow1; dst.resize(w, h, 2); // 2 channel output gx, gy var img = src.data, gxgy=dst.data; var buf0_node = jsfeat.cache.get_buffer((w+2)<<2); var buf1_node = jsfeat.cache.get_buffer((w+2)<<2); if(src.type&jsfeat.U8_t || src.type&jsfeat.S32_t) { trow0 = buf0_node.i32; trow1 = buf1_node.i32; } else { trow0 = buf0_node.f32; trow1 = buf1_node.f32; } for(; y < h; ++y, srow1+=w) { srow0 = ((y > 0 ? y-1 : 1)*w)|0; srow2 = ((y < h-1 ? y+1 : h-2)*w)|0; drow = (y*dstep)|0; // do vertical convolution for(x = 0, x1 = 1; x <= w-2; x+=2, x1+=2) { a = img[srow0+x], b = img[srow2+x]; trow0[x1] = ( (a + b)*3 + (img[srow1+x])*10 ); trow1[x1] = ( b - a ); // a = img[srow0+x+1], b = img[srow2+x+1]; trow0[x1+1] = ( (a + b)*3 + (img[srow1+x+1])*10 ); trow1[x1+1] = ( b - a ); } for(; x < w; ++x, ++x1) { a = img[srow0+x], b = img[srow2+x]; trow0[x1] = ( (a + b)*3 + (img[srow1+x])*10 ); trow1[x1] = ( b - a ); } // make border x = (w + 1)|0; trow0[0] = trow0[1]; trow0[x] = trow0[w]; trow1[0] = trow1[1]; trow1[x] = trow1[w]; // do horizontal convolution, interleave the results and store them for(x = 0; x <= w-4; x+=4) { a = trow1[x+2], b = trow1[x+1], c = trow1[x+3], d = trow1[x+4], e = trow0[x+2], f = trow0[x+3]; gxgy[drow++] = ( e - trow0[x] ); gxgy[drow++] = ( (a + trow1[x])*3 + b*10 ); gxgy[drow++] = ( f - trow0[x+1] ); gxgy[drow++] = ( (c + b)*3 + a*10 ); gxgy[drow++] = ( (trow0[x+4] - e) ); gxgy[drow++] = ( ((d + a)*3 + c*10) ); gxgy[drow++] = ( (trow0[x+5] - f) ); gxgy[drow++] = ( ((trow1[x+5] + c)*3 + d*10) ); } for(; x < w; ++x) { gxgy[drow++] = ( (trow0[x+2] - trow0[x]) ); gxgy[drow++] = ( ((trow1[x+2] + trow1[x])*3 + trow1[x+1]*10) ); } } jsfeat.cache.put_buffer(buf0_node); jsfeat.cache.put_buffer(buf1_node); }, // compute gradient using Sobel kernel [1 2 1] * [-1 0 1]^T // dst: [gx,gy,...] sobel_derivatives: function(src, dst) { var w = src.cols, h = src.rows; var dstep = w<<1,x=0,y=0,x1=0,a,b,c,d,e,f; var srow0=0,srow1=0,srow2=0,drow=0; var trow0,trow1; dst.resize(w, h, 2); // 2 channel output gx, gy var img = src.data, gxgy=dst.data; var buf0_node = jsfeat.cache.get_buffer((w+2)<<2); var buf1_node = jsfeat.cache.get_buffer((w+2)<<2); if(src.type&jsfeat.U8_t || src.type&jsfeat.S32_t) { trow0 = buf0_node.i32; trow1 = buf1_node.i32; } else { trow0 = buf0_node.f32; trow1 = buf1_node.f32; } for(; y < h; ++y, srow1+=w) { srow0 = ((y > 0 ? y-1 : 1)*w)|0; srow2 = ((y < h-1 ? y+1 : h-2)*w)|0; drow = (y*dstep)|0; // do vertical convolution for(x = 0, x1 = 1; x <= w-2; x+=2, x1+=2) { a = img[srow0+x], b = img[srow2+x]; trow0[x1] = ( (a + b) + (img[srow1+x]*2) ); trow1[x1] = ( b - a ); // a = img[srow0+x+1], b = img[srow2+x+1]; trow0[x1+1] = ( (a + b) + (img[srow1+x+1]*2) ); trow1[x1+1] = ( b - a ); } for(; x < w; ++x, ++x1) { a = img[srow0+x], b = img[srow2+x]; trow0[x1] = ( (a + b) + (img[srow1+x]*2) ); trow1[x1] = ( b - a ); } // make border x = (w + 1)|0; trow0[0] = trow0[1]; trow0[x] = trow0[w]; trow1[0] = trow1[1]; trow1[x] = trow1[w]; // do horizontal convolution, interleave the results and store them for(x = 0; x <= w-4; x+=4) { a = trow1[x+2], b = trow1[x+1], c = trow1[x+3], d = trow1[x+4], e = trow0[x+2], f = trow0[x+3]; gxgy[drow++] = ( e - trow0[x] ); gxgy[drow++] = ( a + trow1[x] + b*2 ); gxgy[drow++] = ( f - trow0[x+1] ); gxgy[drow++] = ( c + b + a*2 ); gxgy[drow++] = ( trow0[x+4] - e ); gxgy[drow++] = ( d + a + c*2 ); gxgy[drow++] = ( trow0[x+5] - f ); gxgy[drow++] = ( trow1[x+5] + c + d*2 ); } for(; x < w; ++x) { gxgy[drow++] = ( trow0[x+2] - trow0[x] ); gxgy[drow++] = ( trow1[x+2] + trow1[x] + trow1[x+1]*2 ); } } jsfeat.cache.put_buffer(buf0_node); jsfeat.cache.put_buffer(buf1_node); }, // please note: // dst_(type) size should be cols = src.cols+1, rows = src.rows+1 compute_integral_image: function(src, dst_sum, dst_sqsum, dst_tilted) { var w0=src.cols|0,h0=src.rows|0,src_d=src.data; var w1=(w0+1)|0; var s=0,s2=0,p=0,pup=0,i=0,j=0,v=0,k=0; if(dst_sum && dst_sqsum) { // fill first row with zeros for(; i < w1; ++i) { dst_sum[i] = 0, dst_sqsum[i] = 0; } p = (w1+1)|0, pup = 1; for(i = 0, k = 0; i < h0; ++i, ++p, ++pup) { s = s2 = 0; for(j = 0; j <= w0-2; j+=2, k+=2, p+=2, pup+=2) { v = src_d[k]; s += v, s2 += v*v; dst_sum[p] = dst_sum[pup] + s; dst_sqsum[p] = dst_sqsum[pup] + s2; v = src_d[k+1]; s += v, s2 += v*v; dst_sum[p+1] = dst_sum[pup+1] + s; dst_sqsum[p+1] = dst_sqsum[pup+1] + s2; } for(; j < w0; ++j, ++k, ++p, ++pup) { v = src_d[k]; s += v, s2 += v*v; dst_sum[p] = dst_sum[pup] + s; dst_sqsum[p] = dst_sqsum[pup] + s2; } } } else if(dst_sum) { // fill first row with zeros for(; i < w1; ++i) { dst_sum[i] = 0; } p = (w1+1)|0, pup = 1; for(i = 0, k = 0; i < h0; ++i, ++p, ++pup) { s = 0; for(j = 0; j <= w0-2; j+=2, k+=2, p+=2, pup+=2) { s += src_d[k]; dst_sum[p] = dst_sum[pup] + s; s += src_d[k+1]; dst_sum[p+1] = dst_sum[pup+1] + s; } for(; j < w0; ++j, ++k, ++p, ++pup) { s += src_d[k]; dst_sum[p] = dst_sum[pup] + s; } } } else if(dst_sqsum) { // fill first row with zeros for(; i < w1; ++i) { dst_sqsum[i] = 0; } p = (w1+1)|0, pup = 1; for(i = 0, k = 0; i < h0; ++i, ++p, ++pup) { s2 = 0; for(j = 0; j <= w0-2; j+=2, k+=2, p+=2, pup+=2) { v = src_d[k]; s2 += v*v; dst_sqsum[p] = dst_sqsum[pup] + s2; v = src_d[k+1]; s2 += v*v; dst_sqsum[p+1] = dst_sqsum[pup+1] + s2; } for(; j < w0; ++j, ++k, ++p, ++pup) { v = src_d[k]; s2 += v*v; dst_sqsum[p] = dst_sqsum[pup] + s2; } } } if(dst_tilted) { // fill first row with zeros for(i = 0; i < w1; ++i) { dst_tilted[i] = 0; } // diagonal p = (w1+1)|0, pup = 0; for(i = 0, k = 0; i < h0; ++i, ++p, ++pup) { for(j = 0; j <= w0-2; j+=2, k+=2, p+=2, pup+=2) { dst_tilted[p] = src_d[k] + dst_tilted[pup]; dst_tilted[p+1] = src_d[k+1] + dst_tilted[pup+1]; } for(; j < w0; ++j, ++k, ++p, ++pup) { dst_tilted[p] = src_d[k] + dst_tilted[pup]; } } // diagonal p = (w1+w0)|0, pup = w0; for(i = 0; i < h0; ++i, p+=w1, pup+=w1) { dst_tilted[p] += dst_tilted[pup]; } for(j = w0-1; j > 0; --j) { p = j+h0*w1, pup=p-w1; for(i = h0; i > 0; --i, p-=w1, pup-=w1) { dst_tilted[p] += dst_tilted[pup] + dst_tilted[pup+1]; } } } }, equalize_histogram: function(src, dst) { var w=src.cols,h=src.rows,src_d=src.data; dst.resize(w, h, src.channel); var dst_d=dst.data,size=w*h; var i=0,prev=0,hist0,norm; var hist0_node = jsfeat.cache.get_buffer(256<<2); hist0 = hist0_node.i32; for(; i < 256; ++i) hist0[i] = 0; for (i = 0; i < size; ++i) { ++hist0[src_d[i]]; } prev = hist0[0]; for (i = 1; i < 256; ++i) { prev = hist0[i] += prev; } norm = 255 / size; for (i = 0; i < size; ++i) { dst_d[i] = (hist0[src_d[i]] * norm + 0.5)|0; } jsfeat.cache.put_buffer(hist0_node); }, canny: function(src, dst, low_thresh, high_thresh) { var w=src.cols,h=src.rows,src_d=src.data; dst.resize(w, h, src.channel); var dst_d=dst.data; var i=0,j=0,grad=0,w2=w<<1,_grad=0,suppress=0,f=0,x=0,y=0,s=0; var tg22x=0,tg67x=0; // cache buffers var dxdy_node = jsfeat.cache.get_buffer((h * w2)<<2); var buf_node = jsfeat.cache.get_buffer((3 * (w + 2))<<2); var map_node = jsfeat.cache.get_buffer(((h+2) * (w + 2))<<2); var stack_node = jsfeat.cache.get_buffer((h * w)<<2); var buf = buf_node.i32; var map = map_node.i32; var stack = stack_node.i32; var dxdy = dxdy_node.i32; var dxdy_m = new jsfeat.matrix_t(w, h, jsfeat.S32C2_t, dxdy_node.data); var row0=1,row1=(w+2+1)|0,row2=(2*(w+2)+1)|0,map_w=(w+2)|0,map_i=(map_w+1)|0,stack_i=0; this.sobel_derivatives(src, dxdy_m); if(low_thresh > high_thresh) { i = low_thresh; low_thresh = high_thresh; high_thresh = i; } i = (3 * (w + 2))|0; while(--i>=0) { buf[i] = 0; } i = ((h+2) * (w + 2))|0; while(--i>=0) { map[i] = 0; } for (; j < w; ++j, grad+=2) { //buf[row1+j] = Math.abs(dxdy[grad]) + Math.abs(dxdy[grad+1]); x = dxdy[grad], y = dxdy[grad+1]; //buf[row1+j] = x*x + y*y; buf[row1+j] = ((x ^ (x >> 31)) - (x >> 31)) + ((y ^ (y >> 31)) - (y >> 31)); } for(i=1; i <= h; ++i, grad+=w2) { if(i == h) { j = row2+w; while(--j>=row2) { buf[j] = 0; } } else { for (j = 0; j < w; j++) { //buf[row2+j] = Math.abs(dxdy[grad+(j<<1)]) + Math.abs(dxdy[grad+(j<<1)+1]); x = dxdy[grad+(j<<1)], y = dxdy[grad+(j<<1)+1]; //buf[row2+j] = x*x + y*y; buf[row2+j] = ((x ^ (x >> 31)) - (x >> 31)) + ((y ^ (y >> 31)) - (y >> 31)); } } _grad = (grad - w2)|0; map[map_i-1] = 0; suppress = 0; for(j = 0; j < w; ++j, _grad+=2) { f = buf[row1+j]; if (f > low_thresh) { x = dxdy[_grad]; y = dxdy[_grad+1]; s = x ^ y; // seems ot be faster than Math.abs x = ((x ^ (x >> 31)) - (x >> 31))|0; y = ((y ^ (y >> 31)) - (y >> 31))|0; //x * tan(22.5) x * tan(67.5) == 2 * x + x * tan(22.5) tg22x = x * 13573; tg67x = tg22x + ((x + x) << 15); y <<= 15; if (y < tg22x) { if (f > buf[row1+j-1] && f >= buf[row1+j+1]) { if (f > high_thresh && !suppress && map[map_i+j-map_w] != 2) { map[map_i+j] = 2; suppress = 1; stack[stack_i++] = map_i + j; } else { map[map_i+j] = 1; } continue; } } else if (y > tg67x) { if (f > buf[row0+j] && f >= buf[row2+j]) { if (f > high_thresh && !suppress && map[map_i+j-map_w] != 2) { map[map_i+j] = 2; suppress = 1; stack[stack_i++] = map_i + j; } else { map[map_i+j] = 1; } continue; } } else { s = s < 0 ? -1 : 1; if (f > buf[row0+j-s] && f > buf[row2+j+s]) { if (f > high_thresh && !suppress && map[map_i+j-map_w] != 2) { map[map_i+j] = 2; suppress = 1; stack[stack_i++] = map_i + j; } else { map[map_i+j] = 1; } continue; } } } map[map_i+j] = 0; suppress = 0; } map[map_i+w] = 0; map_i += map_w; j = row0; row0 = row1; row1 = row2; row2 = j; } j = map_i - map_w - 1; for(i = 0; i < map_w; ++i, ++j) { map[j] = 0; } // path following while(stack_i > 0) { map_i = stack[--stack_i]; map_i -= map_w+1; if(map[map_i] == 1) map[map_i] = 2, stack[stack_i++] = map_i; map_i += 1; if(map[map_i] == 1) map[map_i] = 2, stack[stack_i++] = map_i; map_i += 1; if(map[map_i] == 1) map[map_i] = 2, stack[stack_i++] = map_i; map_i += map_w; if(map[map_i] == 1) map[map_i] = 2, stack[stack_i++] = map_i; map_i -= 2; if(map[map_i] == 1) map[map_i] = 2, stack[stack_i++] = map_i; map_i += map_w; if(map[map_i] == 1) map[map_i] = 2, stack[stack_i++] = map_i; map_i += 1; if(map[map_i] == 1) map[map_i] = 2, stack[stack_i++] = map_i; map_i += 1; if(map[map_i] == 1) map[map_i] = 2, stack[stack_i++] = map_i; } map_i = map_w + 1; row0 = 0; for(i = 0; i < h; ++i, map_i+=map_w) { for(j = 0; j < w; ++j) { dst_d[row0++] = (map[map_i+j] == 2) * 0xff; } } // free buffers jsfeat.cache.put_buffer(dxdy_node); jsfeat.cache.put_buffer(buf_node); jsfeat.cache.put_buffer(map_node); jsfeat.cache.put_buffer(stack_node); }, // transform is 3x3 matrix_t warp_perspective: function(src, dst, transform, fill_value) { if (typeof fill_value === "undefined") { fill_value = 0; } var src_width=src.cols|0, src_height=src.rows|0, dst_width=dst.cols|0, dst_height=dst.rows|0; var src_d=src.da