更新代码
This commit is contained in:
1 parent
eda7e8bc96
commit
6ff632e0b0
19 files changed
+2033
-197
No files matched your search
+45
-6
@@ -7,11 +7,17 @@ import (
|
||||
"golang.org/x/image/draw"
|
||||
)
|
||||
|
||||
// detailSize 是细节网格的边长。相比 8×8 的感知哈希,这份高分辨率灰度
|
||||
// 网格专门用来捕捉"布局没变、只是文字/局部内容变了"这类细粒度变化——
|
||||
// 题目换了但背景一样时,粗哈希几乎不动,细节网格的平均差却会明显跳变。
|
||||
const detailSize = 64
|
||||
|
||||
// Fingerprint 是用于判断"画面有没有变化"的感知哈希。
|
||||
type Fingerprint struct {
|
||||
AHash uint64 // 均值哈希
|
||||
DHash uint64 // 差分哈希
|
||||
Mean uint8 // 平均亮度
|
||||
AHash uint64 // 均值哈希(8×8,抗噪、粗筛)
|
||||
DHash uint64 // 差分哈希(8×8,抗噪、粗筛)
|
||||
Mean uint8 // 平均亮度
|
||||
Detail []byte // 细节灰度网格(detailSize×detailSize),用于细粒度内容差
|
||||
}
|
||||
|
||||
// grayGrid 把图像缩放到 w×h 的灰度图。
|
||||
@@ -55,7 +61,13 @@ func Of(img image.Image) Fingerprint {
|
||||
}
|
||||
}
|
||||
|
||||
return Fingerprint{AHash: aHash, DHash: dHash, Mean: mean}
|
||||
// 细节网格:把画面缩到 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 返回两个指纹的汉明距离(取两种哈希的较大值,偏向灵敏)。
|
||||
@@ -63,12 +75,39 @@ 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 判断画面是否发生了足以触发提问的变化。
|
||||
func (f Fingerprint) Changed(o Fingerprint, threshold int) bool {
|
||||
// 任一信号超过阈值即判定为变化:感知哈希(抗噪粗筛)命中,或细节网格的
|
||||
// 归一化平均差超过 sensitivity(细粒度内容变化)。
|
||||
func (f Fingerprint) Changed(o Fingerprint, threshold int, sensitivity float64) bool {
|
||||
if threshold <= 0 {
|
||||
threshold = 5
|
||||
}
|
||||
return f.Distance(o) > threshold
|
||||
if sensitivity <= 0 {
|
||||
sensitivity = 0.01
|
||||
}
|
||||
if f.Distance(o) > threshold {
|
||||
return true
|
||||
}
|
||||
return f.DetailDiff(o) > sensitivity
|
||||
}
|
||||
|
||||
func bitsSet(v uint64) int {
|
||||
|
||||
@@ -24,7 +24,7 @@ func TestFingerprintIdentical(t *testing.T) {
|
||||
if d := Of(a).Distance(Of(b)); d != 0 {
|
||||
t.Fatalf("相同画面应无差异,实际距离 %d", d)
|
||||
}
|
||||
if Of(a).Changed(Of(b), 5) {
|
||||
if Of(a).Changed(Of(b), 5, 0.01) {
|
||||
t.Fatal("相同画面不应判定为变化")
|
||||
}
|
||||
}
|
||||
@@ -37,11 +37,27 @@ func TestFingerprintDetectsChange(t *testing.T) {
|
||||
changed.SetGray(x, y, color.Gray{Y: 250})
|
||||
}
|
||||
}
|
||||
if !Of(changed).Changed(Of(base), 5) {
|
||||
if !Of(changed).Changed(Of(base), 5, 0.01) {
|
||||
t.Fatal("右半边变白应被判定为变化")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFingerprintDetectsSubtleLocalChange(t *testing.T) {
|
||||
// 整块画面背景一致,只有一小块文字/内容变了:8×8 感知哈希会被平均掉,
|
||||
// 但高分辨率细节网格的归一化平均差能可靠捕捉到。这正是"题目变了却检测
|
||||
// 不出来"这类问题的修复目标。
|
||||
base := solid(256, 256, color.Gray{Y: 200})
|
||||
changed := solid(256, 256, color.Gray{Y: 200})
|
||||
for y := 40; y < 90; y++ {
|
||||
for x := 40; x < 90; x++ {
|
||||
changed.SetGray(x, y, color.Gray{Y: 20})
|
||||
}
|
||||
}
|
||||
if !Of(changed).Changed(Of(base), 5, 0.01) {
|
||||
t.Fatal("局部小区域变化应被细节网格判定为变化")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeJPEGResizesLongEdge(t *testing.T) {
|
||||
src := solid(2000, 400, color.Gray{Y: 128})
|
||||
raw, err := EncodeJPEG(src, 1280, 75)
|
||||
|
||||
Reference in new issue
Block a user