灰度图中找出深色像素并给出坐标

« 返回首页

有用户问:拿到一张灰度图,怎么判断某个像素是不是深色,并把深色像素的坐标找出来?

能实现,不需要拓展。 用到三样现成的东西:

  • 画布的获取背景像素色值:给出坐标,取回那个点的颜色;
  • 颜色抽屉的「分解色值」:把颜色拆成红、绿、蓝、透明度四个数;灰度图的三个颜色分量相等,第 1 项就是灰度值(0 最黑,255 最白);
  • 颜色抽屉的「是深色吗」:直接判断一个颜色是不是深色。

思路:取颜色 → 算灰度 → 和阈值比较。灰度小于阈值就算深色,阈值常用 128,可以按图片调整。

准备

  1. 拖一个画布,宽度「充满」,高度 300 像素;背景图片选你的灰度图(素材里上传)。
  2. 拖一个标签、两个按钮(按钮_扫描、按钮_分批)、一个计时器(取消勾选「启用计时」)。
  3. 图片不是灰度图也没关系,加一个图片处理组件先转成灰度(见图片处理):
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,或按实验五分批扫描
画了红框之后再扫描,结果变了 「获取背景像素色值」会读到画在画布上的点和线(只有精灵不算) 先扫描、后标记;重新扫描前调用画布的「清除画布」

平台说明

  • 安卓、鸿蒙都支持以上全部积木,写法相同。
  • 本文只读取画布背景图,不会修改原图文件;要把深色点标出来保存,用画布的「另存为」。
文档反馈