Clean low-confidence isolated bead colors
This commit is contained in:
+62
-5
@@ -209,6 +209,7 @@ function sampleDominantRegions(
|
||||
drawFittedImage(ctx, image, sample.width, sample.height, fitMode, cropZoom, cropX, cropY);
|
||||
const source = ctx.getImageData(0, 0, sample.width, sample.height).data;
|
||||
const result = new Uint8ClampedArray(width * height * 4);
|
||||
const confidence = new Float32Array(width * height);
|
||||
const localMergeThreshold = 0.04;
|
||||
|
||||
for (let row = 0; row < height; row++) {
|
||||
@@ -247,9 +248,54 @@ function sampleDominantRegions(
|
||||
result[targetIndex + 1] = representative.rgb[1];
|
||||
result[targetIndex + 2] = representative.rgb[2];
|
||||
result[targetIndex + 3] = 255;
|
||||
confidence[row * width + column] = dominant.count / (scale * scale);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return { data: result, confidence };
|
||||
}
|
||||
|
||||
function cleanLowConfidenceIsolatedColors(
|
||||
colors: BeadColor[],
|
||||
confidence: Float32Array,
|
||||
width: number,
|
||||
height: number,
|
||||
) {
|
||||
const cleaned = [...colors];
|
||||
let changed = 0;
|
||||
const offsets = [-1, 0, 1];
|
||||
for (let row = 0; row < height; row++) {
|
||||
for (let column = 0; column < width; column++) {
|
||||
const index = row * width + column;
|
||||
const current = colors[index];
|
||||
if (confidence[index] >= 0.625) continue;
|
||||
const chroma = Math.hypot(current.lab[1], current.lab[2]);
|
||||
const protectedDetail = current.lab[0] < 0.32 || current.lab[0] > 0.96 || chroma > 0.14;
|
||||
if (protectedDetail) continue;
|
||||
|
||||
const neighborCounts = new Map<string, { color: BeadColor; count: number }>();
|
||||
let neighborTotal = 0;
|
||||
for (const rowOffset of offsets) {
|
||||
for (const columnOffset of offsets) {
|
||||
if (rowOffset === 0 && columnOffset === 0) continue;
|
||||
const neighborRow = row + rowOffset;
|
||||
const neighborColumn = column + columnOffset;
|
||||
if (neighborRow < 0 || neighborColumn < 0 || neighborRow >= height || neighborColumn >= width) continue;
|
||||
const neighbor = colors[neighborRow * width + neighborColumn];
|
||||
const entry = neighborCounts.get(neighbor.code) ?? { color: neighbor, count: 0 };
|
||||
entry.count += 1;
|
||||
neighborCounts.set(neighbor.code, entry);
|
||||
neighborTotal += 1;
|
||||
}
|
||||
}
|
||||
const majority = [...neighborCounts.values()].sort((a, b) => b.count - a.count)[0];
|
||||
if (!majority || majority.color.code === current.code) continue;
|
||||
const requiredCount = neighborTotal >= 7 ? 5 : Math.max(2, Math.ceil(neighborTotal * 0.67));
|
||||
if (majority.count < requiredCount) continue;
|
||||
cleaned[index] = majority.color;
|
||||
changed += 1;
|
||||
}
|
||||
}
|
||||
return { colors: cleaned, changed };
|
||||
}
|
||||
|
||||
function drawFittedImage(
|
||||
@@ -367,8 +413,11 @@ export default function Home() {
|
||||
const width = Math.max(8, Math.min(256, requestedWidth));
|
||||
const height = Math.max(8, Math.min(256, requestedHeight));
|
||||
let data: Uint8ClampedArray;
|
||||
let dominantConfidence: Float32Array | null = null;
|
||||
if (samplingStrategy === "dominant") {
|
||||
data = sampleDominantRegions(image, width, height, fitMode, crop.zoom, crop.x, crop.y);
|
||||
const sampled = sampleDominantRegions(image, width, height, fitMode, crop.zoom, crop.x, crop.y);
|
||||
data = sampled.data;
|
||||
dominantConfidence = sampled.confidence;
|
||||
} else {
|
||||
const sample = document.createElement("canvas");
|
||||
sample.width = width;
|
||||
@@ -382,15 +431,23 @@ export default function Home() {
|
||||
const { pixelKeys, clusters, binClusters } = mergePerceptualColors(data);
|
||||
const limitedPalette = chooseDistinctMardColors(clusters, Math.max(2, Math.min(colorLimit, PALETTE.length)));
|
||||
const clusterMatches = clusters.map((cluster) => nearestColor(cluster.lab, limitedPalette));
|
||||
const converted = pixelKeys.map((key) => ({ color: clusterMatches[binClusters.get(key) ?? 0] }));
|
||||
let matchedColors = pixelKeys.map((key) => clusterMatches[binClusters.get(key) ?? 0]);
|
||||
let cleanedCount = 0;
|
||||
if (dominantConfidence) {
|
||||
const cleaned = cleanLowConfidenceIsolatedColors(matchedColors, dominantConfidence, width, height);
|
||||
matchedColors = cleaned.colors;
|
||||
cleanedCount = cleaned.changed;
|
||||
}
|
||||
const converted = matchedColors.map((color) => ({ color }));
|
||||
setGridWidth(width);
|
||||
setGridHeight(height);
|
||||
setRequestedWidth(width);
|
||||
setRequestedHeight(height);
|
||||
setPixels(converted);
|
||||
setSelectedCodes(new Set());
|
||||
const strategyName = samplingStrategy === "dominant" ? "区域主色" : "平滑取色";
|
||||
setStatus(`已转换为 ${width} × ${height} · ${strategyName} · ${limitedPalette.length} 种差异色`);
|
||||
const strategyName = samplingStrategy === "dominant" ? `区域主色 · 清理 ${cleanedCount} 个低可信孤立格` : "平滑取色";
|
||||
const actualColorCount = new Set(matchedColors.map((color) => color.code)).size;
|
||||
setStatus(`已转换为 ${width} × ${height} · ${strategyName} · ${actualColorCount} 种差异色`);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
Reference in New Issue
Block a user