129 lines
3.9 KiB
TypeScript
129 lines
3.9 KiB
TypeScript
import sharp from 'sharp';
|
|
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
|
|
async function debugSingle(imagePath: string) {
|
|
const basename = path.basename(imagePath);
|
|
console.log(`\n=== ${basename} ===`);
|
|
|
|
const { data, info } = await sharp(imagePath)
|
|
.raw()
|
|
.toBuffer({ resolveWithObject: true });
|
|
|
|
const { width, height, channels } = info;
|
|
|
|
const darkThreshold = 85;
|
|
const darkMap = new Uint8Array(width * height);
|
|
|
|
for (let y = 0; y < height; y++) {
|
|
for (let x = 0; x < width; x++) {
|
|
const idx = (y * width + x) * channels;
|
|
const r = data[idx];
|
|
const g = data[idx + 1];
|
|
const b = data[idx + 2];
|
|
const brightness = (r * 0.299 + g * 0.587 + b * 0.114);
|
|
|
|
darkMap[y * width + x] = brightness < darkThreshold ? 1 : 0;
|
|
}
|
|
}
|
|
|
|
const visited = new Uint8Array(width * height);
|
|
const regions: Array<{x: number; y: number; w: number; h: number; pixels: number}> = [];
|
|
|
|
for (let y = 0; y < height; y++) {
|
|
for (let x = 0; x < width; x++) {
|
|
const idx = y * width + x;
|
|
if (visited[idx] === 0 && darkMap[idx] === 1) {
|
|
let minX = x, minY = y, maxX = x, maxY = y, pixelCount = 0;
|
|
const stack: Array<[number, number]> = [[x, y]];
|
|
|
|
while (stack.length > 0) {
|
|
const [cx, cy] = stack.pop()!;
|
|
if (cx < 0 || cx >= width || cy < 0 || cy >= height) continue;
|
|
const cidx = cy * width + cx;
|
|
if (visited[cidx] === 1 || darkMap[cidx] !== 1) continue;
|
|
|
|
visited[cidx] = 1;
|
|
pixelCount++;
|
|
minX = Math.min(minX, cx);
|
|
minY = Math.min(minY, cy);
|
|
maxX = Math.max(maxX, cx);
|
|
maxY = Math.max(maxY, cy);
|
|
|
|
stack.push([cx + 1, cy], [cx - 1, cy], [cx, cy + 1], [cx, cy - 1]);
|
|
}
|
|
|
|
const w = maxX - minX + 1;
|
|
const h = maxY - minY + 1;
|
|
if (w >= 20 && h >= 20 && w < width * 0.9 && h < height * 0.9) {
|
|
regions.push({x: minX, y: minY, w, h, pixels: pixelCount});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
console.log(`找到 ${regions.length} 个有效连通区域`);
|
|
|
|
// 过滤符合条件的候选
|
|
const candidates = regions.filter(r => {
|
|
const aspectRatio = r.w / r.h;
|
|
const density = r.pixels / (r.w * r.h);
|
|
const centerY = r.y + r.h / 2;
|
|
|
|
return (
|
|
r.w >= 50 && r.w <= 95 &&
|
|
r.h >= 50 && r.h <= 95 &&
|
|
aspectRatio >= 0.85 && aspectRatio <= 1.18 &&
|
|
centerY > height * 0.12 &&
|
|
centerY < height * 0.78 &&
|
|
density > 0.65
|
|
);
|
|
});
|
|
|
|
console.log(`符合严格条件的候选: ${candidates.length} 个`);
|
|
|
|
if (candidates.length > 0) {
|
|
candidates.forEach((r, i) => {
|
|
const aspectRatio = r.w / r.h;
|
|
const density = r.pixels / (r.w * r.h);
|
|
console.log(` ${i + 1}. [x=${r.x}, y=${r.y}, w=${r.w}, h=${r.h}] 宽高比=${aspectRatio.toFixed(2)}, 密度=${density.toFixed(2)}`);
|
|
});
|
|
} else {
|
|
// 尝试放宽条件
|
|
const relaxed = regions.filter(r => {
|
|
const aspectRatio = r.w / r.h;
|
|
const density = r.pixels / (r.w * r.h);
|
|
|
|
return (
|
|
r.w >= 45 && r.w <= 100 &&
|
|
r.h >= 45 && r.h <= 100 &&
|
|
aspectRatio >= 0.75 && aspectRatio <= 1.33 &&
|
|
r.y < height * 0.82 &&
|
|
r.y > height * 0.06 &&
|
|
density > 0.55
|
|
);
|
|
});
|
|
|
|
console.log(`符合放宽条件的候选: ${relaxed.length} 个`);
|
|
relaxed.slice(0, 5).forEach((r, i) => {
|
|
const aspectRatio = r.w / r.h;
|
|
const density = r.pixels / (r.w * r.h);
|
|
console.log(` ${i + 1}. [x=${r.x}, y=${r.y}, w=${r.w}, h=${r.h}] 宽高比=${aspectRatio.toFixed(2)}, 密度=${density.toFixed(2)}`);
|
|
});
|
|
}
|
|
}
|
|
|
|
async function main() {
|
|
const baseDir = path.join(__dirname, '..');
|
|
const doubanDir = path.join(baseDir, 'images', 'douban');
|
|
|
|
// 检查未检测到的图片
|
|
const failedFiles = ['滑块-2.png', '滑块-3.png', '滑块-6.png', '滑块-7.png', '滑块.png'];
|
|
|
|
for (const file of failedFiles) {
|
|
await debugSingle(path.join(doubanDir, file));
|
|
}
|
|
}
|
|
|
|
main().catch(console.error);
|