a329d4906b
- 方案文档: AVCC 体系建设、IPTV TCS 需求(0-req)/PRD(1-prd)/任务(2-task)/二三四期任务 - tcs-iptv: Go 后端(哈希SDK/MA码生成/可信数据空间mock/业务编排/HTTP API+HMAC鉴权) - web-console: React+AntD 监管大屏(角色工作台/全流程演示/监管片库) - 一剧一码+集级哈希, 集级下架/恢复, 全栈测试通过
107 lines
2.7 KiB
Go
107 lines
2.7 KiB
Go
package hash
|
|
|
|
import (
|
|
"fmt"
|
|
"image"
|
|
"image/color"
|
|
"math/bits"
|
|
)
|
|
|
|
// 感知哈希用于跨格式/转码识别同一内容(需求1-AC3)。
|
|
// MVP 实现 aHash(均值哈希)与 dHash(差值哈希),输入为已解码的视频代表帧图像。
|
|
// 真实视频抽帧由上层(ffmpeg)完成,本包专注哈希算法以便独立测试。
|
|
|
|
const phashDim = 8 // 8x8 → 64-bit 哈希
|
|
|
|
// grayResize 将图像缩放为 w×h 的灰度矩阵(最近邻,零依赖)。
|
|
func grayResize(img image.Image, w, h int) [][]float64 {
|
|
b := img.Bounds()
|
|
srcW, srcH := b.Dx(), b.Dy()
|
|
out := make([][]float64, h)
|
|
for y := 0; y < h; y++ {
|
|
out[y] = make([]float64, w)
|
|
for x := 0; x < w; x++ {
|
|
sx := b.Min.X + x*srcW/w
|
|
sy := b.Min.Y + y*srcH/h
|
|
r, g, bb, _ := img.At(sx, sy).RGBA()
|
|
// 转 8 位灰度(ITU-R 601 亮度)
|
|
gray := 0.299*float64(r>>8) + 0.587*float64(g>>8) + 0.114*float64(bb>>8)
|
|
out[y][x] = gray
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// AHash 计算均值哈希(64-bit,十六进制 16 字符)。
|
|
func AHash(img image.Image) string {
|
|
m := grayResize(img, phashDim, phashDim)
|
|
var sum float64
|
|
for y := 0; y < phashDim; y++ {
|
|
for x := 0; x < phashDim; x++ {
|
|
sum += m[y][x]
|
|
}
|
|
}
|
|
avg := sum / float64(phashDim*phashDim)
|
|
|
|
var hash uint64
|
|
var bit uint
|
|
for y := 0; y < phashDim; y++ {
|
|
for x := 0; x < phashDim; x++ {
|
|
if m[y][x] >= avg {
|
|
hash |= 1 << bit
|
|
}
|
|
bit++
|
|
}
|
|
}
|
|
return fmt.Sprintf("%016x", hash)
|
|
}
|
|
|
|
// DHash 计算差值哈希(64-bit)。对水平相邻像素比较亮度。
|
|
func DHash(img image.Image) string {
|
|
// 需要 (phashDim+1) 列以产生 phashDim 个差值
|
|
m := grayResize(img, phashDim+1, phashDim)
|
|
var hash uint64
|
|
var bit uint
|
|
for y := 0; y < phashDim; y++ {
|
|
for x := 0; x < phashDim; x++ {
|
|
if m[y][x] < m[y][x+1] {
|
|
hash |= 1 << bit
|
|
}
|
|
bit++
|
|
}
|
|
}
|
|
return fmt.Sprintf("%016x", hash)
|
|
}
|
|
|
|
// HammingDistance 计算两个等长十六进制哈希的汉明距离。
|
|
// 距离越小越相似;用于版权比对与跨版本识别。
|
|
func HammingDistance(a, b string) (int, error) {
|
|
if len(a) != len(b) {
|
|
return 0, fmt.Errorf("hash: length mismatch %d vs %d", len(a), len(b))
|
|
}
|
|
var va, vb uint64
|
|
if _, err := fmt.Sscanf(a, "%x", &va); err != nil {
|
|
return 0, err
|
|
}
|
|
if _, err := fmt.Sscanf(b, "%x", &vb); err != nil {
|
|
return 0, err
|
|
}
|
|
return bits.OnesCount64(va ^ vb), nil
|
|
}
|
|
|
|
// newGrayTestImage 是测试辅助:由灰度矩阵生成图像。
|
|
func newGrayTestImage(gray [][]uint8) image.Image {
|
|
h := len(gray)
|
|
w := 0
|
|
if h > 0 {
|
|
w = len(gray[0])
|
|
}
|
|
img := image.NewGray(image.Rect(0, 0, w, h))
|
|
for y := 0; y < h; y++ {
|
|
for x := 0; x < w; x++ {
|
|
img.SetGray(x, y, color.Gray{Y: gray[y][x]})
|
|
}
|
|
}
|
|
return img
|
|
}
|