Files
open-im-server-deploy/internal/rpc/msg/qrcode_decoder.go
kim.dev.6789 e50142a3b9 复制项目
2026-01-14 22:16:44 +08:00

972 lines
23 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package msg
import (
"context"
"fmt"
"image"
"image/color"
_ "image/jpeg"
_ "image/png"
"math"
"os"
"sync"
"github.com/makiuchi-d/gozxing"
"github.com/makiuchi-d/gozxing/qrcode"
"github.com/openimsdk/tools/log"
)
// openImageFile 打开图片文件
func openImageFile(imagePath string) (*os.File, error) {
file, err := os.Open(imagePath)
if err != nil {
return nil, fmt.Errorf("无法打开文件: %v", err)
}
return file, nil
}
// QRDecoder 二维码解码器接口
type QRDecoder interface {
Name() string
Decode(ctx context.Context, imagePath string, logPrefix string) (bool, error) // 返回是否检测到二维码
}
// ============================================================================
// QuircDecoder - Quirc解码器包装
// ============================================================================
// QuircDecoder 使用 Quirc 库的解码器
type QuircDecoder struct {
detectFunc func([]uint8, int, int) (bool, error)
}
// NewQuircDecoder 创建Quirc解码器
func NewQuircDecoder(detectFunc func([]uint8, int, int) (bool, error)) *QuircDecoder {
return &QuircDecoder{detectFunc: detectFunc}
}
func (d *QuircDecoder) Name() string {
return "quirc"
}
func (d *QuircDecoder) Decode(ctx context.Context, imagePath string, logPrefix string) (bool, error) {
if d.detectFunc == nil {
return false, fmt.Errorf("quirc 解码器未启用")
}
// 打开并解码图片
file, err := openImageFile(imagePath)
if err != nil {
log.ZError(ctx, "打开图片文件失败", err, "imagePath", imagePath)
return false, err
}
defer file.Close()
img, _, err := image.Decode(file)
if err != nil {
log.ZError(ctx, "解码图片失败", err, "imagePath", imagePath)
return false, fmt.Errorf("无法解码图片: %v", err)
}
bounds := img.Bounds()
width := bounds.Dx()
height := bounds.Dy()
// 转换为灰度图
grayData := convertToGrayscale(img, width, height)
// 调用Quirc检测
hasQRCode, err := d.detectFunc(grayData, width, height)
if err != nil {
log.ZError(ctx, "Quirc检测失败", err, "width", width, "height", height)
return false, err
}
return hasQRCode, nil
}
// ============================================================================
// CustomQRDecoder - 自定义解码器(兼容圆形角)
// ============================================================================
// CustomQRDecoder 自定义二维码解码器,兼容圆形角等特殊格式
type CustomQRDecoder struct{}
func (d *CustomQRDecoder) Name() string {
return "custom (圆形角兼容)"
}
// Decode 解码二维码,返回是否检测到二维码
func (d *CustomQRDecoder) Decode(ctx context.Context, imagePath string, logPrefix string) (bool, error) {
file, err := openImageFile(imagePath)
if err != nil {
log.ZError(ctx, "打开图片文件失败", err, "imagePath", imagePath)
return false, fmt.Errorf("无法打开文件: %v", err)
}
defer file.Close()
img, _, err := image.Decode(file)
if err != nil {
log.ZError(ctx, "解码图片失败", err, "imagePath", imagePath)
return false, fmt.Errorf("无法解码图片: %v", err)
}
bounds := img.Bounds()
width := bounds.Dx()
height := bounds.Dy()
reader := qrcode.NewQRCodeReader()
hints := make(map[gozxing.DecodeHintType]interface{})
hints[gozxing.DecodeHintType_TRY_HARDER] = true
hints[gozxing.DecodeHintType_PURE_BARCODE] = false
hints[gozxing.DecodeHintType_CHARACTER_SET] = "UTF-8"
// 尝试直接解码
bitmap, err := gozxing.NewBinaryBitmapFromImage(img)
if err == nil {
if _, err := reader.Decode(bitmap, hints); err == nil {
return true, nil
}
// 尝试不使用PURE_BARCODE
delete(hints, gozxing.DecodeHintType_PURE_BARCODE)
if _, err := reader.Decode(bitmap, hints); err == nil {
return true, nil
}
hints[gozxing.DecodeHintType_PURE_BARCODE] = false
}
// 尝试多尺度缩放
scales := []float64{1.0, 1.5, 2.0, 0.75, 0.5}
for _, scale := range scales {
scaledImg := scaleImage(img, width, height, scale)
if scaledImg == nil {
continue
}
scaledBitmap, err := gozxing.NewBinaryBitmapFromImage(scaledImg)
if err == nil {
if _, err := reader.Decode(scaledBitmap, hints); err == nil {
return true, nil
}
}
}
// 转换为灰度图进行预处理
grayData := convertToGrayscale(img, width, height)
// 尝试多种预处理方法
preprocessMethods := []struct {
name string
fn func([]byte, int, int) []byte
}{
{"Otsu二值化", enhanceImageOtsu},
{"标准增强", enhanceImage},
{"强对比度", enhanceImageStrong},
{"圆形角处理", enhanceImageForRoundedCorners},
{"去噪+锐化", enhanceImageDenoiseSharpen},
{"高斯模糊+锐化", enhanceImageGaussianSharpen},
}
scalesForPreprocessed := []float64{1.0, 2.0, 1.5, 1.2, 0.8}
for _, method := range preprocessMethods {
processed := method.fn(grayData, width, height)
// 快速检测定位图案
corners := detectCornersFast(processed, width, height)
if len(corners) < 2 {
// 如果没有检测到足够的定位图案,仍然尝试解码
}
processedImg := createImageFromGrayscale(processed, width, height)
bitmap2, err := gozxing.NewBinaryBitmapFromImage(processedImg)
if err == nil {
if _, err := reader.Decode(bitmap2, hints); err == nil {
return true, nil
}
delete(hints, gozxing.DecodeHintType_PURE_BARCODE)
if _, err := reader.Decode(bitmap2, hints); err == nil {
return true, nil
}
hints[gozxing.DecodeHintType_PURE_BARCODE] = false
}
// 对预处理后的图像进行多尺度缩放
for _, scale := range scalesForPreprocessed {
scaledProcessed := scaleGrayscaleImage(processed, width, height, scale)
if scaledProcessed == nil {
continue
}
scaledImg := createImageFromGrayscale(scaledProcessed.data, scaledProcessed.width, scaledProcessed.height)
scaledBitmap, err := gozxing.NewBinaryBitmapFromImage(scaledImg)
if err == nil {
if _, err := reader.Decode(scaledBitmap, hints); err == nil {
return true, nil
}
delete(hints, gozxing.DecodeHintType_PURE_BARCODE)
if _, err := reader.Decode(scaledBitmap, hints); err == nil {
return true, nil
}
hints[gozxing.DecodeHintType_PURE_BARCODE] = false
}
}
}
return false, nil
}
// ============================================================================
// ParallelQRDecoder - 并行解码器
// ============================================================================
// ParallelQRDecoder 并行解码器,同时运行 quirc 和 custom 解码器
type ParallelQRDecoder struct {
quircDecoder QRDecoder
customDecoder QRDecoder
}
// NewParallelQRDecoder 创建并行解码器
func NewParallelQRDecoder(quircDecoder, customDecoder QRDecoder) *ParallelQRDecoder {
return &ParallelQRDecoder{
quircDecoder: quircDecoder,
customDecoder: customDecoder,
}
}
func (d *ParallelQRDecoder) Name() string {
return "parallel (quirc + custom)"
}
// Decode 并行解码:同时运行 quirc 和 custom任一成功立即返回
func (d *ParallelQRDecoder) Decode(ctx context.Context, imagePath string, logPrefix string) (bool, error) {
type decodeResult struct {
hasQRCode bool
err error
name string
}
resultChan := make(chan decodeResult, 2)
var wg sync.WaitGroup
var mu sync.Mutex
var quircErr error
var customErr error
// 启动Quirc解码
if d.quircDecoder != nil {
wg.Add(1)
go func() {
defer wg.Done()
hasQRCode, err := d.quircDecoder.Decode(ctx, imagePath, logPrefix)
mu.Lock()
if err != nil {
quircErr = err
}
mu.Unlock()
resultChan <- decodeResult{
hasQRCode: hasQRCode,
err: err,
name: d.quircDecoder.Name(),
}
}()
}
// 启动Custom解码
if d.customDecoder != nil {
wg.Add(1)
go func() {
defer wg.Done()
hasQRCode, err := d.customDecoder.Decode(ctx, imagePath, logPrefix)
mu.Lock()
if err != nil {
customErr = err
}
mu.Unlock()
resultChan <- decodeResult{
hasQRCode: hasQRCode,
err: err,
name: d.customDecoder.Name(),
}
}()
}
// 等待第一个结果
var firstResult decodeResult
var secondResult decodeResult
firstResult = <-resultChan
if firstResult.hasQRCode {
// 如果检测到二维码,立即返回
go func() {
<-resultChan
wg.Wait()
}()
return true, nil
}
// 等待第二个结果
if d.quircDecoder != nil && d.customDecoder != nil {
secondResult = <-resultChan
if secondResult.hasQRCode {
wg.Wait()
return true, nil
}
}
wg.Wait()
// 如果都失败,返回错误
if firstResult.err != nil && secondResult.err != nil {
log.ZError(ctx, "并行解码失败,两个解码器都失败", fmt.Errorf("quirc错误=%v, custom错误=%v", quircErr, customErr),
"quircError", quircErr,
"customError", customErr)
return false, fmt.Errorf("quirc 和 custom 都解码失败: quirc错误=%v, custom错误=%v", quircErr, customErr)
}
return false, nil
}
// ============================================================================
// 辅助函数
// ============================================================================
// convertToGrayscale 转换为灰度图
func convertToGrayscale(img image.Image, width, height int) []byte {
grayData := make([]byte, width*height)
bounds := img.Bounds()
if ycbcr, ok := img.(*image.YCbCr); ok {
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
yi := ycbcr.YOffset(x+bounds.Min.X, y+bounds.Min.Y)
grayData[y*width+x] = ycbcr.Y[yi]
}
}
return grayData
}
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
r, g, b, _ := img.At(x+bounds.Min.X, y+bounds.Min.Y).RGBA()
r8 := uint8(r >> 8)
g8 := uint8(g >> 8)
b8 := uint8(b >> 8)
gray := byte((uint16(r8)*299 + uint16(g8)*587 + uint16(b8)*114) / 1000)
grayData[y*width+x] = gray
}
}
return grayData
}
// enhanceImage 图像增强(标准方法)
func enhanceImage(data []byte, width, height int) []byte {
enhanced := make([]byte, len(data))
copy(enhanced, data)
minVal := uint8(255)
maxVal := uint8(0)
for _, v := range data {
if v < minVal {
minVal = v
}
if v > maxVal {
maxVal = v
}
}
if maxVal-minVal < 50 {
rangeVal := maxVal - minVal
if rangeVal == 0 {
rangeVal = 1
}
for i, v := range data {
stretched := uint8((uint16(v-minVal) * 255) / uint16(rangeVal))
enhanced[i] = stretched
}
}
return enhanced
}
// enhanceImageStrong 强对比度增强
func enhanceImageStrong(data []byte, width, height int) []byte {
enhanced := make([]byte, len(data))
histogram := make([]int, 256)
for _, v := range data {
histogram[v]++
}
cdf := make([]int, 256)
cdf[0] = histogram[0]
for i := 1; i < 256; i++ {
cdf[i] = cdf[i-1] + histogram[i]
}
total := len(data)
for i, v := range data {
if total > 0 {
enhanced[i] = uint8((cdf[v] * 255) / total)
}
}
return enhanced
}
// enhanceImageForRoundedCorners 针对圆形角的特殊处理
func enhanceImageForRoundedCorners(data []byte, width, height int) []byte {
enhanced := make([]byte, len(data))
copy(enhanced, data)
minVal := uint8(255)
maxVal := uint8(0)
for _, v := range data {
if v < minVal {
minVal = v
}
if v > maxVal {
maxVal = v
}
}
if maxVal-minVal < 100 {
rangeVal := maxVal - minVal
if rangeVal == 0 {
rangeVal = 1
}
for i, v := range data {
stretched := uint8((uint16(v-minVal) * 255) / uint16(rangeVal))
enhanced[i] = stretched
}
}
// 形态学操作:先腐蚀后膨胀
dilated := make([]byte, len(enhanced))
kernelSize := 3
halfKernel := kernelSize / 2
// 腐蚀(最小值滤波)
for y := halfKernel; y < height-halfKernel; y++ {
for x := halfKernel; x < width-halfKernel; x++ {
minVal := uint8(255)
for ky := -halfKernel; ky <= halfKernel; ky++ {
for kx := -halfKernel; kx <= halfKernel; kx++ {
idx := (y+ky)*width + (x + kx)
if enhanced[idx] < minVal {
minVal = enhanced[idx]
}
}
}
dilated[y*width+x] = minVal
}
}
// 膨胀(最大值滤波)
for y := halfKernel; y < height-halfKernel; y++ {
for x := halfKernel; x < width-halfKernel; x++ {
maxVal := uint8(0)
for ky := -halfKernel; ky <= halfKernel; ky++ {
for kx := -halfKernel; kx <= halfKernel; kx++ {
idx := (y+ky)*width + (x + kx)
if dilated[idx] > maxVal {
maxVal = dilated[idx]
}
}
}
enhanced[y*width+x] = maxVal
}
}
// 边界保持原值
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
if y < halfKernel || y >= height-halfKernel || x < halfKernel || x >= width-halfKernel {
enhanced[y*width+x] = data[y*width+x]
}
}
}
return enhanced
}
// enhanceImageDenoiseSharpen 去噪+锐化处理
func enhanceImageDenoiseSharpen(data []byte, width, height int) []byte {
denoised := medianFilter(data, width, height, 3)
sharpened := sharpenImage(denoised, width, height)
return sharpened
}
// medianFilter 中值滤波去噪
func medianFilter(data []byte, width, height, kernelSize int) []byte {
filtered := make([]byte, len(data))
halfKernel := kernelSize / 2
kernelArea := kernelSize * kernelSize
values := make([]byte, kernelArea)
for y := halfKernel; y < height-halfKernel; y++ {
for x := halfKernel; x < width-halfKernel; x++ {
idx := 0
for ky := -halfKernel; ky <= halfKernel; ky++ {
for kx := -halfKernel; kx <= halfKernel; kx++ {
values[idx] = data[(y+ky)*width+(x+kx)]
idx++
}
}
filtered[y*width+x] = quickSelectMedian(values)
}
}
// 边界保持原值
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
if y < halfKernel || y >= height-halfKernel || x < halfKernel || x >= width-halfKernel {
filtered[y*width+x] = data[y*width+x]
}
}
}
return filtered
}
// quickSelectMedian 快速选择中值
func quickSelectMedian(arr []byte) byte {
n := len(arr)
if n <= 7 {
// 小数组使用插入排序
for i := 1; i < n; i++ {
key := arr[i]
j := i - 1
for j >= 0 && arr[j] > key {
arr[j+1] = arr[j]
j--
}
arr[j+1] = key
}
return arr[n/2]
}
return quickSelect(arr, 0, n-1, n/2)
}
// quickSelect 快速选择第k小的元素
func quickSelect(arr []byte, left, right, k int) byte {
if left == right {
return arr[left]
}
pivotIndex := partition(arr, left, right)
if k == pivotIndex {
return arr[k]
} else if k < pivotIndex {
return quickSelect(arr, left, pivotIndex-1, k)
}
return quickSelect(arr, pivotIndex+1, right, k)
}
func partition(arr []byte, left, right int) int {
pivot := arr[right]
i := left
for j := left; j < right; j++ {
if arr[j] <= pivot {
arr[i], arr[j] = arr[j], arr[i]
i++
}
}
arr[i], arr[right] = arr[right], arr[i]
return i
}
// sharpenImage 锐化处理
func sharpenImage(data []byte, width, height int) []byte {
sharpened := make([]byte, len(data))
kernel := []int{0, -1, 0, -1, 5, -1, 0, -1, 0}
for y := 1; y < height-1; y++ {
for x := 1; x < width-1; x++ {
sum := 0
idx := 0
for ky := -1; ky <= 1; ky++ {
for kx := -1; kx <= 1; kx++ {
pixelIdx := (y+ky)*width + (x + kx)
sum += int(data[pixelIdx]) * kernel[idx]
idx++
}
}
if sum < 0 {
sum = 0
}
if sum > 255 {
sum = 255
}
sharpened[y*width+x] = uint8(sum)
}
}
// 边界保持原值
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
if y == 0 || y == height-1 || x == 0 || x == width-1 {
sharpened[y*width+x] = data[y*width+x]
}
}
}
return sharpened
}
// enhanceImageOtsu Otsu自适应阈值二值化
func enhanceImageOtsu(data []byte, width, height int) []byte {
threshold := calculateOtsuThreshold(data)
binary := make([]byte, len(data))
for i := range data {
if data[i] > threshold {
binary[i] = 255
}
}
return binary
}
// calculateOtsuThreshold 计算Otsu自适应阈值
func calculateOtsuThreshold(data []byte) uint8 {
histogram := make([]int, 256)
for _, v := range data {
histogram[v]++
}
total := len(data)
if total == 0 {
return 128
}
var threshold uint8
var maxVar float64
var sum int
for i := 0; i < 256; i++ {
sum += i * histogram[i]
}
var sum1 int
var wB int
for i := 0; i < 256; i++ {
wB += histogram[i]
if wB == 0 {
continue
}
wF := total - wB
if wF == 0 {
break
}
sum1 += i * histogram[i]
mB := float64(sum1) / float64(wB)
mF := float64(sum-sum1) / float64(wF)
varBetween := float64(wB) * float64(wF) * (mB - mF) * (mB - mF)
if varBetween > maxVar {
maxVar = varBetween
threshold = uint8(i)
}
}
return threshold
}
// enhanceImageGaussianSharpen 高斯模糊+锐化
func enhanceImageGaussianSharpen(data []byte, width, height int) []byte {
blurred := gaussianBlur(data, width, height, 1.0)
sharpened := sharpenImage(blurred, width, height)
enhanced := enhanceImage(sharpened, width, height)
return enhanced
}
// gaussianBlur 高斯模糊
func gaussianBlur(data []byte, width, height int, sigma float64) []byte {
blurred := make([]byte, len(data))
kernelSize := 5
halfKernel := kernelSize / 2
kernel := make([]float64, kernelSize*kernelSize)
sum := 0.0
for y := -halfKernel; y <= halfKernel; y++ {
for x := -halfKernel; x <= halfKernel; x++ {
idx := (y+halfKernel)*kernelSize + (x + halfKernel)
val := math.Exp(-(float64(x*x+y*y) / (2 * sigma * sigma)))
kernel[idx] = val
sum += val
}
}
for i := range kernel {
kernel[i] /= sum
}
for y := halfKernel; y < height-halfKernel; y++ {
for x := halfKernel; x < width-halfKernel; x++ {
var val float64
idx := 0
for ky := -halfKernel; ky <= halfKernel; ky++ {
for kx := -halfKernel; kx <= halfKernel; kx++ {
pixelIdx := (y+ky)*width + (x + kx)
val += float64(data[pixelIdx]) * kernel[idx]
idx++
}
}
blurred[y*width+x] = uint8(val)
}
}
// 边界保持原值
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
if y < halfKernel || y >= height-halfKernel || x < halfKernel || x >= width-halfKernel {
blurred[y*width+x] = data[y*width+x]
}
}
}
return blurred
}
// createImageFromGrayscale 从灰度数据创建图像
func createImageFromGrayscale(data []byte, width, height int) image.Image {
img := image.NewGray(image.Rect(0, 0, width, height))
for y := 0; y < height; y++ {
rowStart := y * width
rowEnd := rowStart + width
if rowEnd > len(data) {
rowEnd = len(data)
}
copy(img.Pix[y*img.Stride:], data[rowStart:rowEnd])
}
return img
}
// scaledImage 缩放后的图像数据
type scaledImage struct {
data []byte
width int
height int
}
// scaleImage 缩放图像
func scaleImage(img image.Image, origWidth, origHeight int, scale float64) image.Image {
if scale == 1.0 {
return img
}
newWidth := int(float64(origWidth) * scale)
newHeight := int(float64(origHeight) * scale)
if newWidth < 50 || newHeight < 50 {
return nil
}
if newWidth > 1500 || newHeight > 1500 {
return nil
}
scaled := image.NewRGBA(image.Rect(0, 0, newWidth, newHeight))
bounds := img.Bounds()
for y := 0; y < newHeight; y++ {
for x := 0; x < newWidth; x++ {
srcX := float64(x) / scale
srcY := float64(y) / scale
x1 := int(srcX)
y1 := int(srcY)
x2 := x1 + 1
y2 := y1 + 1
if x2 >= bounds.Dx() {
x2 = bounds.Dx() - 1
}
if y2 >= bounds.Dy() {
y2 = bounds.Dy() - 1
}
fx := srcX - float64(x1)
fy := srcY - float64(y1)
c11 := getPixelColor(img, bounds.Min.X+x1, bounds.Min.Y+y1)
c12 := getPixelColor(img, bounds.Min.X+x1, bounds.Min.Y+y2)
c21 := getPixelColor(img, bounds.Min.X+x2, bounds.Min.Y+y1)
c22 := getPixelColor(img, bounds.Min.X+x2, bounds.Min.Y+y2)
r := uint8(float64(c11.R)*(1-fx)*(1-fy) + float64(c21.R)*fx*(1-fy) + float64(c12.R)*(1-fx)*fy + float64(c22.R)*fx*fy)
g := uint8(float64(c11.G)*(1-fx)*(1-fy) + float64(c21.G)*fx*(1-fy) + float64(c12.G)*(1-fx)*fy + float64(c22.G)*fx*fy)
b := uint8(float64(c11.B)*(1-fx)*(1-fy) + float64(c21.B)*fx*(1-fy) + float64(c12.B)*(1-fx)*fy + float64(c22.B)*fx*fy)
scaled.Set(x, y, color.RGBA{R: r, G: g, B: b, A: 255})
}
}
return scaled
}
// getPixelColor 获取像素颜色
func getPixelColor(img image.Image, x, y int) color.RGBA {
r, g, b, _ := img.At(x, y).RGBA()
return color.RGBA{
R: uint8(r >> 8),
G: uint8(g >> 8),
B: uint8(b >> 8),
A: 255,
}
}
// scaleGrayscaleImage 缩放灰度图像
func scaleGrayscaleImage(data []byte, origWidth, origHeight int, scale float64) *scaledImage {
if scale == 1.0 {
return &scaledImage{data: data, width: origWidth, height: origHeight}
}
newWidth := int(float64(origWidth) * scale)
newHeight := int(float64(origHeight) * scale)
if newWidth < 21 || newHeight < 21 || newWidth > 2000 || newHeight > 2000 {
return nil
}
scaled := make([]byte, newWidth*newHeight)
for y := 0; y < newHeight; y++ {
for x := 0; x < newWidth; x++ {
srcX := float64(x) / scale
srcY := float64(y) / scale
x1 := int(srcX)
y1 := int(srcY)
x2 := x1 + 1
y2 := y1 + 1
if x2 >= origWidth {
x2 = origWidth - 1
}
if y2 >= origHeight {
y2 = origHeight - 1
}
fx := srcX - float64(x1)
fy := srcY - float64(y1)
v11 := float64(data[y1*origWidth+x1])
v12 := float64(data[y2*origWidth+x1])
v21 := float64(data[y1*origWidth+x2])
v22 := float64(data[y2*origWidth+x2])
val := v11*(1-fx)*(1-fy) + v21*fx*(1-fy) + v12*(1-fx)*fy + v22*fx*fy
scaled[y*newWidth+x] = uint8(val)
}
}
return &scaledImage{data: scaled, width: newWidth, height: newHeight}
}
// Point 表示一个点
type Point struct {
X, Y int
}
// Corner 表示检测到的定位图案角点
type Corner struct {
Center Point
Size int
Type int
}
// detectCornersFast 快速检测定位图案
func detectCornersFast(data []byte, width, height int) []Corner {
var corners []Corner
scanStep := max(2, min(width, height)/80)
if scanStep < 1 {
scanStep = 1
}
for y := scanStep * 3; y < height-scanStep*3; y += scanStep {
for x := scanStep * 3; x < width-scanStep*3; x += scanStep {
if isFinderPatternFast(data, width, height, x, y) {
corners = append(corners, Corner{
Center: Point{X: x, Y: y},
Size: 20,
})
if len(corners) >= 3 {
return corners
}
}
}
}
return corners
}
// isFinderPatternFast 快速检测定位图案
func isFinderPatternFast(data []byte, width, height, x, y int) bool {
centerIdx := y*width + x
if centerIdx < 0 || centerIdx >= len(data) {
return false
}
if data[centerIdx] > 180 {
return false
}
radius := min(width, height) / 15
if radius < 3 {
radius = 3
}
if radius > 30 {
radius = 30
}
directions := []struct{ dx, dy int }{{radius, 0}, {-radius, 0}, {0, radius}, {0, -radius}}
blackCount := 0
whiteCount := 0
for _, dir := range directions {
nx := x + dir.dx
ny := y + dir.dy
if nx >= 0 && nx < width && ny >= 0 && ny < height {
idx := ny*width + nx
if idx >= 0 && idx < len(data) {
if data[idx] < 128 {
blackCount++
} else {
whiteCount++
}
}
}
}
return blackCount >= 2 && whiteCount >= 2
}
// 辅助函数
func max(a, b int) int {
if a > b {
return a
}
return b
}
func min(a, b int) int {
if a < b {
return a
}
return b
}