Newer
Older
return Math.sqrt(
(a[0] - b[0]) * (a[0] - b[0]) + (a[1] - b[1]) * (a[1] - b[1]),
)
}
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// function dtor(a) {
// return (Math.PI * a) / 180
// }
// function cos(a) {
// return Math.cos(dtor(a))
// }
// function sin(a) {
// return Math.sin(dtor(a))
// }
// const angleStep = 90
// const numAngleCells = 180 / angleStep
// const rhoMax = 10000
// function findMaxInHough(accum, threshold) {
// let max = 0
// // let bestRho = 0
// let bestTheta = 0
// for (let i = 0; i < numAngleCells; i++) {
// if (!accum[i]) continue
// for (let j = 0; j < accum[i].length; j++) {
// if (accum[i][j] > max) {
// max = accum[i][j]
// bestTheta = i
// }
// }
// }
// bestTheta *= angleStep
// if (max > threshold) {
// return bestTheta
// }
// return undefined
// }
// function constructHoughAccumulator(config, accumulator, x, y) {
// for (let thetaIndex = 0; thetaIndex < numAngleCells; thetaIndex++) {
// const theta = thetaIndex * angleStep
// let rho = x * cos(theta) + y * sin(theta)
// rho = Math.floor(rho)
// rho += rhoMax
// rho >>= 1
// rho /= config.rhoStep
// rho = Math.floor(rho)
// if (accumulator[thetaIndex] == undefined) accumulator[thetaIndex] = []
// if (accumulator[thetaIndex][rho] == undefined) {
// accumulator[thetaIndex][rho] = 1
// } else {
// accumulator[thetaIndex][rho]++
// }
// }
// }
function boundingCoords(points) {
const xs = points.map((p) => p[0])
const ys = points.map((p) => p[1])
return {
maxX: Math.max(...xs),
minX: Math.min(...xs),
maxY: Math.max(...ys),
minY: Math.min(...ys),
}
}
function vectorLength([x, y]) {
return Math.hypot(x, y)
}
function diffVector([x0, y0], [x1, y1]) {
return [x0 - x1, y0 - y1]
}
function angleBetweenVectors(p1, p2) {
const [[x0, y0], [x1, y1]] = [p1, p2]
return Math.acos((x0 * x1 + y0 * y1) / (vectorLength(p1) * vectorLength(p2)))
}
const LINE_ANGLE_THRESHOLD = Math.PI / 6
const VECTOR_LEN_THRESHOLD_FRACTION = 0.3
const vectorThreshold = Math.floor(
points.length * VECTOR_LEN_THRESHOLD_FRACTION,
)
const pivot = points[0]
let cumulativeThreshold = 0
for (let i = 2; i < points.length; i++) {
const p1 = points[i - 1]
const p2 = points[i]
const d1 = diffVector(pivot, p1)
const d2 = diffVector(p1, p2)
const d2Len = vectorLength(d2)
const angle = angleBetweenVectors(d1, d2)
if (Math.abs(angle) > LINE_ANGLE_THRESHOLD) {
if (cumulativeThreshold < vectorThreshold && d2Len < vectorThreshold) {
cumulativeThreshold += d2Len
continue
}
function mArray(min, max) {
const d = max - min
const centerSegmentSize = d * MATRIX_CENTER_RATIO
const smallStep = (d - centerSegmentSize) / 2
const p = [min + smallStep, min + smallStep + centerSegmentSize, max]
return p
}
function getCluster([x, y], xBounds, yBounds) {
return {
x: xBounds.findIndex((bound) => x <= bound),
y: yBounds.findIndex((bound) => y <= bound),
}
}
function computeClusters(points, xBounds, yBounds) {
const clusters = Array(MATRIX_SIZE)
.fill(0)
.map(() =>
Array(MATRIX_SIZE)
.fill()
const intervals = points.map((point, i) => ({
point,
dist: getDistance(point, points[i + 1]),
}))
intervals.forEach((interval) => {
const { x, y } = getCluster(interval.point, xBounds, yBounds)
clusters[x][y].arr.push(interval)
clusters[x][y].sum += interval.dist
totalSum += interval.dist
function clusterCoefficients(clusters) {
return clusters.arr.map((rowCluster) =>
rowCluster.map((cluster) => cluster.sum / clusters.totalSum),
export function computeMatrixCoefficients(points, boundingRect) {
const { maxX, minX, maxY, minY } = boundingRect
const xBounds = mArray(minX, maxX)
const yBounds = mArray(minY, maxY)
const clusters = computeClusters(points, xBounds, yBounds)
const coefficients = clusterCoefficients(clusters, points)
return coefficients
}
const RECT_THRESHOLD_CENTER = 0
const RECT_THRESHOLD_SIDE_VARIANCE = 0.25
function couldBeRect(points) {
if (points.length < 4) return false
const boundingRect = boundingCoords(points)
const matrixCoefficients = computeMatrixCoefficients(points, boundingRect)
let [maxC, minC] = [0, 1]
for (let i = 0; i < MATRIX_SIZE; i++) {
for (let j = 0; j < MATRIX_SIZE; j++) {
if (!(i === j && j === 1)) {
maxC = Math.max(maxC, matrixCoefficients[i][j])
minC = Math.min(minC, matrixCoefficients[i][j])
}
}
}
if (
matrixCoefficients[1][1] <= RECT_THRESHOLD_CENTER &&
maxC - minC < RECT_THRESHOLD_SIDE_VARIANCE
return { coefficients: matrixCoefficients, boundingRect }
function recognizeRect(points, rectDetectionData) {
const { minX, minY, maxX, maxY } = rectDetectionData.boundingRect
return {
boundingRect: rectDetectionData.boundingRect,
boundingPoints: [
[minX, minY],
[minX, maxY],
[maxX, maxY],
[maxX, minY],
[minX, minY],
],
shape: Shapes.rectangle,
points,
}
}
function recognizeLine(points) {
const [p1, p2] = [points[0], points[points.length - 1]]
const angle =
(angleBetweenVectors(diffVector(p2, p1), [1, 0]) / Math.PI) * 180
return {
shape: Shapes.line,
angle,
points,
length: getDistance(p1, p2),
lastPoint: p2.slice(0, 2),
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
// const MAX_RHO_STEP = 50
// const MIN_RHO_STEP = 5
// function rhoStepForPoints(points) {
// return points.length > 50 ? MAX_RHO_STEP : MIN_RHO_STEP
// }
// function recognizeLineHough(points) {
// if (!(points && points.length)) return {}
// const accum = Array(numAngleCells)
// const houghConfig = {
// rhoStep: rhoStepForPoints(points),
// }
// points.forEach((x) => constructHoughAccumulator(houghConfig, accum, ...x))
// const angle = findMaxInHough(accum, points.length - 10)
// if (angle !== undefined) {
// return {
// shape: Shapes.line,
// angle: 90 - angle,
// hough: accum,
// points,
// }
// }
// return {}
// }
function recognizeFromPoints(points) {
const rectDetectData = couldBeRect(points)
if (rectDetectData) {
return recognizeRect(points, rectDetectData)
} else if (couldBeLine(points)) {
return recognizeLine(points)
}
return {}
}