复制项目

This commit is contained in:
kim.dev.6789
2026-01-14 22:16:44 +08:00
parent e2577b8cee
commit e50142a3b9
691 changed files with 97009 additions and 1 deletions

View File

@@ -0,0 +1,971 @@
// 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
}