feat: vision for qr-scanning and decoding
This commit is contained in:
@@ -24,6 +24,7 @@ import (
|
|||||||
"git.weirdcat.su/weirdcat/auto-attendance/internal/linkvalidator"
|
"git.weirdcat.su/weirdcat/auto-attendance/internal/linkvalidator"
|
||||||
"git.weirdcat.su/weirdcat/auto-attendance/internal/logger"
|
"git.weirdcat.su/weirdcat/auto-attendance/internal/logger"
|
||||||
"git.weirdcat.su/weirdcat/auto-attendance/internal/screencapturer"
|
"git.weirdcat.su/weirdcat/auto-attendance/internal/screencapturer"
|
||||||
|
"git.weirdcat.su/weirdcat/auto-attendance/internal/vision"
|
||||||
"go.uber.org/fx"
|
"go.uber.org/fx"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -34,6 +35,7 @@ func main() {
|
|||||||
logger.NewLogger,
|
logger.NewLogger,
|
||||||
linkvalidator.NewLinkValidator,
|
linkvalidator.NewLinkValidator,
|
||||||
screencapturer.NewWholeScreenCapturer,
|
screencapturer.NewWholeScreenCapturer,
|
||||||
|
vision.NewVision,
|
||||||
),
|
),
|
||||||
fx.Invoke(func(log *logger.Logger, capturer screencapturer.ScreenCapturer) {
|
fx.Invoke(func(log *logger.Logger, capturer screencapturer.ScreenCapturer) {
|
||||||
log.Debug("starting application...")
|
log.Debug("starting application...")
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ require (
|
|||||||
github.com/jezek/xgb v1.1.1 // indirect
|
github.com/jezek/xgb v1.1.1 // indirect
|
||||||
github.com/kbinani/screenshot v0.0.0-20250624051815-089614a94018 // indirect
|
github.com/kbinani/screenshot v0.0.0-20250624051815-089614a94018 // indirect
|
||||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e // 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/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||||
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
||||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // 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/multierr v1.10.0 // indirect
|
||||||
go.uber.org/zap v1.26.0 // indirect
|
go.uber.org/zap v1.26.0 // indirect
|
||||||
go.yaml.in/yaml/v3 v3.0.4 // 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/sys v0.29.0 // indirect
|
||||||
golang.org/x/text v0.28.0 // indirect
|
golang.org/x/text v0.28.0 // indirect
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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/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 h1:H+t6A/QJMbhCSEH5rAuRxh+CtW96g0Or0Fxa9IKr4uc=
|
||||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
|
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 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
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=
|
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.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 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
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.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
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/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 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
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=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
|||||||
@@ -18,3 +18,71 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package vision
|
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}
|
||||||
|
}
|
||||||
|
|||||||
3
src/internal/vision/visiondata.go
Normal file
3
src/internal/vision/visiondata.go
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
package vision
|
||||||
|
|
||||||
|
type VisionData []string
|
||||||
Reference in New Issue
Block a user