使用P5.js统计指定最小尺寸的黑点并获取其坐标
嘿,我完全懂你的困扰——现在的代码只能把所有符合阈值的黑像素混在一起算平均坐标,根本没法区分“单个像素噪点”和“真正达到最小尺寸的黑点区域”对吧?要解决这个问题,核心是要做连通区域检测(Blob Detection):把属于同一个黑点的像素分组,然后判断每个组的尺寸是否达标。
下面给你一套完整的解决方案,一步步帮你实现需求:
核心思路
- 给每个像素做“访问标记”,避免重复处理同一个区域
- 遍历每个像素,遇到未访问的黑像素时,用**广度优先搜索(BFS)**找出所有连通的黑像素,组成一个独立的黑点区域
- 计算每个区域的宽度和高度,过滤掉尺寸不达标的区域
- 统计符合要求的区域数量,还能获取每个区域的中心坐标
完整代码实现
let img; // 可根据你的需求调整参数 const minWidth = 10; // 黑点最小宽度 const minHeight = 10; // 黑点最小高度 const threshold = 20; // 黑像素的RGB阈值 function preload() { img = loadImage('your-image.png'); // 替换成你的图像路径 } function setup() { createCanvas(img.width, img.height); image(img, 0, 0); loadPixels(); detectValidBlackBlobs(); } function detectValidBlackBlobs() { const d = pixelDensity(); // 创建二维数组标记像素是否已被访问 const visited = create2DArray(width, height); let validBlobCount = 0; const validBlobs = []; // 遍历画布所有像素 for (let x = 0; x < width; x++) { for (let y = 0; y < height; y++) { // 找到未被访问的黑像素,启动BFS探索整个连通区域 if (!visited[x][y] && isBlackPixel(x, y)) { const blobPixels = bfsExploreBlob(x, y, visited); // 计算当前区域的边界范围 const [minX, maxX, minY, maxY] = calculateBlobBounds(blobPixels); const blobWidth = maxX - minX + 1; const blobHeight = maxY - minY + 1; // 过滤掉尺寸不达标的区域 if (blobWidth >= minWidth && blobHeight >= minHeight) { validBlobCount++; const centerX = (minX + maxX) / 2; const centerY = (minY + maxY) / 2; validBlobs.push({ x: centerX, y: centerY, width: blobWidth, height: blobHeight }); console.log(`找到有效黑点:中心(${centerX.toFixed(1)}, ${centerY.toFixed(1)}),尺寸${blobWidth}x${blobHeight}`); } } } } console.log(`满足最小尺寸要求的黑点总数:${validBlobCount}`); // 可选:给有效黑点画红色边界框,方便验证 drawBlobBoundingBoxes(validBlobs); } // 判断单个像素是否为黑像素 function isBlackPixel(x, y) { const d = pixelDensity(); const index = 4 * ((y * d) * width * d + (x * d)); return pixels[index] <= threshold && pixels[index + 1] <= threshold && pixels[index + 2] <= threshold; } // 用BFS探索整个连通的黑像素区域 function bfsExploreBlob(startX, startY, visited) { const queue = [{ x: startX, y: startY }]; visited[startX][startY] = true; const blobPixels = []; // 四方向连通(上下左右),如果需要对角线连通可以加上[1,1], [-1,-1]等方向 const directions = [[-1, 0], [1, 0], [0, -1], [0, 1]]; while (queue.length > 0) { const current = queue.shift(); blobPixels.push(current); // 遍历四个方向的相邻像素 for (const dir of directions) { const newX = current.x + dir[0]; const newY = current.y + dir[1]; // 检查坐标合法、未被访问、且是黑像素 if (newX >= 0 && newX < width && newY >= 0 && newY < height && !visited[newX][newY] && isBlackPixel(newX, newY)) { visited[newX][newY] = true; queue.push({ x: newX, y: newY }); } } } return blobPixels; } // 计算黑点区域的边界坐标 function calculateBlobBounds(blobPixels) { let minX = Infinity, maxX = -Infinity; let minY = Infinity, maxY = -Infinity; for (const p of blobPixels) { minX = Math.min(minX, p.x); maxX = Math.max(maxX, p.x); minY = Math.min(minY, p.y); maxY = Math.max(maxY, p.y); } return [minX, maxX, minY, maxY]; } // 创建二维数组用于标记访问状态 function create2DArray(w, h) { const arr = new Array(w); for (let i = 0; i < w; i++) { arr[i] = new Array(h).fill(false); } return arr; } // 画出有效黑点的红色边界框 function drawBlobBoundingBoxes(blobs) { stroke(255, 0, 0); strokeWeight(2); noFill(); rectMode(CORNERS); for (const blob of blobs) { const left = blob.x - blob.width / 2; const right = blob.x + blob.width / 2; const top = blob.y - blob.height / 2; const bottom = blob.y + blob.height / 2; rect(left, top, right, bottom); } }
关键细节说明
- 访问标记矩阵:确保每个像素只被处理一次,避免重复统计同一个黑点区域
- BFS遍历:高效找出所有连通的黑像素,你可以根据需求改成八方向连通(加上对角线)
- 尺寸过滤:通过计算区域的最小/最大坐标得到宽高,直接排除单个像素的噪点
- 可视化验证:最后的
drawBlobBoundingBoxes函数可以帮你直观看到检测结果是否正确
如果是处理实时视频流(比如摄像头输入),只需要把检测逻辑放到draw()函数里,并每次调用loadPixels()即可,不过建议根据性能情况适当缩小画布尺寸。
内容的提问来源于stack exchange,提问作者Clément Grondin
相关产品推荐
相关产品推荐

