有用户问:拿到一张灰度图,怎么判断某个像素是不是深色,并把深色像素的坐标找出来?
能实现,不需要拓展。 用到三样现成的东西:
- 画布的获取背景像素色值:给出坐标,取回那个点的颜色;
- 颜色抽屉的「分解色值」:把颜色拆成红、绿、蓝、透明度四个数;灰度图的三个颜色分量相等,第 1 项就是灰度值(0 最黑,255 最白);
- 颜色抽屉的「是深色吗」:直接判断一个颜色是不是深色。
思路:取颜色 → 算灰度 → 和阈值比较。灰度小于阈值就算深色,阈值常用 128,可以按图片调整。
准备
- 拖一个画布,宽度「充满」,高度 300 像素;背景图片选你的灰度图(素材里上传)。
- 拖一个标签、两个按钮(
按钮_扫描、按钮_分批)、一个计时器(取消勾选「启用计时」)。 - 图片不是灰度图也没关系,加一个图片处理组件先转成灰度(见图片处理):
when Screen1.Initialize() {
画布1.BackgroundImage = 图片处理1.Grayscale("photo.jpg")
}
实验一:点哪判哪
手指点在画布上,标签显示这个点的坐标、灰度值,以及是不是深色:
global threshold = 128
when 画布1.Touched(x, y, touchedAnySprite) {
var gray = splitColor(画布1.GetBackgroundPixelColor(x, y))[1]
if (gray < threshold) {
标签1.Text = join("(", round(x), ", ", round(y), ") 是深色,灰度 ", gray)
} else {
标签1.Text = join("(", round(x), ", ", round(y), ") 不是深色,灰度 ", gray)
}
}
点在黑色笔画上,显示「是深色」;点在白底上,显示「不是深色」。把 threshold 改成 80 或 180 再点同一处,能看到判断随阈值变化。
「是深色吗」积木和自己比阈值有什么区别? 「是深色吗」按人眼感知的相对亮度判断,亮度低于一半就算深色, 折算成灰度大约是 188 以下,所以中灰(比如灰度 150)也会被判成深色。想要「明显发黑才算」,就像上面这样取灰度值自己比较。
实验二:扫描整张图,列出深色点坐标
先写一个判断函数,再用两层循环把画布每隔几个点检查一遍,深色点的坐标 (x, y) 收进列表:
global threshold = 128
global gap = 4
global darkPoints = []
function IsDarkAt(x, y) {
return splitColor(画布1.GetBackgroundPixelColor(x, y))[1] < threshold
}
when 按钮_扫描.Click() {
darkPoints = []
for y = 0 to 画布1.Height - 1 step gap {
for x = 0 to 画布1.Width - 1 step gap {
if (IsDarkAt(x, y)) {
listAdd(darkPoints, list(x, y))
}
}
}
标签1.Text = join("深色点 ", length(darkPoints), " 个,第一个在 ", darkPoints[1])
}
gap 是采样间隔:4 表示每隔 4 个点看一次,速度快 16 倍,找「黑块在哪」完全够用;需要逐点精确时改成 1。
darkPoints 里每一项是 (x y) 两个数,第几项的第 1 个数是横坐标、第 2 个数是纵坐标。
没找到任何深色点时列表为空,取第 1 项会出错,正式使用时先判断 length(darkPoints) > 0。
实验三:深色区域在哪(外接矩形)
很多时候要的不是几千个点,而是「黑色部分的范围」,比如定位图中的一个黑块。扫描时顺手记下最左、最右、最上、最下:
global threshold = 128
global gap = 4
global minX = 100000
global minY = 100000
global maxX = -1
global maxY = -1
function IsDarkAt(x, y) {
return splitColor(画布1.GetBackgroundPixelColor(x, y))[1] < threshold
}
when 按钮_扫描.Click() {
minX = 100000
minY = 100000
maxX = -1
maxY = -1
for y = 0 to 画布1.Height - 1 step gap {
for x = 0 to 画布1.Width - 1 step gap {
if (IsDarkAt(x, y)) {
minX = choose(x < minX, x, minX)
minY = choose(y < minY, y, minY)
maxX = choose(x > maxX, x, maxX)
maxY = choose(y > maxY, y, maxY)
}
}
}
if (maxX < 0) {
标签1.Text = "没有深色像素"
} else {
标签1.Text = join("深色区域:左上 (", minX, ", ", minY, ") 右下 (", maxX, ", ", maxY, ")")
画布1.PaintColor = &HFFFF0000
画布1.DrawLine(minX, minY, maxX, minY)
画布1.DrawLine(maxX, minY, maxX, maxY)
画布1.DrawLine(maxX, maxY, minX, maxY)
画布1.DrawLine(minX, maxY, minX, minY)
}
}
扫描完在画布上用红框把深色区域圈出来,一眼就能核对找得对不对。
实验四:换算成原图的像素坐标
画布会把背景图缩放到画布大小,上面得到的是画布坐标,不是原图的像素坐标。 例如原图 1000×800 像素,画布宽 360,那么画布上的 1 格相当于原图约 2.8 个像素。
用图片处理取原图宽高,按比例换算回去:
function ToImageX(x) {
return round(x * 图片处理1.ImageWidth("photo.jpg") / 画布1.Width)
}
function ToImageY(y) {
return round(y * 图片处理1.ImageHeight("photo.jpg") / 画布1.Height)
}
when 画布1.Touched(x, y, touchedAnySprite) {
标签1.Text = join("画布坐标 (", round(x), ", ", round(y), ") 原图坐标 (", ToImageX(x), ", ", ToImageY(y), ")")
}
实验五:大图不卡顿——分批扫描
积木的循环在界面线程里执行,整张图一口气扫完的这段时间里,界面点不动。画布越大、gap 越小,卡得越久。
办法是让计时器每次只扫一行,扫的过程中界面照常响应,还能显示进度:
global threshold = 128
global gap = 4
global row = 0
global darkPoints = []
function IsDarkAt(x, y) {
return splitColor(画布1.GetBackgroundPixelColor(x, y))[1] < threshold
}
when 按钮_分批.Click() {
darkPoints = []
row = 0
计时器1.TimerInterval = 10
计时器1.TimerEnabled = true
}
when 计时器1.Timer() {
for x = 0 to 画布1.Width - 1 step gap {
if (IsDarkAt(x, row)) {
listAdd(darkPoints, list(x, row))
}
}
row = row + gap
标签1.Text = join("已扫描 ", round(row * 100 / 画布1.Height), "%")
if (row >= 画布1.Height) {
计时器1.TimerEnabled = false
标签1.Text = join("完成,深色点 ", length(darkPoints), " 个")
}
}
排错
| 现象 | 原因 | 解决 |
|---|---|---|
| 取到的颜色是 16777215(无颜色) | 坐标超出了画布范围 | 循环上限用「画布宽度 − 1」「画布高度 − 1」 |
| 坐标和图片软件里看到的对不上 | 背景图被缩放到画布大小,得到的是画布坐标 | 按实验四换算成原图坐标 |
| 彩色图判断不准 | 彩色图红绿蓝三个分量不相等,只看第 1 项(红)不对 | 先用图片处理的「灰度」转成灰度图;或改用「颜色亮度」「是深色吗」积木 |
| 中灰也被判成深色 | 「是深色吗」大约以灰度 188 为界 | 自己取灰度值和阈值比较(实验一) |
| 扫描时界面点不动 | 一次循环量太大 | 调大 gap,或按实验五分批扫描 |
| 画了红框之后再扫描,结果变了 | 「获取背景像素色值」会读到画在画布上的点和线(只有精灵不算) | 先扫描、后标记;重新扫描前调用画布的「清除画布」 |
平台说明
- 安卓、鸿蒙都支持以上全部积木,写法相同。
- 本文只读取画布背景图,不会修改原图文件;要把深色点标出来保存,用画布的「另存为」。
扫码添加客服咨询