久々に画像処理したくなったのでgonumを使って行列計算などしつつ、並列化してそれっぽく処理してみます。
よくあるグレー化を行列計算で行ってみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
bounds := img.Bounds() dest := image.NewRGBA64(bounds) vecGray := mat.NewVecDense(3, []float64{0.299, 0.587, 0.114}) for y := bounds.Min.Y; y < bounds.Max.Y; y++ { for x := bounds.Min.X; x < bounds.Max.X; x++ { /* default usage c := color.Gray16Model.Convert(img.At(x, y)) dest.Set(x, y, c) //*/ r, g, b, a := img.At(x, y).RGBA() v := mat.NewVecDense(3, []float64{float64(r), float64(g), float64(b)}) gray := uint16(mat.Dot(vecGray, v)) dest.Set(x, y, color.RGBA64{gray, gray, gray, uint16(a)}) } } |
NewVecDense()
でベクトルを作ってDot()
でドット積(内積)をだします。
数値はimage/colorのソースを参照しました。
ちなみにDense(密)がついてるのでSparse(疎)もあるかと思いきや見当たらない。
4近傍ラプラシアンフィルタを試してみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
matFilter := mat.NewDense(3, 3, []float64{ 0, 1, 0, 1, -4, 1, 0, 1, 0, }) for y := bounds.Min.Y + 1; y < bounds.Max.Y-1; y++ { for x := bounds.Min.X + 1; x < bounds.Max.X-1; x++ { //周囲9マスの画素値を行列にする f := make([]float64, 9) for i := 0; i < 9; i++ { r, _, _, _ := img.At(x+i%3-1, y+i/3-1).RGBA() f[i] = float64(r) } m := mat.NewDense(3, 3, f) //フィルタを適応 m.MulElem(matFilter, m) s := uint16(math.Max(0, mat.Sum(m))) dest.Set(x, y, color.RGBA64{s, s, s, 1}) } } |
単純な計算でなくMulElem()
しているのはフィルタを変えやすくするためです。
微分フィルタやSobel、Prewittフィルタへの変更は行列をいじるだけです。
画像処理らしく並行処理にしてみます。
ついでにメモリ使用量と処理時間も見てみる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
start := time.Now() var wg sync.WaitGroup for y := bounds.Min.Y + 1; y < bounds.Max.Y-1; y++ { for x := bounds.Min.X + 1; x < bounds.Max.X-1; x++ { wg.Add(1) go func(x int, y int) { defer wg.Done() f := make([]float64, 9) for i := 0; i < 9; i++ { r, _, _, _ := img.At(x+i%3-1, y+i/3-1).RGBA() f[i] = float64(r) } m := mat.NewDense(3, 3, f) m.MulElem(matFilter, m) s := uint16(math.Max(0, mat.Sum(m))) dest.Set(x, y, color.RGBA64{s, s, s, 1}) }(x, y) } } var mem runtime.MemStats runtime.ReadMemStats(&mem) fmt.Println(mem.Alloc, mem.TotalAlloc, mem.HeapAlloc, mem.HeapSys) wg.Wait() end := time.Now() fmt.Printf("process : %f sec\n", (end.Sub(start)).Seconds()) |
処理時間が減ってメモリが若干増えていることがわかります。
今回は大体で処理時間が40%減ってメモリ使用量が5-20%増えてました。
メモリの方は実行ごとにかなり変わるので計り方がよくなかったかも。
ちょっと弄ってfor文の方を分割してみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
var wgfor sync.WaitGroup var th = 8 for n := 0; n < th; n++ { n := n wgfor.Add(1) go func() { defer wgfor.Done() for y := bounds.Min.Y + 1 + n; y < bounds.Max.Y-1; y += th { for x := bounds.Min.X + 1; x < bounds.Max.X-1; x++ { func(x int, y int) { f := make([]float64, 9) for i := 0; i < 9; i++ { r, _, _, _ := img.At(x+i%3-1, y+i/3-1).RGBA() f[i] = float64(r) } m := mat.NewDense(3, 3, f) m.MulElem(matFilter, m) s := uint16(math.Max(0, mat.Sum(m))) dest.Set(x, y, color.RGBA64{s, s, s, 1}) }(x, y) } } }() } |
処理時間とメモリ使用量がさらに減りました。
並行処理は細かくするよりある程度まとまった処理を渡した方がよさそう。
コア数に合わせる感じがいいんだろうか。
余談
ループ変数をgoルーチンで扱う場合には注意が必要です。
参照されるタイミングで変数が変わっているため意図しない動作になります。
無名変数に変数を渡す(go func(x, y int){...}(x, y)
)か、ループ内部でローカル変数に定義しなおす(n := n
)必要があります。