- ここにソースと学習の変遷は書かれています。(https://github.com/kaepa3/effector)
画像の境界を探索します。
処理の概要
- 内側or外側の線の起点を見つけます
- そこから画素をガンガン走査していって(どうやらこれがラスタ走査)境界の次の箇所を見つける
- ワークの画素にプロットしていく
- 画像を出力する。
考え方で難しいのは
「すでに外枠としてプロットしている箇所は追跡を開始しない」
くらいかと思う。
ソースコード
func BoundaryTracking(srcImg image.Image) image.Image {
workImg := image.NewRGBA(srcImg.Bounds())
//キャンパスを真っ白にする。
icom.ImageLoop(workImg, func(x int, y int) {
workImg.Set(x, y, color.RGBA{math.MaxUint8, math.MaxUint8, math.MaxUint8, math.MaxUint8})
})
//探索開始
icom.ImageLoop(srcImg, func(x int, y int) {
if true == isCheckValue(x, y, srcImg) {
r, _, _, _ := srcImg.At(x, y).RGBA()
wR, _, _, _ := workImg.At(x, y).RGBA()
// 元画像が黒かつ、ワークが白(既に走査済みの線の可能性もこれで排除)
if r == 0 && wR != 0 {
//外枠か内枠かを操作する
code := getCode(x, y, srcImg)
if code != -1 {
imgChase(x, y, code, srcImg, workImg)
}
}
}
})
return workImg
}
キャンパスを真っ白にしておかないといけません。
考えが難しいPOINT
if r == 0 && wR != 0 {
既に境界線を走査した場合、wRはimgChase関数内で黒くなっている可能性がある。
同じ処理を動かさないように必要なんですね。
getCode
現在の走査点の左が白なら外枠と判別する、右側なら内枠と判別する。
中身がくりぬかれるのもこの論理の効能である。
func getCode(x, y int, img image.Image) int {
r, _, _, _ := img.At(x-1, y).RGBA()
if r != 0 {
// 外枠の走査開始
return 0
}
r, _, _, _ = img.At(x+1, y).RGBA()
if r != 0 {
// 内枠の走査開始
return 4
}
return -1
}
imgChase
基本的には左、下、右、上の順番に探して、見つかったら黒にするだけ。
ちなみに見つからなかったらとりあえず黒くせず移動する辺りちょっと賢いなと思った。
func imgChase(x, y, code int, srcImg image.Image, workImg *image.RGBA) {
xStart := x
yStart := y
fhase := func(xPos, yPos, a, b int) int {
r, _, _, _ := srcImg.At(xPos, yPos).RGBA()
if r == 0 {
return a
}
return b
}
var x2, y2 int
// 探索を開始して最初の位置に戻ってくるまで
for x2 != x || y2 != y {
x2 = xStart
y2 = yStart
switch code {
case 0: //基準点から下
y2++
code = fhase(x2, y2, 6, 2)
case 2: //基準点から右
x2++
code = fhase(x2, y2, 0, 4)
case 4: //基準点から上
y2--
code = fhase(x2, y2, 2, 6)
case 6: //基準点から左
x2--
code = fhase(x2, y2, 4, 0)
}
r, _, _, a := srcImg.At(x2, y2).RGBA()
if r == 0 {
workImg.Set(x2, y2, color.RGBA{0, 0, 0, uint8(a)})
xStart = x2
yStart = y2
}
}
}
出力結果
元画像
出力結果
良い感じですね、機械学習(ry