🦜

GoCV活用例の紹介(Windowsでの環境構築)

2023/12/20に公開

この記事はGo 言語 Advent Calendar 2023のシリーズ2の7日目の記事です(穴があったので入りました!)。

あらかじめ必要なもの(for Windows11)

コマンドプロンプト
scoop install mingw cmake
go mod init dummy
go get -u -d gocv.io/x/gocv@v0.35.0
for /f "usebackq" %I in (`go env GOMODCACHE`) do cd "%I\gocv.io\x\gocv@v0.35.0\"

依存のビルド

win_build_opencv.cmdの41行目付近に-DWITH_OBSENSOR=OFFというオプションを追加。

コマンドプロンプト
win_build_opencv.cmd

最後元のフォルダに返ってくるのは失敗しますがビルドに問題なければOKです。

サンプルコード

  • OBS Virtual Camera出力をキャプチャ
  • あらかじめ表示に出るマークを切り出しておく(例:mark1.png, mark2.png)
  • マークは後述の一定の手順で特徴を強調し、
  • PHashというアルゴリズムで各マークのハッシュ値を算出しておく
  • ゲーム画面をOBSでキャプチャさせ、このコードでその画像の特定箇所を切り出す
  • 一定の手順で切り出し画像の特徴を強調してハッシュ算出し、算出済みのマーク群のハッシュ値と比較
  • PHashの場合、比較画像が近似していればしているほど小さな値を算出する
  • 完全一致すると「0.0」になる
  • マーク群のうち最もこの値の小さなものが十分小さければ切り出した場所のマークである確率が高い
mkdir sample
cd sample
go mod init sample
main.go
package main

import (
	"image"
	"log"

	"gocv.io/x/gocv"
	"gocv.io/x/gocv/contrib"
)

var (
	phash    = contrib.PHash{}
	markRect = image.Rectangle{
		Min: image.Point{X: 960 - 72, Y: 112},
		Max: image.Point{X: 960 + 72, Y: 112 + 144},
	}
	marks = map[string]gocv.Mat{}
)

func preproc(img *gocv.Mat) {
	gocv.DetailEnhance(*img, img, 20, 0.1)
	gocv.CvtColor(*img, img, gocv.ColorBGRToGray)
	gocv.Normalize(*img, img, 0, 255, gocv.NormMinMax)
	gocv.AdaptiveThreshold(*img, img, 255, gocv.AdaptiveThresholdGaussian, gocv.ThresholdBinary, 15, 10)
}

func init() {
	log.Println("Begin Init")
	defer log.Println("End Init")
	compute := gocv.NewMat()
	for _, name := range []string{"mark1", "mark2"} {
		img := gocv.IMRead(name+".png", gocv.IMReadColor)
		preproc(&img)
		phash.Compute(img, &compute)
		marks[name] = compute
	}
}

func main() {
	log.Println("Start")
	defer log.Println("End")
	cam, err := gocv.VideoCaptureDevice(2) // OBS Virtual Camera
	if err != nil {
		log.Fatal(err)
	}
	defer cam.Close()
	cam.Set(gocv.VideoCaptureFPS, 60)
	cam.Set(gocv.VideoCaptureFrameWidth, 1920)
	cam.Set(gocv.VideoCaptureFrameHeight, 1024)

	img := gocv.NewMat()
	compute := gocv.NewMat()
	for {
		cam.Read(&img)
		mark := img.Region(markRect)
		preproc(&mark)
		phash.Compute(mark, &compute)
		detect := "unknown"
		detectMin := 100.0
		for k, v := range marks {
			similar := phash.Compare(compute, v)
			if similar < 7 {
				if detectMin > similar {
					detectMin = similar
					detect = k
				}
			}
		}
		if detect != "unknown" {
			log.Println(detect)
		}
	}
}

ビルド

コマンドプロンプト
go mod tidy
go build .

まとめ

  • この手法によりゲーム画面などから特定の表示を高精度に検出できる
  • しかもPythonを使った場合に比べGoの場合低遅延での検出ができる

Discussion