From 4876ba5954421f68e41cab472c25a9b28a7cdd99 Mon Sep 17 00:00:00 2001 From: Nikolai Papin Date: Tue, 25 Nov 2025 15:49:20 +0300 Subject: [PATCH] feat: vision for qr-scanning and decoding --- src/cmd/main.go | 2 + src/go.mod | 3 ++ src/go.sum | 6 +++ src/internal/vision/vision.go | 70 ++++++++++++++++++++++++++++++- src/internal/vision/visiondata.go | 3 ++ 5 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 src/internal/vision/visiondata.go diff --git a/src/cmd/main.go b/src/cmd/main.go index d8dff87..75773bb 100644 --- a/src/cmd/main.go +++ b/src/cmd/main.go @@ -24,6 +24,7 @@ import ( "git.weirdcat.su/weirdcat/auto-attendance/internal/linkvalidator" "git.weirdcat.su/weirdcat/auto-attendance/internal/logger" "git.weirdcat.su/weirdcat/auto-attendance/internal/screencapturer" + "git.weirdcat.su/weirdcat/auto-attendance/internal/vision" "go.uber.org/fx" ) @@ -34,6 +35,7 @@ func main() { logger.NewLogger, linkvalidator.NewLinkValidator, screencapturer.NewWholeScreenCapturer, + vision.NewVision, ), fx.Invoke(func(log *logger.Logger, capturer screencapturer.ScreenCapturer) { log.Debug("starting application...") diff --git a/src/go.mod b/src/go.mod index 724c113..a11df27 100644 --- a/src/go.mod +++ b/src/go.mod @@ -12,6 +12,7 @@ require ( github.com/jezek/xgb v1.1.1 // indirect github.com/kbinani/screenshot v0.0.0-20250624051815-089614a94018 // indirect github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect + github.com/makiuchi-d/gozxing v0.1.1 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/sagikazarmark/locafero v0.11.0 // indirect github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect @@ -24,6 +25,8 @@ require ( go.uber.org/multierr v1.10.0 // indirect go.uber.org/zap v1.26.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect + gocv.io/x/gocv v0.42.0 // indirect golang.org/x/sys v0.29.0 // indirect golang.org/x/text v0.28.0 // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect ) diff --git a/src/go.sum b/src/go.sum index e419078..c83bb0a 100644 --- a/src/go.sum +++ b/src/go.sum @@ -12,6 +12,8 @@ github.com/kbinani/screenshot v0.0.0-20250624051815-089614a94018 h1:NQYgMY188uWr github.com/kbinani/screenshot v0.0.0-20250624051815-089614a94018/go.mod h1:Pmpz2BLf55auQZ67u3rvyI2vAQvNetkK/4zYUmpauZQ= github.com/lxn/win v0.0.0-20210218163916-a377121e959e h1:H+t6A/QJMbhCSEH5rAuRxh+CtW96g0Or0Fxa9IKr4uc= github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk= +github.com/makiuchi-d/gozxing v0.1.1 h1:xxqijhoedi+/lZlhINteGbywIrewVdVv2wl9r5O9S1I= +github.com/makiuchi-d/gozxing v0.1.1/go.mod h1:eRIHbOjX7QWxLIDJoQuMLhuXg9LAuw6znsUtRkNw9DU= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= @@ -38,9 +40,13 @@ go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +gocv.io/x/gocv v0.42.0 h1:AAsrFJH2aIsQHukkCovWqj0MCGZleQpVyf5gNVRXjQI= +gocv.io/x/gocv v0.42.0/go.mod h1:zYdWMj29WAEznM3Y8NsU3A0TRq/wR/cy75jeUypThqU= golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/src/internal/vision/vision.go b/src/internal/vision/vision.go index e4ed0da..867ca33 100644 --- a/src/internal/vision/vision.go +++ b/src/internal/vision/vision.go @@ -1,7 +1,7 @@ // Copyright (c) 2025 Nikolai Papin // // This file is part of the Auto Attendance app that looks for -// self-attend QR-codes during lectures and opens their URLs in your +// self-attend QR-codes during lectures and opens their URLs in your // browser. // // This program is free software: you can redistribute it and/or modify @@ -18,3 +18,71 @@ // along with this program. If not, see . package vision + +import ( + "errors" + + "git.weirdcat.su/weirdcat/auto-attendance/internal/logger" + "github.com/makiuchi-d/gozxing" + "github.com/makiuchi-d/gozxing/qrcode" + "gocv.io/x/gocv" +) + +type Vision interface { + AnalyzeImage(filePath string) (data VisionData, err error) +} + +type visionImpl struct { + log *logger.Logger +} + +var ( + ErrImageEmpty = errors.New("Image from file was empty") +) + +// AnalyzeImage implements Vision. +func (v *visionImpl) AnalyzeImage(filePath string) (data VisionData, err error) { + v.log.Debug("analyzing image for qr codes", "filePath", filePath) + + img := gocv.IMRead(filePath, gocv.IMReadColor) + if img.Empty() { + v.log.Error("could not read image file", "filePath", filePath) + return VisionData{}, ErrImageEmpty + } + defer img.Close() + + // Convert to grayscale for QR code detection + gray := gocv.NewMat() + defer gray.Close() + gocv.CvtColor(img, &gray, gocv.ColorBGRToGray) + + // Convert gocv.Mat to image.Image for gozxing + imgGray, err := gray.ToImage() + if err != nil { + v.log.Error("failed to convert image", "error", err) + return VisionData{}, err + } + + // Create a binary bitmap from the image + bmp, err := gozxing.NewBinaryBitmapFromImage(imgGray) + if err != nil { + v.log.Error("failed to create binary bitmap", "error", err) + return VisionData{}, err + } + + reader := qrcode.NewQRCodeReader() + result, err := reader.Decode(bmp, nil) + if err != nil { + v.log.Debug("no qr code found in image", "filePath", filePath, "error", err) + return VisionData{}, nil + } + + v.log.Info("QR code decoded successfully", "content", result.GetText()) + + data = VisionData{result.GetText()} + return data, nil +} + +func NewVision(log *logger.Logger) Vision { + return &visionImpl{log: log} +} diff --git a/src/internal/vision/visiondata.go b/src/internal/vision/visiondata.go new file mode 100644 index 0000000..40f2700 --- /dev/null +++ b/src/internal/vision/visiondata.go @@ -0,0 +1,3 @@ +package vision + +type VisionData []string