479 lines
16 KiB
JavaScript
479 lines
16 KiB
JavaScript
|
|
import { bgrToLab } from './freqLayerPrep';
|
|||
|
|
import { getMaskSegmentRuntimeConfig } from './maskSegmentRuntime';
|
|||
|
|
import { getSemanticColorByName } from './maskSemanticPalette';
|
|||
|
|
/** 非墙像素在 wallSubLabels 中的占位值 */
|
|||
|
|
export const WALL_SUB_LABEL_NONE = 255;
|
|||
|
|
function maskCfg() {
|
|||
|
|
return getMaskSegmentRuntimeConfig().mask;
|
|||
|
|
}
|
|||
|
|
function bboxToPolygon(bbox) {
|
|||
|
|
return [
|
|||
|
|
{ x: bbox.x, y: bbox.y },
|
|||
|
|
{ x: bbox.x + bbox.w, y: bbox.y },
|
|||
|
|
{ x: bbox.x + bbox.w, y: bbox.y + bbox.h },
|
|||
|
|
{ x: bbox.x, y: bbox.y + bbox.h },
|
|||
|
|
];
|
|||
|
|
}
|
|||
|
|
function computeLabChromaMaps(originBgr, cols, rows) {
|
|||
|
|
const pixelCount = cols * rows;
|
|||
|
|
const aMap = new Uint8Array(pixelCount);
|
|||
|
|
const bMap = new Uint8Array(pixelCount);
|
|||
|
|
for (let i = 0; i < pixelCount; i++) {
|
|||
|
|
const s = i * 3;
|
|||
|
|
const lab = bgrToLab(originBgr[s], originBgr[s + 1], originBgr[s + 2]);
|
|||
|
|
aMap[i] = lab.a;
|
|||
|
|
bMap[i] = lab.b;
|
|||
|
|
}
|
|||
|
|
return { aMap, bMap };
|
|||
|
|
}
|
|||
|
|
function chromaMag(a, b) {
|
|||
|
|
const da = a - 128;
|
|||
|
|
const db = b - 128;
|
|||
|
|
return Math.sqrt(da * da + db * db);
|
|||
|
|
}
|
|||
|
|
function chromaDistSq(a0, b0, a1, b1) {
|
|||
|
|
const da = a0 - a1;
|
|||
|
|
const db = b0 - b1;
|
|||
|
|
return da * da + db * db;
|
|||
|
|
}
|
|||
|
|
/** 白/灰墙与蓝/有色墙、或两种不同色相墙之间强制视为材质边界 */
|
|||
|
|
function isCrossMaterialBoundary(a0, b0, a1, b1, neutralChromaMax) {
|
|||
|
|
const m0 = chromaMag(a0, b0);
|
|||
|
|
const m1 = chromaMag(a1, b1);
|
|||
|
|
const neutralGate = neutralChromaMax * 2.2;
|
|||
|
|
if (m0 <= neutralChromaMax && m1 > neutralGate)
|
|||
|
|
return true;
|
|||
|
|
if (m1 <= neutralChromaMax && m0 > neutralGate)
|
|||
|
|
return true;
|
|||
|
|
if (m0 > neutralChromaMax && m1 > neutralChromaMax) {
|
|||
|
|
const hue0 = Math.atan2(b0 - 128, a0 - 128);
|
|||
|
|
const hue1 = Math.atan2(b1 - 128, a1 - 128);
|
|||
|
|
let dh = Math.abs(hue0 - hue1);
|
|||
|
|
if (dh > Math.PI)
|
|||
|
|
dh = 2 * Math.PI - dh;
|
|||
|
|
if (dh > Math.PI / 4)
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
function canMergeWallPixels(refA, refB, na, nb, distSqThreshold, neutralChromaMax) {
|
|||
|
|
if (isCrossMaterialBoundary(refA, refB, na, nb, neutralChromaMax)) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
return chromaDistSq(refA, refB, na, nb) <= distSqThreshold;
|
|||
|
|
}
|
|||
|
|
function findWallSemanticIndex() {
|
|||
|
|
const colors = maskCfg().semanticColors;
|
|||
|
|
return colors.findIndex(entry => entry.name === 'wall');
|
|||
|
|
}
|
|||
|
|
function isWallPixel(labels, baseboardBinary, wallIdx, i) {
|
|||
|
|
if (baseboardBinary[i])
|
|||
|
|
return false;
|
|||
|
|
return labels[i] === wallIdx;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* 4-连通区域生长:与连通域色度均值比较,避免链式桥接;中性/有色墙强制分界。
|
|||
|
|
*/
|
|||
|
|
function labelWallComponents(labels, baseboardBinary, wallIdx, aMap, bMap, cols, rows, distSqThreshold, neutralChromaMax) {
|
|||
|
|
const pixelCount = cols * rows;
|
|||
|
|
const compLabels = new Int32Array(pixelCount);
|
|||
|
|
compLabels.fill(-1);
|
|||
|
|
let compCount = 0;
|
|||
|
|
const queue = new Int32Array(pixelCount);
|
|||
|
|
for (let y = 0; y < rows; y++) {
|
|||
|
|
for (let x = 0; x < cols; x++) {
|
|||
|
|
const i = y * cols + x;
|
|||
|
|
if (!isWallPixel(labels, baseboardBinary, wallIdx, i))
|
|||
|
|
continue;
|
|||
|
|
if (compLabels[i] >= 0)
|
|||
|
|
continue;
|
|||
|
|
const compId = compCount;
|
|||
|
|
compCount += 1;
|
|||
|
|
const seedA = aMap[i];
|
|||
|
|
const seedB = bMap[i];
|
|||
|
|
let sumA = seedA;
|
|||
|
|
let sumB = seedB;
|
|||
|
|
let count = 1;
|
|||
|
|
let head = 0;
|
|||
|
|
let tail = 0;
|
|||
|
|
queue[tail++] = i;
|
|||
|
|
compLabels[i] = compId;
|
|||
|
|
while (head < tail) {
|
|||
|
|
const ci = queue[head++];
|
|||
|
|
const cx = ci % cols;
|
|||
|
|
const cy = (ci / cols) | 0;
|
|||
|
|
const meanA = sumA / count;
|
|||
|
|
const meanB = sumB / count;
|
|||
|
|
const neighbors = [ci - 1, ci + 1, ci - cols, ci + cols];
|
|||
|
|
for (const ni of neighbors) {
|
|||
|
|
if (ni < 0 || ni >= pixelCount)
|
|||
|
|
continue;
|
|||
|
|
const nx = ni % cols;
|
|||
|
|
if (Math.abs(nx - cx) > 1)
|
|||
|
|
continue;
|
|||
|
|
if (!isWallPixel(labels, baseboardBinary, wallIdx, ni))
|
|||
|
|
continue;
|
|||
|
|
if (compLabels[ni] >= 0)
|
|||
|
|
continue;
|
|||
|
|
const na = aMap[ni];
|
|||
|
|
const nb = bMap[ni];
|
|||
|
|
const stepOk = canMergeWallPixels(aMap[ci], bMap[ci], na, nb, distSqThreshold * 1.8, neutralChromaMax);
|
|||
|
|
const meanOk = canMergeWallPixels(meanA, meanB, na, nb, distSqThreshold, neutralChromaMax);
|
|||
|
|
const seedOk = canMergeWallPixels(seedA, seedB, na, nb, distSqThreshold * 3, neutralChromaMax);
|
|||
|
|
if (!stepOk || !meanOk || !seedOk) {
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
compLabels[ni] = compId;
|
|||
|
|
sumA += na;
|
|||
|
|
sumB += nb;
|
|||
|
|
count += 1;
|
|||
|
|
queue[tail++] = ni;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return { compLabels, compCount };
|
|||
|
|
}
|
|||
|
|
function computeComponentStats(compLabels, compCount, cols, rows) {
|
|||
|
|
const stats = Array.from({ length: compCount }, (_, label) => ({
|
|||
|
|
label,
|
|||
|
|
area: 0,
|
|||
|
|
bbox: { x: cols, y: rows, w: 0, h: 0 },
|
|||
|
|
}));
|
|||
|
|
for (let y = 0; y < rows; y++) {
|
|||
|
|
for (let x = 0; x < cols; x++) {
|
|||
|
|
const i = y * cols + x;
|
|||
|
|
const comp = compLabels[i];
|
|||
|
|
if (comp < 0)
|
|||
|
|
continue;
|
|||
|
|
const s = stats[comp];
|
|||
|
|
s.area += 1;
|
|||
|
|
if (x < s.bbox.x)
|
|||
|
|
s.bbox.x = x;
|
|||
|
|
if (y < s.bbox.y)
|
|||
|
|
s.bbox.y = y;
|
|||
|
|
const right = x + 1;
|
|||
|
|
const bottom = y + 1;
|
|||
|
|
if (right > s.bbox.x + s.bbox.w)
|
|||
|
|
s.bbox.w = right - s.bbox.x;
|
|||
|
|
if (bottom > s.bbox.y + s.bbox.h)
|
|||
|
|
s.bbox.h = bottom - s.bbox.y;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return stats;
|
|||
|
|
}
|
|||
|
|
function computeComponentChromaMeans(compLabels, aMap, bMap, compCount, cols, rows) {
|
|||
|
|
const sumA = new Float64Array(compCount);
|
|||
|
|
const sumB = new Float64Array(compCount);
|
|||
|
|
const counts = new Float64Array(compCount);
|
|||
|
|
const pixelCount = cols * rows;
|
|||
|
|
for (let i = 0; i < pixelCount; i++) {
|
|||
|
|
const comp = compLabels[i];
|
|||
|
|
if (comp < 0)
|
|||
|
|
continue;
|
|||
|
|
sumA[comp] += aMap[i];
|
|||
|
|
sumB[comp] += bMap[i];
|
|||
|
|
counts[comp] += 1;
|
|||
|
|
}
|
|||
|
|
const meanA = new Float64Array(compCount);
|
|||
|
|
const meanB = new Float64Array(compCount);
|
|||
|
|
for (let c = 0; c < compCount; c++) {
|
|||
|
|
if (counts[c] > 0) {
|
|||
|
|
meanA[c] = sumA[c] / counts[c];
|
|||
|
|
meanB[c] = sumB[c] / counts[c];
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
meanA[c] = 128;
|
|||
|
|
meanB[c] = 128;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return { meanA, meanB };
|
|||
|
|
}
|
|||
|
|
function mergeSmallComponents(compLabels, stats, aMap, bMap, cols, rows, minArea, distSqThreshold, neutralChromaMax) {
|
|||
|
|
const compCount = stats.length;
|
|||
|
|
const { meanA, meanB } = computeComponentChromaMeans(compLabels, aMap, bMap, compCount, cols, rows);
|
|||
|
|
const adjacency = new Map();
|
|||
|
|
const addEdge = (a, b) => {
|
|||
|
|
if (a === b || a < 0 || b < 0)
|
|||
|
|
return;
|
|||
|
|
let map = adjacency.get(a);
|
|||
|
|
if (!map) {
|
|||
|
|
map = new Map();
|
|||
|
|
adjacency.set(a, map);
|
|||
|
|
}
|
|||
|
|
map.set(b, (map.get(b) ?? 0) + 1);
|
|||
|
|
};
|
|||
|
|
for (let y = 0; y < rows; y++) {
|
|||
|
|
for (let x = 0; x < cols; x++) {
|
|||
|
|
const i = y * cols + x;
|
|||
|
|
const a = compLabels[i];
|
|||
|
|
if (a < 0)
|
|||
|
|
continue;
|
|||
|
|
if (x + 1 < cols) {
|
|||
|
|
const b = compLabels[i + 1];
|
|||
|
|
if (b >= 0)
|
|||
|
|
addEdge(a, b);
|
|||
|
|
}
|
|||
|
|
if (y + 1 < rows) {
|
|||
|
|
const b = compLabels[i + cols];
|
|||
|
|
if (b >= 0)
|
|||
|
|
addEdge(a, b);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
const remap = new Int32Array(compCount);
|
|||
|
|
for (let i = 0; i < compCount; i++)
|
|||
|
|
remap[i] = i;
|
|||
|
|
const find = (x) => {
|
|||
|
|
while (remap[x] !== x) {
|
|||
|
|
remap[x] = remap[remap[x]];
|
|||
|
|
x = remap[x];
|
|||
|
|
}
|
|||
|
|
return x;
|
|||
|
|
};
|
|||
|
|
const union = (a, b) => {
|
|||
|
|
const ra = find(a);
|
|||
|
|
const rb = find(b);
|
|||
|
|
if (ra === rb)
|
|||
|
|
return;
|
|||
|
|
const areaA = stats[ra].area;
|
|||
|
|
const areaB = stats[rb].area;
|
|||
|
|
if (areaA >= areaB) {
|
|||
|
|
remap[rb] = ra;
|
|||
|
|
stats[ra].area += stats[rb].area;
|
|||
|
|
stats[rb].area = 0;
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
remap[ra] = rb;
|
|||
|
|
stats[rb].area += stats[ra].area;
|
|||
|
|
stats[ra].area = 0;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
for (let c = 0; c < compCount; c++) {
|
|||
|
|
if (stats[c].area >= minArea)
|
|||
|
|
continue;
|
|||
|
|
const neighbors = adjacency.get(c);
|
|||
|
|
if (!neighbors || neighbors.size === 0)
|
|||
|
|
continue;
|
|||
|
|
let bestNeighbor = -1;
|
|||
|
|
let bestBorder = 0;
|
|||
|
|
for (const [nb, border] of neighbors) {
|
|||
|
|
if (border > bestBorder) {
|
|||
|
|
bestBorder = border;
|
|||
|
|
bestNeighbor = nb;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (bestNeighbor >= 0) {
|
|||
|
|
if (!canMergeWallPixels(meanA[c], meanB[c], meanA[bestNeighbor], meanB[bestNeighbor], distSqThreshold, neutralChromaMax)) {
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
union(c, bestNeighbor);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
const pixelCount = cols * rows;
|
|||
|
|
for (let i = 0; i < pixelCount; i++) {
|
|||
|
|
const c = compLabels[i];
|
|||
|
|
if (c < 0)
|
|||
|
|
continue;
|
|||
|
|
compLabels[i] = find(c);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
function relabelComponentsContiguous(compLabels, cols, rows) {
|
|||
|
|
const pixelCount = cols * rows;
|
|||
|
|
const remap = new Map();
|
|||
|
|
const out = new Int32Array(pixelCount);
|
|||
|
|
out.fill(-1);
|
|||
|
|
for (let i = 0; i < pixelCount; i++) {
|
|||
|
|
const c = compLabels[i];
|
|||
|
|
if (c < 0)
|
|||
|
|
continue;
|
|||
|
|
let next = remap.get(c);
|
|||
|
|
if (next === undefined) {
|
|||
|
|
next = remap.size;
|
|||
|
|
remap.set(c, next);
|
|||
|
|
}
|
|||
|
|
out[i] = next;
|
|||
|
|
}
|
|||
|
|
const compCount = remap.size;
|
|||
|
|
const stats = computeComponentStats(out, compCount, cols, rows);
|
|||
|
|
return { labels: out, compCount, stats };
|
|||
|
|
}
|
|||
|
|
function buildPickMapAfterWallSplit(labels, baseboardBinary, wallIdx, wallSubLabels, indexToName, nameToId, cols, rows) {
|
|||
|
|
const pixelCount = cols * rows;
|
|||
|
|
const pick = new Uint8Array(pixelCount);
|
|||
|
|
const baseboardName = 'baseboard';
|
|||
|
|
const baseboardId = nameToId.get(baseboardName);
|
|||
|
|
const baseboardCode = baseboardId === undefined ? 0 : baseboardId + 1;
|
|||
|
|
for (let i = 0; i < pixelCount; i++) {
|
|||
|
|
if (baseboardBinary[i]) {
|
|||
|
|
if (baseboardCode > 0) {
|
|||
|
|
pick[i] = baseboardCode;
|
|||
|
|
}
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
if (labels[i] === wallIdx && wallSubLabels[i] !== WALL_SUB_LABEL_NONE) {
|
|||
|
|
const wallName = `wall-${wallSubLabels[i] + 1}`;
|
|||
|
|
const regionId = nameToId.get(wallName);
|
|||
|
|
if (regionId !== undefined) {
|
|||
|
|
pick[i] = regionId + 1;
|
|||
|
|
}
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
const semanticIndex = labels[i];
|
|||
|
|
if (semanticIndex === 255)
|
|||
|
|
continue;
|
|||
|
|
const name = indexToName[semanticIndex];
|
|||
|
|
if (!name)
|
|||
|
|
continue;
|
|||
|
|
const regionId = nameToId.get(name);
|
|||
|
|
if (regionId !== undefined) {
|
|||
|
|
pick[i] = regionId + 1;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return pick;
|
|||
|
|
}
|
|||
|
|
function dilatePickBuffer1px(pick, cols, rows) {
|
|||
|
|
const pixelCount = cols * rows;
|
|||
|
|
const dst = new Uint8Array(pixelCount);
|
|||
|
|
dst.set(pick);
|
|||
|
|
for (let y = 1; y < rows - 1; y++) {
|
|||
|
|
for (let x = 1; x < cols - 1; x++) {
|
|||
|
|
const i = y * cols + x;
|
|||
|
|
if (pick[i] !== 0)
|
|||
|
|
continue;
|
|||
|
|
const n = [
|
|||
|
|
pick[(y - 1) * cols + (x - 1)],
|
|||
|
|
pick[(y - 1) * cols + x],
|
|||
|
|
pick[(y - 1) * cols + (x + 1)],
|
|||
|
|
pick[y * cols + (x - 1)],
|
|||
|
|
pick[y * cols + (x + 1)],
|
|||
|
|
pick[(y + 1) * cols + (x - 1)],
|
|||
|
|
pick[(y + 1) * cols + x],
|
|||
|
|
pick[(y + 1) * cols + (x + 1)],
|
|||
|
|
];
|
|||
|
|
const counts = {};
|
|||
|
|
for (let k = 0; k < 8; k++) {
|
|||
|
|
const code = n[k];
|
|||
|
|
if (code !== 0) {
|
|||
|
|
counts[code] = (counts[code] ?? 0) + 1;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
for (const codeStr of Object.keys(counts)) {
|
|||
|
|
const code = Number(codeStr);
|
|||
|
|
if (counts[code] >= 4) {
|
|||
|
|
dst[i] = code;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return dst;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* 在语义分割完成后,将 wall 区域按原图纹理特征细分为 wall-1、wall-2…
|
|||
|
|
*/
|
|||
|
|
export function splitWallRegionsByTexture(result, originBgr, cols, rows, minArea) {
|
|||
|
|
const cfg = maskCfg();
|
|||
|
|
if (!cfg.splitWalls) {
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
const wallRegion = result.regions.find(reg => reg.name === 'wall');
|
|||
|
|
if (!wallRegion) {
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
const wallIdx = findWallSemanticIndex();
|
|||
|
|
if (wallIdx < 0) {
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
const { labels, baseboardBinary, regions } = result;
|
|||
|
|
const pixelCount = cols * rows;
|
|||
|
|
if (originBgr.length < pixelCount * 3) {
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
const { aMap: rawA, bMap: rawB } = computeLabChromaMaps(originBgr, cols, rows);
|
|||
|
|
const distSqThreshold = cfg.splitWallsColorDistSq;
|
|||
|
|
const neutralChromaMax = cfg.splitWallsNeutralChromaMax;
|
|||
|
|
const minAreaFloor = Math.max(minArea, Math.floor(cols * rows * cfg.splitWallsMinAreaRatio));
|
|||
|
|
const { compLabels: rawCompLabels, compCount: rawCount } = labelWallComponents(labels, baseboardBinary, wallIdx, rawA, rawB, cols, rows, distSqThreshold, neutralChromaMax);
|
|||
|
|
if (rawCount === 0) {
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
let stats = computeComponentStats(rawCompLabels, rawCount, cols, rows);
|
|||
|
|
mergeSmallComponents(rawCompLabels, stats, rawA, rawB, cols, rows, minAreaFloor, distSqThreshold, neutralChromaMax);
|
|||
|
|
const { labels: finalCompLabels, compCount, stats: finalStats } = relabelComponentsContiguous(rawCompLabels, cols, rows);
|
|||
|
|
if (compCount === 0) {
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
// 按面积降序,截断至 maxCount
|
|||
|
|
const ranked = finalStats
|
|||
|
|
.map((s, idx) => ({ ...s, origIdx: idx }))
|
|||
|
|
.filter(s => s.area > 0)
|
|||
|
|
.sort((a, b) => b.area - a.area)
|
|||
|
|
.slice(0, cfg.splitWallsMaxCount);
|
|||
|
|
const rankMap = new Map();
|
|||
|
|
ranked.forEach((s, rank) => {
|
|||
|
|
rankMap.set(s.origIdx, rank);
|
|||
|
|
});
|
|||
|
|
const wallSubLabels = new Uint8Array(pixelCount);
|
|||
|
|
wallSubLabels.fill(WALL_SUB_LABEL_NONE);
|
|||
|
|
for (let i = 0; i < pixelCount; i++) {
|
|||
|
|
const c = finalCompLabels[i];
|
|||
|
|
if (c < 0)
|
|||
|
|
continue;
|
|||
|
|
const rank = rankMap.get(c);
|
|||
|
|
if (rank === undefined)
|
|||
|
|
continue;
|
|||
|
|
wallSubLabels[i] = rank;
|
|||
|
|
}
|
|||
|
|
const wallRef = getSemanticColorByName('wall');
|
|||
|
|
const wallHex = wallRef?.hex ?? wallRegion.hex;
|
|||
|
|
const wallColor = wallRef?.bgr ?? wallRegion.color;
|
|||
|
|
const nonWallRegions = regions.filter(reg => reg.name !== 'wall');
|
|||
|
|
const wallSubRegions = ranked.map((s, rank) => {
|
|||
|
|
const bbox = s.bbox;
|
|||
|
|
const poly = bboxToPolygon(bbox);
|
|||
|
|
return {
|
|||
|
|
id: 0,
|
|||
|
|
name: `wall-${rank + 1}`,
|
|||
|
|
hex: wallHex,
|
|||
|
|
color: { ...wallColor },
|
|||
|
|
polygons: [poly],
|
|||
|
|
outlinePolygons: [poly],
|
|||
|
|
bbox,
|
|||
|
|
area: s.area,
|
|||
|
|
};
|
|||
|
|
});
|
|||
|
|
const mergedRegions = [...nonWallRegions, ...wallSubRegions];
|
|||
|
|
mergedRegions.sort((a, b) => b.area - a.area);
|
|||
|
|
mergedRegions.forEach((reg, index) => {
|
|||
|
|
reg.id = index;
|
|||
|
|
});
|
|||
|
|
const nameToId = new Map(mergedRegions.map(reg => [reg.name, reg.id]));
|
|||
|
|
const indexToName = cfg.semanticColors.map(entry => entry.name);
|
|||
|
|
const pickRaw = buildPickMapAfterWallSplit(labels, baseboardBinary, wallIdx, wallSubLabels, indexToName, nameToId, cols, rows);
|
|||
|
|
const pickBuffer = dilatePickBuffer1px(pickRaw, cols, rows);
|
|||
|
|
for (const reg of mergedRegions) {
|
|||
|
|
if (!/^wall-\d+$/.test(reg.name))
|
|||
|
|
continue;
|
|||
|
|
const subIdx = Number(reg.name.slice(5)) - 1;
|
|||
|
|
let area = 0;
|
|||
|
|
for (let i = 0; i < pixelCount; i++) {
|
|||
|
|
if (wallSubLabels[i] === subIdx)
|
|||
|
|
area += 1;
|
|||
|
|
}
|
|||
|
|
reg.area = area;
|
|||
|
|
}
|
|||
|
|
return {
|
|||
|
|
regions: mergedRegions,
|
|||
|
|
pickMap: { buffer: pickBuffer, cols, rows },
|
|||
|
|
labels,
|
|||
|
|
baseboardBinary,
|
|||
|
|
segCols: cols,
|
|||
|
|
segRows: rows,
|
|||
|
|
wallSubLabels,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
export function isWallSubRegionName(name) {
|
|||
|
|
return /^wall-\d+$/.test(name);
|
|||
|
|
}
|
|||
|
|
//# sourceMappingURL=wallTextureSplit.js.map
|