UNPKG

@webarkit/jsfeat-next

Version:

Typescript version of jsfeat for WebARKit

1,501 lines (1,306 loc) 131 kB
import { IData_Type, data_type } from "./data_type/data_type"; import { cache } from "./cache/cache"; import { imgproc } from "./imgproc/imgproc"; import { _resample, _resample_u8 } from "./imgproc/resample"; import { _convol, _convol_u8 } from "./imgproc/convol"; import { linalg } from "./linalg/linalg"; import { swap, hypot } from "./linalg/linalg_base"; import { fast_corners } from "./fast_corners/fast_corners"; import { _cmp_score_16 } from "./fast_corners/fast_private"; import { math } from "./math/math"; import matmath from "./matmath/matmath"; import { matrix_t } from "./matrix_t/matrix_t"; import { pyramid_t } from "./pyramid_t/pyramid_t"; import { point_t } from "./point_t/point_t"; import { transform } from "./transform/transform"; import { keypoint_t } from "./keypoint_t/keypoint_t"; import { orb } from "./orb/orb"; import { bit_pattern_31 } from "./orb/bit_pattern_31"; import { rectify_patch } from "./orb/rectify_patch"; import { yape } from "./yape/yape"; import { compute_laplacian, hessian_min_eigen_value } from "./yape06/yape06_utils"; import { yape06 } from "./yape06/yape06"; import { ransac_params_t } from "./motion_estimator/ransac_params_t"; import { motion_estimator } from "./motion_estimator/motion_estimator"; import { optical_flow_lk } from "./optical_flow_lk/optical_flow_lk"; import { JSFEAT_CONSTANTS } from "./constants/constants"; import pkg from "../package.json"; export default class jsfeatNext { private dt: IData_Type; protected cache: cache; static cache: typeof cache; static fast_corners: typeof fast_corners; static imgproc: typeof imgproc; static linalg: typeof linalg; static math: typeof math; static matmath: typeof matmath; static matrix_t: typeof matrix_t; static pyramid_t: typeof pyramid_t; static transform: typeof transform; static keypoint_t: typeof keypoint_t; static yape: typeof yape; static yape06: typeof yape06; static ransac_params_t: typeof ransac_params_t; static affine2d: typeof affine2d; static homography2d: typeof homography2d; static motion_estimator: typeof motion_estimator; static optical_flow_lk: typeof optical_flow_lk; static orb: typeof orb; constructor() { this.dt = new data_type(); this.cache = new cache(); this.cache.allocate(30, 640 * 4); } // VERSION static VERSION: string = pkg.version; // CONSTANTS static EPSILON = JSFEAT_CONSTANTS.EPSILON; static FLT_MIN = JSFEAT_CONSTANTS.FLT_MIN; static U8_t = JSFEAT_CONSTANTS.U8_t; static S32_t = JSFEAT_CONSTANTS.S32_t; static F32_t = JSFEAT_CONSTANTS.F32_t; static S64_t = JSFEAT_CONSTANTS.S64_t; static F64_t = JSFEAT_CONSTANTS.F64_t; static C1_t = JSFEAT_CONSTANTS.C1_t; static C2_t = JSFEAT_CONSTANTS.C2_t; static C3_t = JSFEAT_CONSTANTS.C3_t; static C4_t = JSFEAT_CONSTANTS.C4_t; // color conversion static COLOR_RGBA2GRAY = JSFEAT_CONSTANTS.COLOR_RGBA2GRAY; static COLOR_RGB2GRAY = JSFEAT_CONSTANTS.COLOR_RGB2GRAY; static COLOR_BGRA2GRAY = JSFEAT_CONSTANTS.COLOR_BGRA2GRAY; static COLOR_BGR2GRAY = JSFEAT_CONSTANTS.COLOR_BGR2GRAY; // box blur option static BOX_BLUR_NOSCALE = JSFEAT_CONSTANTS.BOX_BLUR_NOSCALE; // svd options static SVD_U_T = JSFEAT_CONSTANTS.SVD_U_T; static SVD_V_T = JSFEAT_CONSTANTS.SVD_V_T; // popular formats static U8C1_t = this.U8_t | this.C1_t; static U8C3_t = this.U8_t | this.C3_t; static U8C4_t = this.U8_t | this.C4_t; static F32C1_t = this.F32_t | this.C1_t; static F32C2_t = this.F32_t | this.C2_t; static S32C1_t = this.S32_t | this.C1_t; static S32C2_t = this.S32_t | this.C2_t; get_data_type(type: number): number { return this.dt._get_data_type(type); } get_channel(type: number): number { return this.dt._get_channel(type); } get_data_type_size(type: number): number { return this.dt._get_data_type_size(type); } } class motion_model extends jsfeatNext { public T0: matrix_t; public T1: matrix_t; public AtA: matrix_t; public AtB: matrix_t; constructor() { super(); this.T0 = new matrix_t(3, 3, JSFEAT_CONSTANTS.F32_t | JSFEAT_CONSTANTS.C1_t); this.T1 = new matrix_t(3, 3, JSFEAT_CONSTANTS.F32_t | JSFEAT_CONSTANTS.C1_t); this.AtA = new matrix_t(6, 6, JSFEAT_CONSTANTS.F32_t | JSFEAT_CONSTANTS.C1_t); this.AtB = new matrix_t(6, 1, JSFEAT_CONSTANTS.F32_t | JSFEAT_CONSTANTS.C1_t); } sqr(x: number): number { return x * x; } // does isotropic normalization iso_normalize_points(from: point_t[], to: point_t[], T0: number[], T1: number[], count: number): void { let i = 0; let cx0 = 0.0, cy0 = 0.0, d0 = 0.0, s0 = 0.0; let cx1 = 0.0, cy1 = 0.0, d1 = 0.0, s1 = 0.0; let dx = 0.0, dy = 0.0; for (; i < count; ++i) { cx0 += from[i].x; cy0 += from[i].y; cx1 += to[i].x; cy1 += to[i].y; } cx0 /= count; cy0 /= count; cx1 /= count; cy1 /= count; for (i = 0; i < count; ++i) { dx = from[i].x - cx0; dy = from[i].y - cy0; d0 += Math.sqrt(dx * dx + dy * dy); dx = to[i].x - cx1; dy = to[i].y - cy1; d1 += Math.sqrt(dx * dx + dy * dy); } d0 /= count; d1 /= count; s0 = Math.SQRT2 / d0; s1 = Math.SQRT2 / d1; T0[0] = T0[4] = s0; T0[2] = -cx0 * s0; T0[5] = -cy0 * s0; T0[1] = T0[3] = T0[6] = T0[7] = 0.0; T0[8] = 1.0; T1[0] = T1[4] = s1; T1[2] = -cx1 * s1; T1[5] = -cy1 * s1; T1[1] = T1[3] = T1[6] = T1[7] = 0.0; T1[8] = 1.0; } have_collinear_points(points: point_t[], count: number): boolean { let j = 0, k = 0, i = (count - 1) | 0; let dx1 = 0.0, dy1 = 0.0, dx2 = 0.0, dy2 = 0.0; // check that the i-th selected point does not belong // to a line connecting some previously selected points for (; j < i; ++j) { dx1 = points[j].x - points[i].x; dy1 = points[j].y - points[i].y; for (k = 0; k < j; ++k) { dx2 = points[k].x - points[i].x; dy2 = points[k].y - points[i].y; if ( Math.abs(dx2 * dy1 - dy2 * dx1) <= JSFEAT_CONSTANTS.EPSILON * (Math.abs(dx1) + Math.abs(dy1) + Math.abs(dx2) + Math.abs(dy2)) ) return true; } } return false; } } class affine2d extends motion_model { constructor() { super(); } run(from: point_t[], to: point_t[], model: matrix_t, count: number): number { let i = 0, j = 0; const dt = model.type | JSFEAT_CONSTANTS.C1_t; const md = model.data, t0d = this.T0.data, t1d = this.T1.data; let pt0, pt1, px = 0.0, py = 0.0; const _matmath = new matmath(); const _linalg = new jsfeatNext.linalg(); this.iso_normalize_points(from, to, t0d, t1d, count); const a_buff = this.cache.get_buffer((2 * count * 6) << 3); const b_buff = this.cache.get_buffer((2 * count) << 3); const a_mt = new matrix_t(6, 2 * count, dt, a_buff.data); const b_mt = new matrix_t(1, 2 * count, dt, b_buff.data); const ad = a_mt.data, bd = b_mt.data; for (; i < count; ++i) { pt0 = from[i]; pt1 = to[i]; px = t0d[0] * pt0.x + t0d[1] * pt0.y + t0d[2]; py = t0d[3] * pt0.x + t0d[4] * pt0.y + t0d[5]; j = i * 2 * 6; (ad[j] = px), (ad[j + 1] = py), (ad[j + 2] = 1.0), (ad[j + 3] = 0.0), (ad[j + 4] = 0.0), (ad[j + 5] = 0.0); j += 6; (ad[j] = 0.0), (ad[j + 1] = 0.0), (ad[j + 2] = 0.0), (ad[j + 3] = px), (ad[j + 4] = py), (ad[j + 5] = 1.0); bd[i << 1] = t1d[0] * pt1.x + t1d[1] * pt1.y + t1d[2]; bd[(i << 1) + 1] = t1d[3] * pt1.x + t1d[4] * pt1.y + t1d[5]; } _matmath.multiply_AtA(this.AtA, a_mt); _matmath.multiply_AtB(this.AtB, a_mt, b_mt); _linalg.lu_solve(this.AtA, this.AtB); (md[0] = this.AtB.data[0]), (md[1] = this.AtB.data[1]), (md[2] = this.AtB.data[2]); (md[3] = this.AtB.data[3]), (md[4] = this.AtB.data[4]), (md[5] = this.AtB.data[5]); (md[6] = 0.0), (md[7] = 0.0), (md[8] = 1.0); // fill last row // denormalize _matmath.invert_3x3(this.T1, this.T1); _matmath.multiply_3x3(model, this.T1, model); _matmath.multiply_3x3(model, model, this.T0); // free buffer this.cache.put_buffer(a_buff); this.cache.put_buffer(b_buff); return 1; } } class homography2d extends motion_model { public mLtL: matrix_t; public Evec: matrix_t; constructor() { super(); this.mLtL = new matrix_t(9, 9, JSFEAT_CONSTANTS.F32_t | JSFEAT_CONSTANTS.C1_t); this.Evec = new matrix_t(9, 9, JSFEAT_CONSTANTS.F32_t | JSFEAT_CONSTANTS.C1_t); } run(from: point_t[], to: point_t[], model: matrix_t, count: number): number { let i = 0, j = 0; const md = model.data, t0d = this.T0.data, t1d = this.T1.data; const LtL = this.mLtL.data, evd = this.Evec.data; let x = 0.0, y = 0.0, X = 0.0, Y = 0.0; const _linalg = new jsfeatNext.linalg(); const _matmath = new matmath(); // norm let smx = 0.0, smy = 0.0, cmx = 0.0, cmy = 0.0, sMx = 0.0, sMy = 0.0, cMx = 0.0, cMy = 0.0; for (; i < count; ++i) { cmx += to[i].x; cmy += to[i].y; cMx += from[i].x; cMy += from[i].y; } cmx /= count; cmy /= count; cMx /= count; cMy /= count; for (i = 0; i < count; ++i) { smx += Math.abs(to[i].x - cmx); smy += Math.abs(to[i].y - cmy); sMx += Math.abs(from[i].x - cMx); sMy += Math.abs(from[i].y - cMy); } if ( Math.abs(smx) < JSFEAT_CONSTANTS.EPSILON || Math.abs(smy) < JSFEAT_CONSTANTS.EPSILON || Math.abs(sMx) < JSFEAT_CONSTANTS.EPSILON || Math.abs(sMy) < JSFEAT_CONSTANTS.EPSILON ) return 0; smx = count / smx; smy = count / smy; sMx = count / sMx; sMy = count / sMy; t0d[0] = sMx; t0d[1] = 0; t0d[2] = -cMx * sMx; t0d[3] = 0; t0d[4] = sMy; t0d[5] = -cMy * sMy; t0d[6] = 0; t0d[7] = 0; t0d[8] = 1; t1d[0] = 1.0 / smx; t1d[1] = 0; t1d[2] = cmx; t1d[3] = 0; t1d[4] = 1.0 / smy; t1d[5] = cmy; t1d[6] = 0; t1d[7] = 0; t1d[8] = 1; // // construct system i = 81; while (--i >= 0) { LtL[i] = 0.0; } for (i = 0; i < count; ++i) { x = (to[i].x - cmx) * smx; y = (to[i].y - cmy) * smy; X = (from[i].x - cMx) * sMx; Y = (from[i].y - cMy) * sMy; LtL[0] += X * X; LtL[1] += X * Y; LtL[2] += X; LtL[6] += X * -x * X; LtL[7] += X * -x * Y; LtL[8] += X * -x; LtL[10] += Y * Y; LtL[11] += Y; LtL[15] += Y * -x * X; LtL[16] += Y * -x * Y; LtL[17] += Y * -x; LtL[20] += 1.0; LtL[24] += -x * X; LtL[25] += -x * Y; LtL[26] += -x; LtL[30] += X * X; LtL[31] += X * Y; LtL[32] += X; LtL[33] += X * -y * X; LtL[34] += X * -y * Y; LtL[35] += X * -y; LtL[40] += Y * Y; LtL[41] += Y; LtL[42] += Y * -y * X; LtL[43] += Y * -y * Y; LtL[44] += Y * -y; LtL[50] += 1.0; LtL[51] += -y * X; LtL[52] += -y * Y; LtL[53] += -y; LtL[60] += -x * X * -x * X + -y * X * -y * X; LtL[61] += -x * X * -x * Y + -y * X * -y * Y; LtL[62] += -x * X * -x + -y * X * -y; LtL[70] += -x * Y * -x * Y + -y * Y * -y * Y; LtL[71] += -x * Y * -x + -y * Y * -y; LtL[80] += -x * -x + -y * -y; } // // symmetry for (i = 0; i < 9; ++i) { for (j = 0; j < i; ++j) LtL[i * 9 + j] = LtL[j * 9 + i]; } _linalg.eigenVV(this.mLtL, this.Evec); (md[0] = evd[72]), (md[1] = evd[73]), (md[2] = evd[74]); (md[3] = evd[75]), (md[4] = evd[76]), (md[5] = evd[77]); (md[6] = evd[78]), (md[7] = evd[79]), (md[8] = evd[80]); // denormalize _matmath.multiply_3x3(model, this.T1, model); _matmath.multiply_3x3(model, model, this.T0); // set bottom right to 1.0 x = 1.0 / md[8]; md[0] *= x; md[1] *= x; md[2] *= x; md[3] *= x; md[4] *= x; md[5] *= x; md[6] *= x; md[7] *= x; md[8] = 1.0; return 1; } error(from: point_t[], to: point_t[], model: matrix_t, err: Int32Array | Float32Array, count: number): void { let i = 0; let pt0, pt1, ww = 0.0, dx = 0.0, dy = 0.0; const m = model.data; for (; i < count; ++i) { pt0 = from[i]; pt1 = to[i]; ww = 1.0 / (m[6] * pt0.x + m[7] * pt0.y + 1.0); dx = (m[0] * pt0.x + m[1] * pt0.y + m[2]) * ww - pt1.x; dy = (m[3] * pt0.x + m[4] * pt0.y + m[5]) * ww - pt1.y; err[i] = dx * dx + dy * dy; } } check_subset(from: point_t[], to: point_t[], count: number): boolean { // seems to reject good subsets actually //if( have_collinear_points(from, count) || have_collinear_points(to, count) ) { //return false; //} const _matmath = new matmath(); if (count == 4) { let negative = 0; const fp0 = from[0], fp1 = from[1], fp2 = from[2], fp3 = from[3]; const tp0 = to[0], tp1 = to[1], tp2 = to[2], tp3 = to[3]; // set1 let A11 = fp0.x, A12 = fp0.y, A13 = 1.0; let A21 = fp1.x, A22 = fp1.y, A23 = 1.0; let A31 = fp2.x, A32 = fp2.y, A33 = 1.0; let B11 = tp0.x, B12 = tp0.y, B13 = 1.0; let B21 = tp1.x, B22 = tp1.y, B23 = 1.0; let B31 = tp2.x, B32 = tp2.y, B33 = 1.0; let detA = _matmath.determinant_3x3(A11, A12, A13, A21, A22, A23, A31, A32, A33); let detB = _matmath.determinant_3x3(B11, B12, B13, B21, B22, B23, B31, B32, B33); if (detA * detB < 0) negative++; // set2 (A11 = fp1.x), (A12 = fp1.y); (A21 = fp2.x), (A22 = fp2.y); (A31 = fp3.x), (A32 = fp3.y); (B11 = tp1.x), (B12 = tp1.y); (B21 = tp2.x), (B22 = tp2.y); (B31 = tp3.x), (B32 = tp3.y); detA = _matmath.determinant_3x3(A11, A12, A13, A21, A22, A23, A31, A32, A33); detB = _matmath.determinant_3x3(B11, B12, B13, B21, B22, B23, B31, B32, B33); if (detA * detB < 0) negative++; // set3 (A11 = fp0.x), (A12 = fp0.y); (A21 = fp2.x), (A22 = fp2.y); (A31 = fp3.x), (A32 = fp3.y); (B11 = tp0.x), (B12 = tp0.y); (B21 = tp2.x), (B22 = tp2.y); (B31 = tp3.x), (B32 = tp3.y); detA = _matmath.determinant_3x3(A11, A12, A13, A21, A22, A23, A31, A32, A33); detB = _matmath.determinant_3x3(B11, B12, B13, B21, B22, B23, B31, B32, B33); if (detA * detB < 0) negative++; // set4 (A11 = fp0.x), (A12 = fp0.y); (A21 = fp1.x), (A22 = fp1.y); (A31 = fp3.x), (A32 = fp3.y); (B11 = tp0.x), (B12 = tp0.y); (B21 = tp1.x), (B22 = tp1.y); (B31 = tp3.x), (B32 = tp3.y); detA = _matmath.determinant_3x3(A11, A12, A13, A21, A22, A23, A31, A32, A33); detB = _matmath.determinant_3x3(B11, B12, B13, B21, B22, B23, B31, B32, B33); if (detA * detB < 0) negative++; if (negative != 0 && negative != 4) { return false; } } return true; // all good } } jsfeatNext.cache = cache; jsfeatNext.pyramid_t = class pyramid_t extends jsfeatNext { public levels: number; public data: any; private pyrdown: any; constructor(levels: number) { super(); this.levels = levels | 0; this.data = new Array(levels); const _imgproc = new jsfeatNext.imgproc(); this.pyrdown = _imgproc.pyrdown; } allocate(start_w: number, start_h: number, data_type: number): void { let i = this.levels; while (--i >= 0) { this.data[i] = new matrix_t(start_w >> i, start_h >> i, data_type); } } build(input: matrix_t, skip_first_level: boolean): void { if (typeof skip_first_level === "undefined") { skip_first_level = true; } // just copy data to first level let i = 2, a = input, b: any = this.data[0]; if (!skip_first_level) { let j = input.cols * input.rows; while (--j >= 0) { b.data[j] = input.data[j]; } } b = this.data[1]; this.pyrdown(a, b); for (; i < this.levels; ++i) { a = b; b = this.data[i]; this.pyrdown(a, b); } } }; jsfeatNext.transform = transform; jsfeatNext.matrix_t = matrix_t; jsfeatNext.keypoint_t = keypoint_t; jsfeatNext.fast_corners = class fast_corners extends jsfeatNext { private offsets16: Int32Array; public _threshold: number; public threshold_tab: Uint8Array; public pixel_off: Int32Array; public score_diff: Int32Array; constructor() { super(); this.offsets16 = new Int32Array([ 0, 3, 1, 3, 2, 2, 3, 1, 3, 0, 3, -1, 2, -2, 1, -3, 0, -3, -1, -3, -2, -2, -3, -1, -3, 0, -3, 1, -2, 2, -1, 3, ]); this.threshold_tab = new Uint8Array(512); this._threshold = 20; this.pixel_off = new Int32Array(25); this.score_diff = new Int32Array(25); } set_threshold(threshold: number): number { this._threshold = Math.min(Math.max(threshold, 0), 255); for (let i = -255; i <= 255; ++i) { this.threshold_tab[i + 255] = i < -this._threshold ? 1 : i > this._threshold ? 2 : 0; } return this._threshold; } detect(src: matrix_t, corners: point_t[], border: number): number { if (typeof border === "undefined") { border = 3; } const K = 8, N = 25; const img = src.data, w = src.cols, h = src.rows; let i = 0, j = 0, k = 0, vt = 0, x = 0, m3 = 0; const buf_node = this.cache.get_buffer(3 * w); const cpbuf_node = this.cache.get_buffer(((w + 1) * 3) << 2); const buf = buf_node.u8; const cpbuf = cpbuf_node.i32; const pixel = this.pixel_off; const sd = this.score_diff; const sy = Math.max(3, border); const ey = Math.min(h - 2, h - border); const sx = Math.max(3, border); const ex = Math.min(w - 3, w - border); let _count = 0, corners_cnt = 0, pt; const score_func = _cmp_score_16; const thresh_tab = this.threshold_tab; const threshold = this._threshold; let v = 0, tab = 0, d = 0, ncorners = 0, cornerpos = 0, curr = 0, ptr = 0, prev = 0, pprev = 0; let jp1 = 0, jm1 = 0, score = 0; this._cmp_offsets(pixel, w, 16); // local vars are faster? const pixel0 = pixel[0]; const pixel1 = pixel[1]; const pixel2 = pixel[2]; const pixel3 = pixel[3]; const pixel4 = pixel[4]; const pixel5 = pixel[5]; const pixel6 = pixel[6]; const pixel7 = pixel[7]; const pixel8 = pixel[8]; const pixel9 = pixel[9]; const pixel10 = pixel[10]; const pixel11 = pixel[11]; const pixel12 = pixel[12]; const pixel13 = pixel[13]; const pixel14 = pixel[14]; const pixel15 = pixel[15]; for (i = 0; i < w * 3; ++i) { buf[i] = 0; } for (i = sy; i < ey; ++i) { ptr = (i * w + sx) | 0; m3 = (i - 3) % 3; curr = (m3 * w) | 0; cornerpos = (m3 * (w + 1)) | 0; for (j = 0; j < w; ++j) buf[curr + j] = 0; ncorners = 0; if (i < ey - 1) { j = sx; for (; j < ex; ++j, ++ptr) { v = img[ptr]; tab = -v + 255; d = thresh_tab[tab + img[ptr + pixel0]] | thresh_tab[tab + img[ptr + pixel8]]; if (d == 0) { continue; } d &= thresh_tab[tab + img[ptr + pixel2]] | thresh_tab[tab + img[ptr + pixel10]]; d &= thresh_tab[tab + img[ptr + pixel4]] | thresh_tab[tab + img[ptr + pixel12]]; d &= thresh_tab[tab + img[ptr + pixel6]] | thresh_tab[tab + img[ptr + pixel14]]; if (d == 0) { continue; } d &= thresh_tab[tab + img[ptr + pixel1]] | thresh_tab[tab + img[ptr + pixel9]]; d &= thresh_tab[tab + img[ptr + pixel3]] | thresh_tab[tab + img[ptr + pixel11]]; d &= thresh_tab[tab + img[ptr + pixel5]] | thresh_tab[tab + img[ptr + pixel13]]; d &= thresh_tab[tab + img[ptr + pixel7]] | thresh_tab[tab + img[ptr + pixel15]]; if (d & 1) { vt = v - threshold; _count = 0; for (k = 0; k < N; ++k) { x = img[ptr + pixel[k]]; if (x < vt) { ++_count; if (_count > K) { ++ncorners; cpbuf[cornerpos + ncorners] = j; buf[curr + j] = score_func(img, ptr, pixel, sd, threshold); break; } } else { _count = 0; } } } if (d & 2) { vt = v + threshold; _count = 0; for (k = 0; k < N; ++k) { x = img[ptr + pixel[k]]; if (x > vt) { ++_count; if (_count > K) { ++ncorners; cpbuf[cornerpos + ncorners] = j; buf[curr + j] = score_func(img, ptr, pixel, sd, threshold); break; } } else { _count = 0; } } } } } cpbuf[cornerpos + w] = ncorners; if (i == sy) { continue; } m3 = (i - 4 + 3) % 3; prev = (m3 * w) | 0; cornerpos = (m3 * (w + 1)) | 0; m3 = (i - 5 + 3) % 3; pprev = (m3 * w) | 0; ncorners = cpbuf[cornerpos + w]; for (k = 0; k < ncorners; ++k) { j = cpbuf[cornerpos + k]; jp1 = (j + 1) | 0; jm1 = (j - 1) | 0; score = buf[prev + j]; if ( score > buf[prev + jp1] && score > buf[prev + jm1] && score > buf[pprev + jm1] && score > buf[pprev + j] && score > buf[pprev + jp1] && score > buf[curr + jm1] && score > buf[curr + j] && score > buf[curr + jp1] ) { // save corner pt = corners[corners_cnt]; (pt.x = j), (pt.y = i - 1), (pt.score = score); corners_cnt++; } } } // y loop this.cache.put_buffer(buf_node); this.cache.put_buffer(cpbuf_node); return corners_cnt; } private _cmp_offsets(pixel: Uint8Array | Int32Array, step: number, pattern_size: number): void { let k = 0; const offsets = this.offsets16; for (; k < pattern_size; ++k) { pixel[k] = offsets[k << 1] + offsets[(k << 1) + 1] * step; } for (; k < 25; ++k) { pixel[k] = pixel[k - pattern_size]; } } }; 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 <number>(<unknown>(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; fo