├── .github ├── CONTRIBUTING.md ├── issue_template.md ├── pull_request_template.md └── workflows │ └── go.yml ├── .gitignore ├── LICENSE ├── README.md ├── circle.yml ├── cv.go ├── examples └── main.go ├── fn.go ├── go.mod └── go.sum /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to gcv 2 | 3 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | 1. Please **speak English**, this is the language everybody of us can speak and write. 2 | 2. Please ask questions or config/deploy problems on our Gitter channel: https://gitter.im/go-ego/ego 3 | 3. Please take a moment to search that an issue **doesn't already exist**. 4 | 4. Please give all relevant information below for bug reports, incomplete details will be handled as an invalid report. 5 | 6 | **You MUST delete the content above including this line before posting, otherwise your issue will be invalid.** 7 | 8 | - gcv version (or commit ref): 9 | - Go version: 10 | 11 | - Operating system and bit: 12 | - Can you reproduce the bug at [Examples](https://github.com/vcaesar/gcv/tree/master/examples): 13 | - [ ] Yes (provide example code) 14 | - [ ] No 15 | - [ ] Not relevant 16 | - Provide example code: 17 | ```Go 18 | 19 | ``` 20 | - Log gist: 21 | 22 | ## Description 23 | 24 | ... 25 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | The pull request will be closed without any reasons if it does not satisfy any of following requirements: 2 | 3 | 1. Make sure you are targeting the `master` branch, pull requests on release branches are only allowed for bug fixes. 4 | 2. Please read contributing guidelines: [CONTRIBUTING](https://github.com/vcaesar/gcv/blob/master/CONTRIBUTING.md) 5 | 3. Describe what your pull request does and which issue you're targeting (if any and Please use English) 6 | 4. ... if it is not related to any particular issues, explain why we should not reject your pull request. 7 | 5. The Commits must **use English**, must be test and No useless submissions. 8 | 9 | **You MUST delete the content above including this line before posting, otherwise your pull request will be invalid.** 10 | 11 | **Please provide Issues links to:** 12 | 13 | - Issues: #1 14 | 15 | **Provide test code:** 16 | 17 | ```Go 18 | 19 | ``` 20 | 21 | ## Description 22 | 23 | ... 24 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | on: [push] 3 | jobs: 4 | test: 5 | # name: build 6 | strategy: 7 | matrix: 8 | # go: [1.16.x, 1.17.x] 9 | os: [macOS-latest] 10 | runs-on: ${{ matrix.os }} 11 | 12 | steps: 13 | - name: Set up Go 1.24.0 14 | uses: actions/setup-go@v1 15 | with: 16 | go-version: 1.24.0 17 | id: go 18 | 19 | - name: Check out code into the Go module directory 20 | uses: actions/checkout@v1 21 | 22 | - name: Get opencv 23 | run: | 24 | brew install opencv 25 | 26 | - name: Get dependencies 27 | run: | 28 | go get -v -t -d ./... 29 | 30 | - name: Build 31 | run: go build -v . 32 | - name: Test 33 | run: go test -v . 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | .DS_Store 3 | .vscode 4 | .idea 5 | 6 | # Binaries for programs and plugins 7 | *.exe 8 | *.exe~ 9 | *.dll 10 | *.so 11 | *.dylib 12 | 13 | # Test binary, built with `go test -c` 14 | *.test 15 | 16 | # Output of the go coverage tool, specifically when used with LiteIDE 17 | *.out 18 | 19 | # Dependency directories (remove the comment below to include it) 20 | # vendor/ 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2021, Evans 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gcv 2 | 3 | [![Build Status](https://github.com/vcaesar/gcv/workflows/Go/badge.svg)](https://github.com/vcaesar/gcv/commits/master) 4 | [![Build Status](https://travis-ci.org/vcaesar/gcv.svg)](https://travis-ci.org/vcaesar/gcv) 5 | [![CircleCI Status](https://circleci.com/gh/vcaesar/gcv.svg?style=shield)](https://circleci.com/gh/vcaesar/gcv) 6 | [![codecov](https://codecov.io/gh/vcaesar/gcv/branch/master/graph/badge.svg)](https://codecov.io/gh/vcaesar/gcv) 7 | [![Go Report Card](https://goreportcard.com/badge/github.com/vcaesar/gcv)](https://goreportcard.com/report/github.com/vcaesar/gcv) 8 | [![GoDoc](https://godoc.org/github.com/vcaesar/gcv?status.svg)](https://godoc.org/github.com/vcaesar/gcv) 9 | [![Release](https://github-release-version.herokuapp.com/github/vcaesar/gcv/release.svg?style=flat)](https://github.com/vcaesar/gcv/releases/latest) 10 | 11 | ## Install 12 | 13 | You should make sure Golang, GCC, and [OpenCV](https://gocv.io/getting-started/linux/) installed correctly before starting. 14 | 15 | ## Use 16 | 17 | ```go 18 | package main 19 | 20 | import ( 21 | "fmt" 22 | 23 | "github.com/go-vgo/robotgo" 24 | "github.com/vcaesar/gcv" 25 | ) 26 | 27 | func main() { 28 | img, _ := robotgo.CaptureImg() 29 | img1, _ := robotgo.CaptureImg(18, 4, 20, 20) 30 | rs := gcv.FindAllImg(img1, img) 31 | fmt.Println("find: ", rs[0].TopLeft.Y, rs[0].Rects.TopLeft.X, rs[0].ImgSize.H) 32 | fmt.Println("find: ", rs) 33 | 34 | m1, _ := gcv.ImgToMat(img) 35 | m2, _ := gcv.ImgToMat(img1) 36 | rs = gcv.FindAllTemplate(m1, m2, 0.8) 37 | fmt.Println("find: ", rs) 38 | } 39 | ``` 40 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | jobs: 4 | build: 5 | docker: 6 | - image: golang:1.24.0 7 | working_directory: /gopath/src/github.com/vcaesar/gcv 8 | steps: 9 | - checkout 10 | - run: apt update 11 | - run: 12 | apt -y install gcc libc6-dev 13 | libx11-dev xorg-dev libxtst-dev 14 | xsel xclip 15 | libopencv-dev 16 | # libpng++-dev 17 | # xcb libxcb-xkb-dev x11-xkb-utils libx11-xcb-dev libxkbcommon-x11-dev libxkbcommon-dev 18 | - run: apt -y install xvfb 19 | # specify any bash command here prefixed with `run: ` 20 | - run: go get -v -t -d ./... 21 | - run: xvfb-run go test -v ./... 22 | # codecov.io 23 | - run: go test -v -covermode=count -coverprofile=coverage.out 24 | - run: bash <(curl -s https://codecov.io/bash) 25 | -------------------------------------------------------------------------------- /cv.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Evans. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package gcv 6 | 7 | import ( 8 | "image" 9 | "image/color" 10 | 11 | "github.com/go-vgo/gt/hset" 12 | "github.com/vcaesar/imgo" 13 | "gocv.io/x/gocv" 14 | ) 15 | 16 | // FindImgFile find image file in subfile 17 | func FindImgFile(tempFile, file string, flag ...int) (float32, float32, image.Point, image.Point) { 18 | return FindImgMatC(IMRead(file, flag...), IMRead(tempFile, flag...)) 19 | } 20 | 21 | // FindImg find image in the subImg 22 | func FindImg(subImg, imgSource image.Image) (float32, float32, image.Point, image.Point) { 23 | m1, _ := ImgToMatA(imgSource) 24 | m2, _ := ImgToMatA(subImg) 25 | return FindImgMatC(m1, m2) 26 | } 27 | 28 | // FindImgByte find image in the subImg by []byte 29 | func FindImgByte(subImg, imgSource []byte) (float32, float32, image.Point, image.Point) { 30 | m1, _ := imgo.ByteToImg(imgSource) 31 | m2, _ := imgo.ByteToImg(subImg) 32 | return FindImg(m2, m1) 33 | } 34 | 35 | // FindImgX find image in the subImg return x, y 36 | func FindImgX(subImg, imgSource image.Image) (int, int) { 37 | _, _, _, maxLoc := FindImg(subImg, imgSource) 38 | return maxLoc.X, maxLoc.Y 39 | } 40 | 41 | // FindImgMatC find the image Mat in the temp Mat and close gocv.Mat 42 | func FindImgMatC(imgSource, temp gocv.Mat) (float32, float32, image.Point, image.Point) { 43 | defer imgSource.Close() 44 | defer temp.Close() 45 | return FindImgMat(imgSource, temp) 46 | } 47 | 48 | // FindImgMat find the image Mat in the temp Mat 49 | func FindImgMat(imgSource, temp gocv.Mat) (float32, float32, image.Point, image.Point) { 50 | res := gocv.NewMat() 51 | defer res.Close() 52 | msk := gocv.NewMat() 53 | defer msk.Close() 54 | 55 | gocv.MatchTemplate(imgSource, temp, &res, gocv.TmCcoeffNormed, msk) 56 | minVal, maxVal, minLoc, maxLoc := gocv.MinMaxLoc(res) 57 | 58 | return minVal, maxVal, minLoc, maxLoc 59 | } 60 | 61 | // FlannbasedMatch new flann based match 62 | func FlannbasedMatch(query, train gocv.Mat, k int) [][]gocv.DMatch { 63 | fb := gocv.NewFlannBasedMatcher() 64 | return fb.KnnMatch(query, train, k) 65 | } 66 | 67 | // Point the image X, Y point structure 68 | type Point struct { 69 | X, Y int 70 | } 71 | 72 | // Size the image size structure 73 | type Size struct { 74 | W, H int 75 | } 76 | 77 | // Rect the image rectangles structure 78 | type Rect struct { 79 | TopLeft, TopRight Point 80 | BottomLeft, BottomRight Point 81 | } 82 | 83 | // Result find template result structure 84 | type Result struct { 85 | Middle, TopLeft Point // maxLoc 86 | Rects Rect 87 | 88 | MaxVal []float32 89 | ImgSize Size 90 | } 91 | 92 | // Find find all the img search in the img source by 93 | // find all template and sift and return Result 94 | func Find(imgSearch, imgSource image.Image, args ...interface{}) (r Result) { 95 | res := FindAll(imgSearch, imgSource, args...) 96 | if len(res) > 0 { 97 | r = res[0] 98 | } 99 | return 100 | } 101 | 102 | // FindX find all the img search in the img source by 103 | // find all template and sift and return x, y 104 | func FindX(imgSearch, imgSource image.Image, args ...interface{}) (x, y int) { 105 | res := Find(imgSearch, imgSource, args...) 106 | x, y = res.Middle.X, res.Middle.Y 107 | return 108 | } 109 | 110 | // FindAllX find all the img search in the img source by 111 | // find all template and sift and return []x, []y 112 | func FindAllX(imgSearch, imgSource image.Image, args ...interface{}) (x, y []int) { 113 | res := FindAll(imgSearch, imgSource, args...) 114 | for i := 0; i < len(res); i++ { 115 | x = append(x, res[i].Middle.X) 116 | y = append(y, res[i].Middle.Y) 117 | } 118 | return 119 | } 120 | 121 | // FindAllImg find the search image all template in the source image return []Result 122 | func FindAllImg(imgSearch, imgSource image.Image, args ...interface{}) []Result { 123 | imSource, _ := ImgToMatA(imgSource) 124 | imSearch, _ := ImgToMatA(imgSearch) 125 | 126 | return FindAllTemplateC(imSource, imSearch, args...) 127 | } 128 | 129 | // FindAll find all the img search in the img source by 130 | // find all template and sift and return []Result 131 | func FindAll(imgSearch, imgSource image.Image, args ...interface{}) []Result { 132 | imSource, _ := ImgToMatA(imgSource) 133 | imSearch, _ := ImgToMatA(imgSearch) 134 | 135 | res := FindAllTemplateC(imSource, imSearch, args...) 136 | if len(res) <= 0 { 137 | res = FindAllSiftC(imSource, imSearch, args...) 138 | } 139 | return res 140 | } 141 | 142 | // FindAllImgFlie find the search image all template in the source image file 143 | // return []Result 144 | func FindAllImgFile(fileSearh, file string, args ...interface{}) []Result { 145 | return FindAllTemplateC(IMRead(file), IMRead(fileSearh), args...) 146 | } 147 | 148 | // FindMultiAllImgFile find the multi file search image all template 149 | // in the file source image return [][]Result 150 | func FindMultiAllImgFile(fileSearh []string, file string, args ...interface{}) [][]Result { 151 | imSource := IMRead(file) 152 | var imSearch []gocv.Mat 153 | for i := 0; i < len(fileSearh); i++ { 154 | search := IMRead(fileSearh[i]) 155 | imSearch = append(imSearch, search) 156 | } 157 | return FindMultiAllTemplateC(imSource, imSearch, args...) 158 | } 159 | 160 | // FindMultiAllImg find the multi search image all template in the source image 161 | // return [][]Result 162 | func FindMultiAllImg(imgSearch []image.Image, imgSource image.Image, args ...interface{}) [][]Result { 163 | imSource, _ := ImgToMatA(imgSource) 164 | var imSearch []gocv.Mat 165 | for i := 0; i < len(imgSearch); i++ { 166 | search, _ := ImgToMatA(imgSearch[i]) 167 | imSearch = append(imSearch, search) 168 | } 169 | 170 | return FindMultiAllTemplateC(imSource, imSearch, args...) 171 | } 172 | 173 | // FindMultiAllTemplate find the multi imgSearch all template in the imgSource return [][]Result 174 | // and close gocv.Mat 175 | func FindMultiAllTemplateC(imgSource gocv.Mat, imgSearch []gocv.Mat, args ...interface{}) (r [][]Result) { 176 | defer imgSource.Close() 177 | for i := 0; i < len(imgSearch); i++ { 178 | r = append(r, FindAllTemplateCS(imgSource, imgSearch[i], args...)) 179 | } 180 | 181 | return 182 | } 183 | 184 | // FindMultiAllTemplate find the multi imgSearch all template in the imgSource return [][]Result 185 | func FindMultiAllTemplate(imgSource gocv.Mat, imgSearch []gocv.Mat, args ...interface{}) (r [][]Result) { 186 | for i := 0; i < len(imgSearch); i++ { 187 | r = append(r, FindAllTemplate(imgSource, imgSearch[i], args...)) 188 | } 189 | 190 | return 191 | } 192 | 193 | // FindAllTemplateCS find the imgSearch all template in the imgSource return []Result 194 | // and close gocv.Mat 195 | func FindAllTemplateCS(imgSource, imgSearch gocv.Mat, args ...interface{}) []Result { 196 | // defer imgSource.Close() 197 | defer imgSearch.Close() 198 | return FindAllTemplate(imgSource, imgSearch, args...) 199 | } 200 | 201 | // FindAllTemplateC find the imgSearch all template in the imgSource return []Result 202 | // and close gocv.Mat 203 | func FindAllTemplateC(imgSource, imgSearch gocv.Mat, args ...interface{}) []Result { 204 | defer imgSource.Close() 205 | defer imgSearch.Close() 206 | return FindAllTemplate(imgSource, imgSearch, args...) 207 | } 208 | 209 | // FindAllSiftC find the imgSearch all sift in the imgSource return []Result 210 | // and close gocv.Mat 211 | func FindAllSiftC(imSource, imSearch gocv.Mat, args ...interface{}) []Result { 212 | defer imSource.Close() 213 | defer imSearch.Close() 214 | return FindAllSift(imSource, imSearch, args...) 215 | } 216 | 217 | // FindAllTemplate find the imgSearch all template in the imgSource return []Result 218 | func FindAllTemplate(imgSource, imgSearch gocv.Mat, args ...interface{}) []Result { 219 | threshold := float32(0.8) 220 | if len(args) > 0 { 221 | threshold = float32(args[0].(float64)) 222 | } 223 | 224 | maxCount := 10 225 | if len(args) > 1 { 226 | maxCount = args[1].(int) 227 | } 228 | // rgb := false 229 | // if len(args) > 2 { 230 | // rgb = args[2].(bool) 231 | // } 232 | 233 | method := false 234 | if len(args) > 3 { 235 | method = args[3].(bool) 236 | } 237 | 238 | iGray := gocv.NewMat() 239 | defer iGray.Close() 240 | sGray := gocv.NewMat() 241 | defer sGray.Close() 242 | 243 | // if !rgb { 244 | gocv.CvtColor(imgSource, &iGray, gocv.ColorRGBToGray) 245 | gocv.CvtColor(imgSearch, &sGray, gocv.ColorRGBToGray) 246 | // } 247 | 248 | results := make([]Result, 0) 249 | for { 250 | _, maxVal, minLoc, maxLoc := FindImgMat(iGray, sGray) 251 | h, w := imgSearch.Rows(), imgSearch.Cols() 252 | if maxVal < threshold || len(results) > maxCount { 253 | break 254 | } 255 | 256 | if method { 257 | maxLoc = minLoc 258 | } 259 | 260 | rs := getVal(maxLoc, maxVal, w, h) 261 | results = append(results, rs) 262 | if len(results) <= 0 { 263 | return nil 264 | } 265 | 266 | Fill(iGray, rs.Rects) 267 | // Rectangle(iGray, maxLoc, w, h) 268 | // Show(iGray) 269 | } 270 | 271 | return results 272 | } 273 | 274 | func getVal(maxLoc image.Point, maxVal float32, w, h int) Result { 275 | rect := Rect{ 276 | TopLeft: Point{maxLoc.X, maxLoc.Y}, 277 | BottomLeft: Point{maxLoc.X, maxLoc.Y + h}, 278 | BottomRight: Point{maxLoc.X + w, maxLoc.Y + h}, 279 | TopRight: Point{maxLoc.X + w, maxLoc.Y}, 280 | } 281 | 282 | middle := image.Pt(maxLoc.X+w/2, maxLoc.Y+h/2) 283 | middlePoint := Point{middle.X, middle.Y} 284 | 285 | topLeft := Point{maxLoc.X, maxLoc.Y} 286 | maxVals := []float32{maxVal} 287 | size := Size{w, h} 288 | 289 | return Result{ 290 | Middle: middlePoint, 291 | TopLeft: topLeft, // MaxLoc 292 | Rects: rect, 293 | MaxVal: maxVals, 294 | ImgSize: size, 295 | } 296 | } 297 | 298 | // Rectangle rectangle the iGray image 299 | func Rectangle(iGray gocv.Mat, maxLoc image.Point, w, h int) { 300 | r := image.Rect( 301 | int(maxLoc.X-w/2), 302 | int(maxLoc.Y-h/2), 303 | int(maxLoc.X+w/2), 304 | int(maxLoc.Y+h/2), 305 | ) 306 | 307 | // white := color.RGBA{255, 255, 255, 255} 308 | black := color.RGBA{0, 0, 0, 0} 309 | gocv.Rectangle(&iGray, r, black, -1) 310 | } 311 | 312 | // Fill fillpoly the iGray image 313 | func Fill(iGray gocv.Mat, rect Rect) { 314 | pts := [][]image.Point{{ 315 | image.Pt(rect.TopLeft.X, rect.TopLeft.Y), 316 | image.Pt(rect.BottomLeft.X, rect.BottomLeft.Y), 317 | image.Pt(rect.BottomRight.X, rect.BottomLeft.Y), 318 | image.Pt(rect.TopRight.X, rect.TopRight.Y), 319 | }} 320 | 321 | pts1 := gocv.NewPointsVectorFromPoints(pts) 322 | defer pts1.Close() 323 | 324 | blue := color.RGBA{0, 0, 255, 0} 325 | gocv.FillPoly(&iGray, pts1, blue) 326 | } 327 | 328 | func findH(kpS, kpSrc []gocv.KeyPoint, goodDiff []gocv.DMatch) (gocv.Mat, gocv.Mat) { 329 | src := gocv.NewMatWithSize(4, 1, gocv.MatTypeCV64FC2) 330 | defer src.Close() 331 | dst := gocv.NewMatWithSize(4, 1, gocv.MatTypeCV64FC2) 332 | // defer dst.Close() 333 | mask := gocv.NewMat() 334 | // defer mask.Close() 335 | 336 | // Get the keypoints from the good matches 337 | for i := 0; i < len(goodDiff); i++ { 338 | dst.SetDoubleAt(i, 0, kpS[goodDiff[i].QueryIdx].X) 339 | dst.SetDoubleAt(i, 1, kpS[goodDiff[i].QueryIdx].Y) 340 | 341 | src.SetDoubleAt(i, 0, kpSrc[goodDiff[i].TrainIdx].X) 342 | src.SetDoubleAt(i, 1, kpSrc[goodDiff[i].TrainIdx].Y) 343 | } 344 | 345 | // find estimate H 346 | hm := gocv.FindHomography(src, dst, gocv.HomographyMethodRANSAC, 5.0, 347 | &mask, 2000, 0.95) 348 | 349 | return hm, mask 350 | } 351 | 352 | func transform(h, w int, hm gocv.Mat) gocv.Mat { 353 | dst := gocv.NewMatWithSize(4, 2, gocv.MatTypeCV32FC2) 354 | // defer dst.Close() 355 | 356 | // new mat from the img search 357 | src := gocv.NewMatWithSize(4, 2, gocv.MatTypeCV32FC2) 358 | defer src.Close() 359 | 360 | src.SetFloatAt(0, 0, 0) 361 | src.SetFloatAt(0, 1, 0) 362 | 363 | src.SetFloatAt(1, 0, 0) 364 | src.SetFloatAt(1, 1, float32(h-1)) 365 | 366 | src.SetFloatAt(2, 0, float32(w-1)) 367 | src.SetFloatAt(2, 1, float32(h-1)) 368 | 369 | src.SetFloatAt(2, 0, float32(w-1)) 370 | src.SetFloatAt(3, 1, 0) 371 | 372 | gocv.PerspectiveTransform(src, &dst, hm) 373 | return dst 374 | } 375 | 376 | func getPoint(ms [][]gocv.DMatch, kpS, kpSrc []gocv.KeyPoint, ratio float64) []gocv.DMatch { 377 | var good, goodDiff []gocv.DMatch 378 | 379 | // Filter matches low distance by ratio 380 | for i := 0; i < len(ms); i++ { 381 | if ms[i][0].Distance < ratio*ms[i][1].Distance { 382 | good = append(good, ms[i][0]) 383 | } 384 | } 385 | 386 | // Remove the duplicates point 387 | h1 := hset.New() 388 | for i := 0; i < len(good); i++ { 389 | p1 := Point{ 390 | X: int(kpSrc[good[i].TrainIdx].X), 391 | Y: int(kpSrc[good[i].TrainIdx].Y), 392 | } 393 | 394 | if !h1.Exists(p1) { 395 | h1.Add(p1) 396 | goodDiff = append(goodDiff, good[i]) 397 | } 398 | } 399 | 400 | return goodDiff 401 | } 402 | 403 | // FindAllSift find the imSearch all sift in imSource return result 404 | func FindAllSift(imSource, imSearch gocv.Mat, args ...interface{}) (res []Result) { 405 | sift := gocv.NewSIFT() 406 | defer sift.Close() 407 | // 408 | mask1 := gocv.NewMat() 409 | defer mask1.Close() 410 | mask2 := gocv.NewMat() 411 | defer mask2.Close() 412 | 413 | minMatch := 4 414 | ratio := 0.75 // 0.9 415 | 416 | // detect the feature and compute descriptor 417 | kpS, des := sift.DetectAndCompute(imSearch, mask1) 418 | kpSrc, deSrc := sift.DetectAndCompute(imSource, mask2) 419 | if len(kpS) < 2 || len(kpSrc) < 2 || len(kpS) < minMatch { 420 | return 421 | } 422 | 423 | ms := FlannbasedMatch(des, deSrc, 2) 424 | goodDiff := getPoint(ms, kpS, kpSrc, ratio) 425 | 426 | if len(goodDiff) == 0 { 427 | return 428 | } 429 | 430 | h, w := GetSize(imSearch) 431 | // get the result value 432 | if len(goodDiff) == 1 { 433 | kpt := kpSrc[goodDiff[0].TrainIdx] 434 | middlePoint := Point{int(kpt.X), int(kpt.Y)} 435 | 436 | res = append(res, Result{ 437 | Middle: middlePoint, 438 | MaxVal: []float32{0.5}, 439 | Rects: Rect{}, 440 | ImgSize: Size{w, h}, 441 | }) 442 | 443 | return 444 | } 445 | 446 | // if len(goodDiff) > 3 { 447 | hm, mask := findH(kpS, kpSrc, goodDiff) 448 | dst := transform(h, w, hm) 449 | hm.Close() 450 | defer mask.Close() 451 | defer dst.Close() 452 | 453 | p1 := Point{int(dst.GetFloatAt(0, 0)) + w, int(dst.GetFloatAt(0, 1))} 454 | p2 := Point{int(dst.GetFloatAt(1, 0)) + w, int(dst.GetFloatAt(1, 1))} 455 | p3 := Point{int(dst.GetFloatAt(2, 0)) + w, int(dst.GetFloatAt(2, 1))} 456 | p4 := Point{int(dst.GetFloatAt(3, 0)) + w, int(dst.GetFloatAt(3, 1))} 457 | 458 | res = append(res, Result{ 459 | Middle: Point{p1.X + p3.X/2, p1.Y + p3.Y/2}, 460 | TopLeft: p1, 461 | Rects: Rect{ 462 | TopLeft: p1, 463 | BottomLeft: p2, 464 | BottomRight: p3, 465 | TopRight: p4, 466 | }, 467 | MaxVal: []float32{float32(mask.GetDoubleAt(h, w)), float32(len(goodDiff))}, 468 | ImgSize: Size{w, h}, 469 | }) 470 | // } 471 | 472 | return 473 | } 474 | -------------------------------------------------------------------------------- /examples/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/go-vgo/robotgo" 7 | "github.com/vcaesar/gcv" 8 | ) 9 | 10 | func main() { 11 | // save images 12 | img, _ := robotgo.CaptureImg() 13 | img1, _ := robotgo.CaptureImg(18, 4, 20, 20) 14 | 15 | rs := gcv.FindAllImg(img1, img) 16 | if len(rs) > 0 { 17 | fmt.Println("find: ", rs[0].TopLeft.Y, rs[0].Rects.TopLeft.X, rs[0].ImgSize.H) 18 | } 19 | fmt.Println("find: ", rs) 20 | 21 | m1, _ := gcv.ImgToMat(img) 22 | m2, _ := gcv.ImgToMat(img1) 23 | 24 | rs = gcv.FindAllTemplate(m1, m2, 0.8) 25 | fmt.Println("find: ", rs) 26 | 27 | rs = gcv.FindAllSift(m1, m2) 28 | fmt.Println("find sift: ", rs) 29 | } 30 | -------------------------------------------------------------------------------- /fn.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Evans. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package gcv 6 | 7 | import ( 8 | "image" 9 | "image/color" 10 | 11 | "gocv.io/x/gocv" 12 | ) 13 | 14 | // IMRead read the image file to gocv.Mat 15 | func IMRead(file string, flag ...int) gocv.Mat { 16 | f1 := 4 17 | if len(flag) > 0 { 18 | f1 = flag[0] 19 | } 20 | return gocv.IMRead(file, gocv.IMReadFlag(f1)) 21 | } 22 | 23 | // IMWrite write the gocv.Mat to file 24 | func IMWrite(name string, img gocv.Mat) bool { 25 | return gocv.IMWrite(name, img) 26 | } 27 | 28 | // IMWrite write the image.Image to file 29 | func ImgWrite(name string, img image.Image) bool { 30 | im, err := ImgToMat(img) 31 | if err != nil { 32 | return false 33 | } 34 | 35 | return IMWrite(name, im) 36 | } 37 | 38 | // ImgToMat trans image.Image to gocv.Mat 39 | func ImgToMat(img image.Image) (gocv.Mat, error) { 40 | return gocv.ImageToMatRGB(img) 41 | } 42 | 43 | // ImgToMatA trans image.Image to gocv.Mat 44 | func ImgToMatA(img image.Image) (gocv.Mat, error) { 45 | return gocv.ImageToMatRGBA(img) 46 | } 47 | 48 | // MatToImg trans gocv.Mat to image.Image 49 | func MatToImg(m1 gocv.Mat) (image.Image, error) { 50 | return m1.ToImage() 51 | } 52 | 53 | // Show show the gocv.Mat image in the window 54 | func Show(img gocv.Mat, args ...interface{}) { 55 | wName := "show" 56 | if len(args) > 0 { 57 | wName = args[0].(string) 58 | } 59 | 60 | window := gocv.NewWindow(wName) 61 | defer window.Close() 62 | 63 | // h, w := GetSize(img) 64 | h, w := 600, 800 65 | if len(args) > 2 { 66 | w = args[1].(int) 67 | h = args[2].(int) 68 | } 69 | window.ResizeWindow(w, h) 70 | 71 | window.IMShow(img) 72 | window.WaitKey(0) 73 | } 74 | 75 | // GetSize get the cv.Mat width, hight 76 | func GetSize(img gocv.Mat) (int, int) { 77 | return img.Rows(), img.Cols() 78 | } 79 | 80 | // Resize resize the image 81 | func Resize(img gocv.Mat, sz image.Point, w, h float64) gocv.Mat { 82 | dst := gocv.NewMat() 83 | gocv.Resize(img, &dst, sz, w, h, gocv.InterpolationFlags(1)) 84 | return dst 85 | } 86 | 87 | // Rotate rotate the image to 90, 180, 270 degrees 88 | func Rotate(img gocv.Mat, args ...interface{}) gocv.Mat { 89 | angle := 0 90 | if len(args) > 0 { 91 | angle = args[0].(int) 92 | } 93 | code := gocv.RotateFlag(angle) 94 | mask := gocv.NewMat() 95 | 96 | gocv.Rotate(img, &mask, code) 97 | return mask 98 | } 99 | 100 | // Crop crop the cv.Mat by rect with crabcut 101 | func Crop(img gocv.Mat, rect image.Rectangle) gocv.Mat { 102 | mask := gocv.NewMat() 103 | 104 | bgdModel := gocv.NewMat() 105 | defer bgdModel.Close() 106 | fgdModel := gocv.NewMat() 107 | defer fgdModel.Close() 108 | 109 | gocv.GrabCut(img, &mask, rect, &bgdModel, &fgdModel, 1, gocv.GCEval) 110 | return mask 111 | } 112 | 113 | // MarkPoint mark the point draw line to img 114 | func MarkPoint(img gocv.Mat, point image.Point, args ...interface{}) gocv.Mat { 115 | circle := false 116 | if len(args) > 0 { 117 | circle = args[0].(bool) 118 | } 119 | colors := uint8(100) 120 | if len(args) > 1 { 121 | colors = args[1].(uint8) 122 | } 123 | radius := 20 124 | if len(args) > 2 { 125 | radius = args[2].(int) 126 | } 127 | 128 | if circle { 129 | gocv.Circle(&img, point, radius, color.RGBA{255, 0, 0, 0}, 2) 130 | } 131 | 132 | rg := color.RGBA{colors, 0, 0, 0} 133 | x, y := point.X, point.Y 134 | gocv.Line(&img, image.Point{x - radius, y}, image.Point{x + radius, y}, 135 | rg, 2) 136 | gocv.Line(&img, image.Point{x, y - radius}, image.Point{x, y + radius}, 137 | rg, 2) 138 | return img 139 | } 140 | 141 | // MaskImg rectangle the mark to img 142 | func MaskImg(img *gocv.Mat, mark image.Rectangle, args ...interface{}) { 143 | lineW := -1 144 | if len(args) > 1 { 145 | lineW = args[1].(int) 146 | } 147 | offset := int(lineW / 2) 148 | 149 | colors := color.RGBA{255, 255, 255, 0} 150 | if len(args) > 0 { 151 | colors = args[0].(color.RGBA) 152 | } 153 | 154 | min := mark.Min 155 | max := mark.Max 156 | mark = image.Rectangle{ 157 | Min: image.Point{min.X - offset, min.Y - offset}, 158 | Max: image.Point{max.X + lineW, max.Y + lineW}} 159 | gocv.Rectangle(img, mark, colors, lineW) 160 | } 161 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/vcaesar/gcv 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.24.1 6 | 7 | require ( 8 | github.com/go-vgo/gt v0.42.0 9 | github.com/go-vgo/robotgo v0.110.8 10 | github.com/vcaesar/imgo v0.41.0 11 | gocv.io/x/gocv v0.41.0 12 | ) 13 | 14 | require ( 15 | github.com/dblohm7/wingoes v0.0.0-20240820181039-f2b84150679e // indirect 16 | github.com/ebitengine/purego v0.8.3 // indirect 17 | github.com/gen2brain/shm v0.1.1 // indirect 18 | github.com/go-ole/go-ole v1.3.0 // indirect 19 | github.com/godbus/dbus/v5 v5.1.0 // indirect 20 | github.com/jezek/xgb v1.1.1 // indirect 21 | github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 // indirect 22 | github.com/otiai10/gosseract v2.2.1+incompatible // indirect 23 | github.com/otiai10/mint v1.6.3 // indirect 24 | github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect 25 | github.com/robotn/xgb v0.10.0 // indirect 26 | github.com/robotn/xgbutil v0.10.0 // indirect 27 | github.com/shirou/gopsutil/v4 v4.25.4 // indirect 28 | github.com/tailscale/win v0.0.0-20250213223159-5992cb43ca35 // indirect 29 | github.com/tklauser/go-sysconf v0.3.15 // indirect 30 | github.com/tklauser/numcpus v0.10.0 // indirect 31 | github.com/vcaesar/gops v0.41.0 // indirect 32 | github.com/vcaesar/keycode v0.10.1 // indirect 33 | github.com/vcaesar/screenshot v0.11.1 // indirect 34 | github.com/vcaesar/tt v0.20.1 // indirect 35 | github.com/yusufpapurcu/wmi v1.2.4 // indirect 36 | golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect 37 | golang.org/x/image v0.27.0 // indirect 38 | golang.org/x/net v0.26.0 // indirect 39 | golang.org/x/sys v0.33.0 // indirect 40 | ) 41 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298/go.mod h1:D+QujdIlUNfa0igpNMk6UIvlb6C252URs4yupRUV4lQ= 2 | github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966/go.mod h1:Mid70uvE93zn9wgF92A/r5ixgnvX8Lh68fxp9KQBaI0= 3 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/dblohm7/wingoes v0.0.0-20240820181039-f2b84150679e h1:L+XrFvD0vBIBm+Wf9sFN6aU395t7JROoai0qXZraA4U= 6 | github.com/dblohm7/wingoes v0.0.0-20240820181039-f2b84150679e/go.mod h1:SUxUaAK/0UG5lYyZR1L1nC4AaYYvSSYTWQSH3FPcxKU= 7 | github.com/ebitengine/purego v0.8.3 h1:K+0AjQp63JEZTEMZiwsI9g0+hAMNohwUOtY0RPGexmc= 8 | github.com/ebitengine/purego v0.8.3/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= 9 | github.com/gen2brain/shm v0.1.1 h1:1cTVA5qcsUFixnDHl14TmRoxgfWEEZlTezpUj1vm5uQ= 10 | github.com/gen2brain/shm v0.1.1/go.mod h1:UgIcVtvmOu+aCJpqJX7GOtiN7X2ct+TKLg4RTxwPIUA= 11 | github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= 12 | github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= 13 | github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= 14 | github.com/go-vgo/gt v0.42.0 h1:8HUJhjVawBuQxZCRioBEXR/JqR9t5iHbgHAHGHOUZwM= 15 | github.com/go-vgo/gt v0.42.0/go.mod h1:LnBOW1OSqlsQB/pjc9E3QEuQ8qqWPDBZZ0J9DGVCRkI= 16 | github.com/go-vgo/robotgo v0.110.8 h1:tWoUyqlZgDJ61bQju3WGSb/NIIfNV4TkYL3GFeWcHio= 17 | github.com/go-vgo/robotgo v0.110.8/go.mod h1:45w33PzprtFncpw4cAt9SzMtSY9XnVfotu+RrCVN8JE= 18 | github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= 19 | github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 20 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 21 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 22 | github.com/jezek/xgb v1.1.1 h1:bE/r8ZZtSv7l9gk6nU0mYx51aXrvnyb44892TwSaqS4= 23 | github.com/jezek/xgb v1.1.1/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk= 24 | github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 h1:PpXWgLPs+Fqr325bN2FD2ISlRRztXibcX6e8f5FR5Dc= 25 | github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= 26 | github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= 27 | github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= 28 | github.com/otiai10/gosseract v2.2.1+incompatible h1:Ry5ltVdpdp4LAa2bMjsSJH34XHVOV7XMi41HtzL8X2I= 29 | github.com/otiai10/gosseract v2.2.1+incompatible/go.mod h1:XrzWItCzCpFRZ35n3YtVTgq5bLAhFIkascoRo8G32QE= 30 | github.com/otiai10/mint v1.6.3 h1:87qsV/aw1F5as1eH1zS/yqHY85ANKVMgkDrf9rcxbQs= 31 | github.com/otiai10/mint v1.6.3/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM= 32 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 33 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 34 | github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= 35 | github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= 36 | github.com/robotn/xgb v0.0.0-20190912153532-2cb92d044934/go.mod h1:SxQhJskUJ4rleVU44YvnrdvxQr0tKy5SRSigBrCgyyQ= 37 | github.com/robotn/xgb v0.10.0 h1:O3kFbIwtwZ3pgLbp1h5slCQ4OpY8BdwugJLrUe6GPIM= 38 | github.com/robotn/xgb v0.10.0/go.mod h1:SxQhJskUJ4rleVU44YvnrdvxQr0tKy5SRSigBrCgyyQ= 39 | github.com/robotn/xgbutil v0.10.0 h1:gvf7mGQqCWQ68aHRtCxgdewRk+/KAJui6l3MJQQRCKw= 40 | github.com/robotn/xgbutil v0.10.0/go.mod h1:svkDXUDQjUiWzLrA0OZgHc4lbOts3C+uRfP6/yjwYnU= 41 | github.com/shirou/gopsutil/v4 v4.25.4 h1:cdtFO363VEOOFrUCjZRh4XVJkb548lyF0q0uTeMqYPw= 42 | github.com/shirou/gopsutil/v4 v4.25.4/go.mod h1:xbuxyoZj+UsgnZrENu3lQivsngRR5BdjbJwf2fv4szA= 43 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 44 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 45 | github.com/tailscale/win v0.0.0-20250213223159-5992cb43ca35 h1:wAZbkTZkqDzWsqxPh2qkBd3KvFU7tcxV0BP0Rnhkxog= 46 | github.com/tailscale/win v0.0.0-20250213223159-5992cb43ca35/go.mod h1:aMd4yDHLjbOuYP6fMxj1d9ACDQlSWwYztcpybGHCQc8= 47 | github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA= 48 | github.com/tc-hib/winres v0.2.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk= 49 | github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= 50 | github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= 51 | github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= 52 | github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ= 53 | github.com/vcaesar/gops v0.41.0 h1:FG748Jyw3FOuZnbzSgB+CQSx2e5LbLCPWV2JU1brFdc= 54 | github.com/vcaesar/gops v0.41.0/go.mod h1:/3048L7Rj7QjQKTSB+kKc7hDm63YhTWy5QJ10TCP37A= 55 | github.com/vcaesar/imgo v0.41.0 h1:kNLYGrThXhB9Dd6IwFmfPnxq9P6yat2g7dpPjr7OWO8= 56 | github.com/vcaesar/imgo v0.41.0/go.mod h1:/LGOge8etlzaVu/7l+UfhJxR6QqaoX5yeuzGIMfWb4I= 57 | github.com/vcaesar/keycode v0.10.1 h1:0DesGmMAPWpYTCYddOFiCMKCDKgNnwiQa2QXindVUHw= 58 | github.com/vcaesar/keycode v0.10.1/go.mod h1:JNlY7xbKsh+LAGfY2j4M3znVrGEm5W1R8s/Uv6BJcfQ= 59 | github.com/vcaesar/screenshot v0.11.1 h1:GgPuN89XC4Yh38dLx4quPlSo3YiWWhwIria/j3LtrqU= 60 | github.com/vcaesar/screenshot v0.11.1/go.mod h1:gJNwHBiP1v1v7i8TQ4yV1XJtcyn2I/OJL7OziVQkwjs= 61 | github.com/vcaesar/tt v0.20.1 h1:D/jUeeVCNbq3ad8M7hhtB3J9x5RZ6I1n1eZ0BJp7M+4= 62 | github.com/vcaesar/tt v0.20.1/go.mod h1:cH2+AwGAJm19Wa6xvEa+0r+sXDJBT0QgNQey6mwqLeU= 63 | github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= 64 | github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= 65 | gocv.io/x/gocv v0.41.0 h1:KM+zRXUP28b6dHfhy+4JxDODbCNQNtLg8kio+YE7TqA= 66 | gocv.io/x/gocv v0.41.0/go.mod h1:zYdWMj29WAEznM3Y8NsU3A0TRq/wR/cy75jeUypThqU= 67 | golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI= 68 | golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ= 69 | golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w= 70 | golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g= 71 | golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= 72 | golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 73 | golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 74 | golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 75 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 76 | golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= 77 | golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 78 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 79 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 80 | --------------------------------------------------------------------------------