From f78a76d5f6db3613627351c91e097b91bc97af49 Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Wed, 8 Jul 2026 11:30:20 +0200 Subject: [PATCH] refactor(imgproc): de-duplicate imgproc module (#47) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Second de-duplication step of #47, following the pattern proven in #62. - src/imgproc/imgproc.ts: replace the type-only stub with the REAL implementation moved verbatim from the monolith (grayscale, resample, box_blur_gray, gaussian_blur, hough_transform, pyrdown, scharr/sobel derivatives, compute_integral_image, equalize_histogram, canny, warp_perspective, warp_affine, skindetector). Only deliberate change: gaussian_blur instantiates the math module directly (import from ../math/math) instead of via the jsfeatNext.math static slot, removing an attach-order dependency on the aggregator. - src/jsfeatNext.ts: shrinks by ~1050 lines; attaches imgproc from its module (jsfeatNext.imgproc = imgproc). - Side effect: the latent trap in src/orb/rectify_patch.ts (it imports imgproc, which until now was the throwing stub) is healed — it resolves to the real implementation. Verified behavior-preserving: tsc --noEmit clean; npm test 57/57 (14 of those pin imgproc bit-for-bit vs original jsfeat); UMD build checked (instanceof chain, static-constant inheritance, grayscale + gaussian_blur smoke on the bundle). Co-Authored-By: Claude Fable 5 --- src/imgproc/imgproc.ts | 1052 +++++++++++++++++++++++++++++++++++++++- src/jsfeatNext.ts | 1052 +--------------------------------------- 2 files changed, 1037 insertions(+), 1067 deletions(-) diff --git a/src/imgproc/imgproc.ts b/src/imgproc/imgproc.ts index 8aaf57b..d8163e1 100644 --- a/src/imgproc/imgproc.ts +++ b/src/imgproc/imgproc.ts @@ -1,45 +1,1065 @@ +import jsfeatNext from "../core/core"; import { matrix_t } from "../matrix_t/matrix_t"; -export class imgproc { +import { JSFEAT_CONSTANTS } from "../constants/constants"; +import { _resample, _resample_u8 } from "./resample"; +import { _convol, _convol_u8 } from "./convol"; +import { math } from "../math/math"; + +/** + * Real implementation, moved out of the src/jsfeatNext.ts monolith (issue #47). + * This file previously held a type-only stub whose methods threw + * "Method not implemented." — the implementation below is the inline code + * from the monolith, verbatim (the only change: gaussian_blur instantiates + * the math module directly instead of via the jsfeatNext.math static slot). + */ +export class imgproc extends jsfeatNext { + constructor() { + super(); + } + grayscale(src: Uint8Array | Uint8ClampedArray, w: number, h: number, dst: matrix_t, code?: number): void { - throw new Error("Method not implemented."); + // this is default image data representation in browser + if (typeof code === "undefined") { + code = JSFEAT_CONSTANTS.COLOR_RGBA2GRAY; + } + let x = 0, + y = 0, + i = 0, + j = 0, + ir = 0, + jr = 0; + let coeff_r = 4899, + coeff_g = 9617, + coeff_b = 1868, + cn = 4; + + if (code == JSFEAT_CONSTANTS.COLOR_BGRA2GRAY || code == JSFEAT_CONSTANTS.COLOR_BGR2GRAY) { + coeff_r = 1868; + coeff_b = 4899; + } + if (code == JSFEAT_CONSTANTS.COLOR_RGB2GRAY || code == JSFEAT_CONSTANTS.COLOR_BGR2GRAY) { + cn = 3; + } + const cn2 = cn << 1, + cn3 = (cn * 3) | 0; + + dst.resize(w, h, 1); + const 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(src: matrix_t, dst: matrix_t, nw: number, nh: number): void { - throw new Error("Method not implemented."); + const 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_CONSTANTS.U8_t && dst.type & JSFEAT_CONSTANTS.U8_t && (h * w) / (nh * nw) < 0x100) { + _resample_u8(src, dst, this.cache, nw, nh); + } else { + _resample(src, dst, this.cache, nw, nh); + } + } } + box_blur_gray(src: matrix_t, dst: matrix_t, radius: number, options: number): void { - throw new Error("Method not implemented."); + if (typeof options === "undefined") { + options = 0; + } + const w = src.cols, + h = src.rows, + h2 = h << 1, + w2 = w << 1; + let i = 0, + x = 0, + y = 0, + end = 0; + const windowSize = ((radius << 1) + 1) | 0; + const radiusPlusOne = (radius + 1) | 0, + radiusPlus2 = (radiusPlusOne + 1) | 0; + const scale = options & JSFEAT_CONSTANTS.BOX_BLUR_NOSCALE ? 1 : 1.0 / (windowSize * windowSize); + + const tmp_buff = this.cache.get_buffer((w * h) << 2); + + let sum = 0, + dstIndex = 0, + srcIndex = 0, + nextPixelIndex = 0, + previousPixelIndex = 0; + const data_i32 = tmp_buff.i32; // to prevent overflow + let data_u8 = src.data; + let 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; + } + } + + this.cache.put_buffer(tmp_buff); } + gaussian_blur(src: matrix_t, dst: matrix_t, kernel_size: number, sigma: number): void { - throw new Error("Method not implemented."); + const jsfeatmath = new math(); + 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; + const half_kernel = kernel_size >> 1; + const w = src.cols, + h = src.rows; + const data_type = src.type, + is_u8 = data_type & JSFEAT_CONSTANTS.U8_t; + + dst.resize(w, h, src.channel); + + const src_d = src.data, + dst_d = dst.data; + let buf, + filter, + buf_sz = (kernel_size + Math.max(h, w)) | 0; + + const buf_node = this.cache.get_buffer(buf_sz << 2); + const filt_node = this.cache.get_buffer(kernel_size << 2); + + if (is_u8) { + buf = buf_node.i32; + filter = filt_node.i32; + } else if (data_type & JSFEAT_CONSTANTS.S32_t) { + buf = buf_node.i32; + filter = filt_node.f32; + } else { + buf = buf_node.f32; + filter = filt_node.f32; + } + + jsfeatmath.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); + } + + this.cache.put_buffer(buf_node); + this.cache.put_buffer(filt_node); } - hough_transform(img: matrix_t, rho_res: number, theta_res: number, threshold: number): Array { - throw new Error("Method not implemented."); + + hough_transform(img: matrix_t, rho_res: number, theta_res: number, threshold: number): number[] { + let r; + let i; + const image = img.data; + + const width = img.cols; + const height = img.rows; + const step = width; + + const min_theta = 0.0; + const max_theta = Math.PI; + + const numangle = Math.round((max_theta - min_theta) / theta_res); + const numrho = Math.round(((width + height) * 2 + 1) / rho_res); + const irho = 1.0 / rho_res; + + const accum = new Int32Array((numangle + 2) * (numrho + 2)); //typed arrays are initialized to 0 + const tabSin = new Float32Array(numangle); + const tabCos = new Float32Array(numangle); + + let n = 0; + let 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 (i = 0; i < height; i++) { + for (let 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 (n = 0; n < numangle; n++) { + 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 + const _sort_buf = []; + for (r = 0; r < numrho; r++) { + for (n = 0; n < numangle; n++) { + const 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 + const linesMax = Math.min(numangle * numrho, _sort_buf.length); + const scale = 1.0 / (numrho + 2); + const lines = new Array(); + for (i = 0; i < linesMax; i++) { + const idx = _sort_buf[i]; + n = Math.floor(idx * scale) - 1; + r = idx - (n + 1) * (numrho + 2) - 1; + const lrho = (r - (numrho - 1) * 0.5) * rho_res; + const langle = n * theta_res; + lines.push([lrho, langle]); + } + return lines; } + pyrdown(src: matrix_t, dst: matrix_t, sx?: number, sy?: number): void { - throw new Error("Method not implemented."); + // this is needed for bbf + if (typeof sx === "undefined") { + sx = 0; + } + if (typeof sy === "undefined") { + sy = 0; + } + + const w = src.cols, + h = src.rows; + const w2 = w >> 1, + h2 = h >> 1; + const _w2 = w2 - (sx << 1), + _h2 = h2 - (sy << 1); + let x = 0, + y = 0, + sptr = sx + sy * w, + sline = 0, + dptr = 0, + dline = 0; + + dst.resize(w2, h2, src.channel); + + const 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(src: matrix_t, dst: matrix_t): void { - throw new Error("Method not implemented."); + const w = src.cols, + h = src.rows; + let dstep = w << 1, + x = 0, + y = 0, + x1 = 0, + a, + b, + c, + d, + e, + f; + let srow0 = 0, + srow1 = 0, + srow2 = 0, + drow = 0; + let trow0, trow1; + + dst.resize(w, h, 2); // 2 channel output gx, gy + + const img = src.data, + gxgy = dst.data; + + const buf0_node = this.cache.get_buffer((w + 2) << 2); + const buf1_node = this.cache.get_buffer((w + 2) << 2); + + if (src.type & JSFEAT_CONSTANTS.U8_t || src.type & JSFEAT_CONSTANTS.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; + } + } + this.cache.put_buffer(buf0_node); + this.cache.put_buffer(buf1_node); } + + // compute gradient using Sobel kernel [1 2 1] * [-1 0 1]^T + // dst: [gx,gy,...] sobel_derivatives(src: matrix_t, dst: matrix_t): void { - throw new Error("Method not implemented."); + const w = src.cols, + h = src.rows; + let dstep = w << 1, + x = 0, + y = 0, + x1 = 0, + a, + b, + c, + d, + e, + f; + let srow0 = 0, + srow1 = 0, + srow2 = 0, + drow = 0; + let trow0, trow1; + + dst.resize(w, h, 2); // 2 channel output gx, gy + + const img = src.data, + gxgy = dst.data; + + const buf0_node = this.cache.get_buffer((w + 2) << 2); + const buf1_node = this.cache.get_buffer((w + 2) << 2); + + if (src.type & JSFEAT_CONSTANTS.U8_t || src.type & JSFEAT_CONSTANTS.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; + } + } + this.cache.put_buffer(buf0_node); + this.cache.put_buffer(buf1_node); } + + // please note: + // dst_(type) size should be cols = src.cols+1, rows = src.rows+1 compute_integral_image(src: matrix_t, dst_sum: number[], dst_sqsum: number[], dst_tilted: any[]): void { - throw new Error("Method not implemented."); + const w0 = src.cols | 0, + h0 = src.rows | 0, + src_d = src.data; + const w1 = (w0 + 1) | 0; + let 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(src: matrix_t, dst: matrix_t): void { - throw new Error("Method not implemented."); + const w = src.cols, + h = src.rows, + src_d = src.data; + + dst.resize(w, h, src.channel); + + const dst_d = dst.data, + size = w * h; + let i = 0, + prev = 0, + hist0, + norm; + + const hist0_node = this.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; + } + this.cache.put_buffer(hist0_node); } + canny(src: matrix_t, dst: matrix_t, low_thresh: number, high_thresh: number): void { - throw new Error("Method not implemented."); + const w = src.cols, + h = src.rows, + src_d = src.data; + + dst.resize(w, h, src.channel); + + const dst_d = dst.data; + let i = 0, + j: number = 0, + grad = 0, + w2 = w << 1, + _grad = 0, + suppress = 0, + f = 0, + x = 0, + y = 0, + s = 0; + let tg22x = 0, + tg67x = 0; + + // cache buffers + const dxdy_node = this.cache.get_buffer((h * w2) << 2); + const buf_node = this.cache.get_buffer((3 * (w + 2)) << 2); + const map_node = this.cache.get_buffer(((h + 2) * (w + 2)) << 2); + const stack_node = this.cache.get_buffer((h * w) << 2); + + const buf = buf_node.i32; + const map = map_node.i32; + const stack = stack_node.i32; + const dxdy = dxdy_node.i32; + const dxdy_m = new matrix_t(w, h, JSFEAT_CONSTANTS.S32C2_t, dxdy_node.data); + let row0 = 1, + row1 = (w + 2 + 1) | 0, + row2 = (2 * (w + 2) + 1) | 0, + map_w = (w + 2) | 0, + map_i: number = (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++] = Number(map[map_i + j] == 2) * 0xff; + } + } + + // free buffers + this.cache.put_buffer(dxdy_node); + this.cache.put_buffer(buf_node); + this.cache.put_buffer(map_node); + this.cache.put_buffer(stack_node); } + + // transform is 3x3 matrix_t warp_perspective(src: matrix_t, dst: matrix_t, transform: matrix_t, fill_value: number): void { - throw new Error("Method not implemented."); + if (typeof fill_value === "undefined") { + fill_value = 0; + } + const src_width = src.cols | 0, + src_height = src.rows | 0, + dst_width = dst.cols | 0, + dst_height = dst.rows | 0; + const src_d = src.data, + dst_d = dst.data; + let x = 0, + y = 0, + off = 0, + ixs = 0, + iys = 0, + xs = 0.0, + ys = 0.0, + xs0 = 0.0, + ys0 = 0.0, + ws = 0.0, + sc = 0.0, + a = 0.0, + b = 0.0, + p0 = 0.0, + p1 = 0.0; + const td = transform.data; + const m00 = td[0], + m01 = td[1], + m02 = td[2], + m10 = td[3], + m11 = td[4], + m12 = td[5], + m20 = td[6], + m21 = td[7], + m22 = td[8]; + + for (let dptr = 0; y < dst_height; ++y) { + (xs0 = m01 * y + m02), (ys0 = m11 * y + m12), (ws = m21 * y + m22); + for (x = 0; x < dst_width; ++x, ++dptr, xs0 += m00, ys0 += m10, ws += m20) { + sc = 1.0 / ws; + (xs = xs0 * sc), (ys = ys0 * sc); + (ixs = xs | 0), (iys = ys | 0); + + if (xs > 0 && ys > 0 && ixs < src_width - 1 && iys < src_height - 1) { + a = Math.max(xs - ixs, 0.0); + b = Math.max(ys - iys, 0.0); + off = (src_width * iys + ixs) | 0; + + p0 = src_d[off] + a * (src_d[off + 1] - src_d[off]); + p1 = src_d[off + src_width] + a * (src_d[off + src_width + 1] - src_d[off + src_width]); + + dst_d[dptr] = p0 + b * (p1 - p0); + } else dst_d[dptr] = fill_value; + } + } } + + // transform is 3x3 or 2x3 matrix_t only first 6 values referenced warp_affine(src: matrix_t, dst: matrix_t, transform: matrix_t, fill_value: number): void { - throw new Error("Method not implemented."); + if (typeof fill_value === "undefined") { + fill_value = 0; + } + const src_width = src.cols, + src_height = src.rows, + dst_width = dst.cols, + dst_height = dst.rows; + const src_d = src.data, + dst_d = dst.data; + let x = 0, + y = 0, + off = 0, + ixs = 0, + iys = 0, + xs = 0.0, + ys = 0.0, + a = 0.0, + b = 0.0, + p0 = 0.0, + p1 = 0.0; + const td = transform.data; + const m00 = td[0], + m01 = td[1], + m02 = td[2], + m10 = td[3], + m11 = td[4], + m12 = td[5]; + + for (let dptr = 0; y < dst_height; ++y) { + xs = m01 * y + m02; + ys = m11 * y + m12; + for (x = 0; x < dst_width; ++x, ++dptr, xs += m00, ys += m10) { + ixs = xs | 0; + iys = ys | 0; + + if (ixs >= 0 && iys >= 0 && ixs < src_width - 1 && iys < src_height - 1) { + a = xs - ixs; + b = ys - iys; + off = src_width * iys + ixs; + + p0 = src_d[off] + a * (src_d[off + 1] - src_d[off]); + p1 = src_d[off + src_width] + a * (src_d[off + src_width + 1] - src_d[off + src_width]); + + dst_d[dptr] = p0 + b * (p1 - p0); + } else dst_d[dptr] = fill_value; + } + } } + + // Basic RGB Skin detection filter + // from http://popscan.blogspot.fr/2012/08/skin-detection-in-digital-images.html skindetector(src: { width: number; height: number; data: any[] }, dst: number[]): void { - throw new Error("Method not implemented."); + let r, g, b, j; + let i = src.width * src.height; + while (i--) { + j = i * 4; + r = src.data[j]; + g = src.data[j + 1]; + b = src.data[j + 2]; + if (r > 95 && g > 40 && b > 20 && r > g && r > b && r - Math.min(g, b) > 15 && Math.abs(r - g) > 15) { + dst[i] = 255; + } else { + dst[i] = 0; + } + } } } diff --git a/src/jsfeatNext.ts b/src/jsfeatNext.ts index a41098f..e29e5ad 100644 --- a/src/jsfeatNext.ts +++ b/src/jsfeatNext.ts @@ -782,1057 +782,7 @@ jsfeatNext.fast_corners = class fast_corners extends jsfeatNext { } }; -jsfeatNext.imgproc = class imgproc extends jsfeatNext { - constructor() { - super(); - } - - grayscale(src: Uint8Array | Uint8ClampedArray, w: number, h: number, dst: matrix_t, code?: number): void { - // this is default image data representation in browser - if (typeof code === "undefined") { - code = JSFEAT_CONSTANTS.COLOR_RGBA2GRAY; - } - let x = 0, - y = 0, - i = 0, - j = 0, - ir = 0, - jr = 0; - let coeff_r = 4899, - coeff_g = 9617, - coeff_b = 1868, - cn = 4; - - if (code == JSFEAT_CONSTANTS.COLOR_BGRA2GRAY || code == JSFEAT_CONSTANTS.COLOR_BGR2GRAY) { - coeff_r = 1868; - coeff_b = 4899; - } - if (code == JSFEAT_CONSTANTS.COLOR_RGB2GRAY || code == JSFEAT_CONSTANTS.COLOR_BGR2GRAY) { - cn = 3; - } - const cn2 = cn << 1, - cn3 = (cn * 3) | 0; - - dst.resize(w, h, 1); - const 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(src: matrix_t, dst: matrix_t, nw: number, nh: number): void { - const 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_CONSTANTS.U8_t && dst.type & JSFEAT_CONSTANTS.U8_t && (h * w) / (nh * nw) < 0x100) { - _resample_u8(src, dst, this.cache, nw, nh); - } else { - _resample(src, dst, this.cache, nw, nh); - } - } - } - - box_blur_gray(src: matrix_t, dst: matrix_t, radius: number, options: number): void { - if (typeof options === "undefined") { - options = 0; - } - const w = src.cols, - h = src.rows, - h2 = h << 1, - w2 = w << 1; - let i = 0, - x = 0, - y = 0, - end = 0; - const windowSize = ((radius << 1) + 1) | 0; - const radiusPlusOne = (radius + 1) | 0, - radiusPlus2 = (radiusPlusOne + 1) | 0; - const scale = options & JSFEAT_CONSTANTS.BOX_BLUR_NOSCALE ? 1 : 1.0 / (windowSize * windowSize); - - const tmp_buff = this.cache.get_buffer((w * h) << 2); - - let sum = 0, - dstIndex = 0, - srcIndex = 0, - nextPixelIndex = 0, - previousPixelIndex = 0; - const data_i32 = tmp_buff.i32; // to prevent overflow - let data_u8 = src.data; - let 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; - } - } - - this.cache.put_buffer(tmp_buff); - } - - gaussian_blur(src: matrix_t, dst: matrix_t, kernel_size: number, sigma: number): void { - const jsfeatmath = new jsfeatNext.math(); - 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; - const half_kernel = kernel_size >> 1; - const w = src.cols, - h = src.rows; - const data_type = src.type, - is_u8 = data_type & JSFEAT_CONSTANTS.U8_t; - - dst.resize(w, h, src.channel); - - const src_d = src.data, - dst_d = dst.data; - let buf, - filter, - buf_sz = (kernel_size + Math.max(h, w)) | 0; - - const buf_node = this.cache.get_buffer(buf_sz << 2); - const filt_node = this.cache.get_buffer(kernel_size << 2); - - if (is_u8) { - buf = buf_node.i32; - filter = filt_node.i32; - } else if (data_type & JSFEAT_CONSTANTS.S32_t) { - buf = buf_node.i32; - filter = filt_node.f32; - } else { - buf = buf_node.f32; - filter = filt_node.f32; - } - - jsfeatmath.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); - } - - this.cache.put_buffer(buf_node); - this.cache.put_buffer(filt_node); - } - - hough_transform(img: matrix_t, rho_res: number, theta_res: number, threshold: number): number[] { - let r; - let i; - const image = img.data; - - const width = img.cols; - const height = img.rows; - const step = width; - - const min_theta = 0.0; - const max_theta = Math.PI; - - const numangle = Math.round((max_theta - min_theta) / theta_res); - const numrho = Math.round(((width + height) * 2 + 1) / rho_res); - const irho = 1.0 / rho_res; - - const accum = new Int32Array((numangle + 2) * (numrho + 2)); //typed arrays are initialized to 0 - const tabSin = new Float32Array(numangle); - const tabCos = new Float32Array(numangle); - - let n = 0; - let 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 (i = 0; i < height; i++) { - for (let 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 (n = 0; n < numangle; n++) { - 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 - const _sort_buf = []; - for (r = 0; r < numrho; r++) { - for (n = 0; n < numangle; n++) { - const 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 - const linesMax = Math.min(numangle * numrho, _sort_buf.length); - const scale = 1.0 / (numrho + 2); - const lines = new Array(); - for (i = 0; i < linesMax; i++) { - const idx = _sort_buf[i]; - n = Math.floor(idx * scale) - 1; - r = idx - (n + 1) * (numrho + 2) - 1; - const lrho = (r - (numrho - 1) * 0.5) * rho_res; - const langle = n * theta_res; - lines.push([lrho, langle]); - } - return lines; - } - - pyrdown(src: matrix_t, dst: matrix_t, sx?: number, sy?: number): void { - // this is needed for bbf - if (typeof sx === "undefined") { - sx = 0; - } - if (typeof sy === "undefined") { - sy = 0; - } - - const w = src.cols, - h = src.rows; - const w2 = w >> 1, - h2 = h >> 1; - const _w2 = w2 - (sx << 1), - _h2 = h2 - (sy << 1); - let x = 0, - y = 0, - sptr = sx + sy * w, - sline = 0, - dptr = 0, - dline = 0; - - dst.resize(w2, h2, src.channel); - - const 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(src: matrix_t, dst: matrix_t): void { - const w = src.cols, - h = src.rows; - let dstep = w << 1, - x = 0, - y = 0, - x1 = 0, - a, - b, - c, - d, - e, - f; - let srow0 = 0, - srow1 = 0, - srow2 = 0, - drow = 0; - let trow0, trow1; - - dst.resize(w, h, 2); // 2 channel output gx, gy - - const img = src.data, - gxgy = dst.data; - - const buf0_node = this.cache.get_buffer((w + 2) << 2); - const buf1_node = this.cache.get_buffer((w + 2) << 2); - - if (src.type & JSFEAT_CONSTANTS.U8_t || src.type & JSFEAT_CONSTANTS.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; - } - } - this.cache.put_buffer(buf0_node); - this.cache.put_buffer(buf1_node); - } - - // compute gradient using Sobel kernel [1 2 1] * [-1 0 1]^T - // dst: [gx,gy,...] - sobel_derivatives(src: matrix_t, dst: matrix_t): void { - const w = src.cols, - h = src.rows; - let dstep = w << 1, - x = 0, - y = 0, - x1 = 0, - a, - b, - c, - d, - e, - f; - let srow0 = 0, - srow1 = 0, - srow2 = 0, - drow = 0; - let trow0, trow1; - - dst.resize(w, h, 2); // 2 channel output gx, gy - - const img = src.data, - gxgy = dst.data; - - const buf0_node = this.cache.get_buffer((w + 2) << 2); - const buf1_node = this.cache.get_buffer((w + 2) << 2); - - if (src.type & JSFEAT_CONSTANTS.U8_t || src.type & JSFEAT_CONSTANTS.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; - } - } - this.cache.put_buffer(buf0_node); - this.cache.put_buffer(buf1_node); - } - - // please note: - // dst_(type) size should be cols = src.cols+1, rows = src.rows+1 - compute_integral_image(src: matrix_t, dst_sum: number[], dst_sqsum: number[], dst_tilted: any[]): void { - const w0 = src.cols | 0, - h0 = src.rows | 0, - src_d = src.data; - const w1 = (w0 + 1) | 0; - let 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(src: matrix_t, dst: matrix_t): void { - const w = src.cols, - h = src.rows, - src_d = src.data; - - dst.resize(w, h, src.channel); - - const dst_d = dst.data, - size = w * h; - let i = 0, - prev = 0, - hist0, - norm; - - const hist0_node = this.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; - } - this.cache.put_buffer(hist0_node); - } - - canny(src: matrix_t, dst: matrix_t, low_thresh: number, high_thresh: number): void { - const w = src.cols, - h = src.rows, - src_d = src.data; - - dst.resize(w, h, src.channel); - - const dst_d = dst.data; - let i = 0, - j: number = 0, - grad = 0, - w2 = w << 1, - _grad = 0, - suppress = 0, - f = 0, - x = 0, - y = 0, - s = 0; - let tg22x = 0, - tg67x = 0; - - // cache buffers - const dxdy_node = this.cache.get_buffer((h * w2) << 2); - const buf_node = this.cache.get_buffer((3 * (w + 2)) << 2); - const map_node = this.cache.get_buffer(((h + 2) * (w + 2)) << 2); - const stack_node = this.cache.get_buffer((h * w) << 2); - - const buf = buf_node.i32; - const map = map_node.i32; - const stack = stack_node.i32; - const dxdy = dxdy_node.i32; - const dxdy_m = new matrix_t(w, h, JSFEAT_CONSTANTS.S32C2_t, dxdy_node.data); - let row0 = 1, - row1 = (w + 2 + 1) | 0, - row2 = (2 * (w + 2) + 1) | 0, - map_w = (w + 2) | 0, - map_i: number = (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++] = Number(map[map_i + j] == 2) * 0xff; - } - } - - // free buffers - this.cache.put_buffer(dxdy_node); - this.cache.put_buffer(buf_node); - this.cache.put_buffer(map_node); - this.cache.put_buffer(stack_node); - } - - // transform is 3x3 matrix_t - warp_perspective(src: matrix_t, dst: matrix_t, transform: matrix_t, fill_value: number): void { - if (typeof fill_value === "undefined") { - fill_value = 0; - } - const src_width = src.cols | 0, - src_height = src.rows | 0, - dst_width = dst.cols | 0, - dst_height = dst.rows | 0; - const src_d = src.data, - dst_d = dst.data; - let x = 0, - y = 0, - off = 0, - ixs = 0, - iys = 0, - xs = 0.0, - ys = 0.0, - xs0 = 0.0, - ys0 = 0.0, - ws = 0.0, - sc = 0.0, - a = 0.0, - b = 0.0, - p0 = 0.0, - p1 = 0.0; - const td = transform.data; - const m00 = td[0], - m01 = td[1], - m02 = td[2], - m10 = td[3], - m11 = td[4], - m12 = td[5], - m20 = td[6], - m21 = td[7], - m22 = td[8]; - - for (let dptr = 0; y < dst_height; ++y) { - (xs0 = m01 * y + m02), (ys0 = m11 * y + m12), (ws = m21 * y + m22); - for (x = 0; x < dst_width; ++x, ++dptr, xs0 += m00, ys0 += m10, ws += m20) { - sc = 1.0 / ws; - (xs = xs0 * sc), (ys = ys0 * sc); - (ixs = xs | 0), (iys = ys | 0); - - if (xs > 0 && ys > 0 && ixs < src_width - 1 && iys < src_height - 1) { - a = Math.max(xs - ixs, 0.0); - b = Math.max(ys - iys, 0.0); - off = (src_width * iys + ixs) | 0; - - p0 = src_d[off] + a * (src_d[off + 1] - src_d[off]); - p1 = src_d[off + src_width] + a * (src_d[off + src_width + 1] - src_d[off + src_width]); - - dst_d[dptr] = p0 + b * (p1 - p0); - } else dst_d[dptr] = fill_value; - } - } - } - - // transform is 3x3 or 2x3 matrix_t only first 6 values referenced - warp_affine(src: matrix_t, dst: matrix_t, transform: matrix_t, fill_value: number): void { - if (typeof fill_value === "undefined") { - fill_value = 0; - } - const src_width = src.cols, - src_height = src.rows, - dst_width = dst.cols, - dst_height = dst.rows; - const src_d = src.data, - dst_d = dst.data; - let x = 0, - y = 0, - off = 0, - ixs = 0, - iys = 0, - xs = 0.0, - ys = 0.0, - a = 0.0, - b = 0.0, - p0 = 0.0, - p1 = 0.0; - const td = transform.data; - const m00 = td[0], - m01 = td[1], - m02 = td[2], - m10 = td[3], - m11 = td[4], - m12 = td[5]; - - for (let dptr = 0; y < dst_height; ++y) { - xs = m01 * y + m02; - ys = m11 * y + m12; - for (x = 0; x < dst_width; ++x, ++dptr, xs += m00, ys += m10) { - ixs = xs | 0; - iys = ys | 0; - - if (ixs >= 0 && iys >= 0 && ixs < src_width - 1 && iys < src_height - 1) { - a = xs - ixs; - b = ys - iys; - off = src_width * iys + ixs; - - p0 = src_d[off] + a * (src_d[off + 1] - src_d[off]); - p1 = src_d[off + src_width] + a * (src_d[off + src_width + 1] - src_d[off + src_width]); - - dst_d[dptr] = p0 + b * (p1 - p0); - } else dst_d[dptr] = fill_value; - } - } - } - - // Basic RGB Skin detection filter - // from http://popscan.blogspot.fr/2012/08/skin-detection-in-digital-images.html - skindetector(src: { width: number; height: number; data: any[] }, dst: number[]): void { - let r, g, b, j; - let i = src.width * src.height; - while (i--) { - j = i * 4; - r = src.data[j]; - g = src.data[j + 1]; - b = src.data[j + 2]; - if (r > 95 && g > 40 && b > 20 && r > g && r > b && r - Math.min(g, b) > 15 && Math.abs(r - g) > 15) { - dst[i] = 255; - } else { - dst[i] = 0; - } - } - } -}; +jsfeatNext.imgproc = imgproc; jsfeatNext.math = math;