// Package vision 提供帧差指纹、图片编码与(可选的)本地检测接口。 package vision import ( "image" "golang.org/x/image/draw" ) // detailSize 是细节网格的边长。相比 8×8 的感知哈希,这份高分辨率灰度 // 网格专门用来捕捉"布局没变、只是文字/局部内容变了"这类细粒度变化—— // 题目换了但背景一样时,粗哈希几乎不动,细节网格的平均差却会明显跳变。 const detailSize = 64 // Fingerprint 是用于判断"画面有没有变化"的感知哈希。 type Fingerprint struct { AHash uint64 // 均值哈希(8×8,抗噪、粗筛) DHash uint64 // 差分哈希(8×8,抗噪、粗筛) Mean uint8 // 平均亮度 Detail []byte // 细节灰度网格(detailSize×detailSize),用于细粒度内容差 } // grayGrid 把图像缩放到 w×h 的灰度图。 func grayGrid(src image.Image, w, h int) *image.Gray { dst := image.NewGray(image.Rect(0, 0, w, h)) draw.ApproxBiLinear.Scale(dst, dst.Bounds(), src, src.Bounds(), draw.Src, nil) return dst } // Of 计算图像指纹。 func Of(img image.Image) Fingerprint { if img == nil { return Fingerprint{} } small := grayGrid(img, 8, 8) var sum, n int for _, v := range small.Pix { sum += int(v) n++ } avg := sum / max(n, 1) mean := uint8(min(avg, 255)) var aHash uint64 for i, v := range small.Pix { if int(v) > avg { aHash |= 1 << uint(i) } } wide := grayGrid(img, 9, 8) var dHash uint64 for y := 0; y < 8; y++ { for x := 0; x < 8; x++ { l := wide.GrayAt(x, y).Y r := wide.GrayAt(x+1, y).Y if l > r { dHash |= 1 << uint(y*8+x) } } } // 细节网格:把画面缩到 detailSize×detailSize 的灰度图, // 用于和上一帧做逐像素的归一化平均差(MAD),比感知哈希灵敏得多。 detailGrid := grayGrid(img, detailSize, detailSize) detail := make([]byte, len(detailGrid.Pix)) copy(detail, detailGrid.Pix) return Fingerprint{AHash: aHash, DHash: dHash, Mean: mean, Detail: detail} } // Distance 返回两个指纹的汉明距离(取两种哈希的较大值,偏向灵敏)。 func (f Fingerprint) Distance(o Fingerprint) int { return max(bitsSet(f.AHash^o.AHash), bitsSet(f.DHash^o.DHash)) } // DetailDiff 返回两份细节网格的归一化平均绝对差(0~1)。 // 它衡量"逐像素平均变了多少",对文字替换、局部内容更新这类变化远比对 // 比哈希敏感,是提升画面更新识别灵敏度的主力信号。 func (f Fingerprint) DetailDiff(o Fingerprint) float64 { a, b := f.Detail, o.Detail if len(a) == 0 || len(b) == 0 || len(a) != len(b) { return 0 } var sum int for i := range a { d := int(a[i]) - int(b[i]) if d < 0 { d = -d } sum += d } return float64(sum) / (float64(len(a)) * 255.0) } // Changed 判断画面是否发生了足以触发提问的变化。 // 任一信号超过阈值即判定为变化:感知哈希(抗噪粗筛)命中,或细节网格的 // 归一化平均差超过 sensitivity(细粒度内容变化)。 func (f Fingerprint) Changed(o Fingerprint, threshold int, sensitivity float64) bool { if threshold <= 0 { threshold = 5 } if sensitivity <= 0 { sensitivity = 0.01 } if f.Distance(o) > threshold { return true } return f.DetailDiff(o) > sensitivity } func bitsSet(v uint64) int { n := 0 for v != 0 { v &= v - 1 n++ } return n }