Newer
Older
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 numAngleCells = 180 / angleStep
const rhoMax = 1000
const getDistance = (a, b) => {
if (!(a & b)) return 0
return Math.sqrt(
(a[0] - b[0]) * (a[0] - b[0]) + (a[1] - b[1]) * (a[1] - b[1]),
)
}
function findMaxInHough(accum, threshold) {
let max = 0
// let bestRho = 0
let bestTheta = 0
for (let i = 0; i < numAngleCells; i++) {
for (let j = 0; j < accum[i].length; j++) {
if (accum[i][j] > max) {
max = accum[i][j]
// bestRho = j
bestTheta = i
}
}
}
// bestRho <<= 1
// bestRho -= rhoMax
// bestRho *= rhoStep
bestTheta *= angleStep
if (max > threshold) {
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 = 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),
}
}
const MATRIX_SIZE = 3
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()
.map(() => []),
)
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].push(interval)
return clusters
}
function clusterCoefficients(clusters, points) {
return clusters.map((rowCluster) =>
rowCluster.map((cluster) => cluster.length / points.length),
)
}
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
}
function couldBeLine(points) {
const { maxX, minX, maxY, minY } = boundingCoords(points)
const [dx, dy] = [maxX - minX, maxY - minY].map((x) => x + 0.00001)
return dy / dx > LINE_Q || dx / dy > LINE_Q
}
const RECT_THRESHOLD_CENTER = 0.05
const RECT_THRESHOLD_SIDE_VARIANCE = 0.12
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 < 3; i++) {
for (let j = 0; j < 3; j++) {
if (!(i === j && j === 1)) {
maxC = Math.max(maxC, matrixCoefficients[i][j])
minC = Math.min(minC, matrixCoefficients[i][j])
}
}
}
console.log(matrixCoefficients)
(matrixCoefficients[1][1] < RECT_THRESHOLD_CENTER &&
maxC - minC < RECT_THRESHOLD_SIDE_VARIANCE) ||
(maxC - minC < RECT_THRESHOLD_SIDE_VARIANCE * 2 &&
matrixCoefficients[1][1] === 0)
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) {
if (!(points && points.length)) return {}
const accum = Array(numAngleCells)
rhoStep: points.length > 50 ? 50 : 5,
}
points.forEach((x) => constructHoughAccumulator(houghConfig, accum, ...x))
const angle = findMaxInHough(accum, points.length - 1)
if (angle !== undefined) {
return {
shape: Shapes.line,
function recognizeFromPoints(points) {
const rectDetectData = couldBeRect(points)
if (rectDetectData) {
return recognizeRect(points, rectDetectData)
} else if (couldBeLine(points)) {
return recognizeLine(points)
}
return {}
}