phash是一种感知哈希,专为图像相似性比对设计,通过灰度化、缩放、dct变换、低频系数提取和中位数二值化生成64位指纹,对缩放、旋转、水印等失真鲁棒;而普通哈希如md5对微小改动极度敏感,且不具感知不变性。

什么是 pHash,它和普通哈希有什么区别
普通哈希(比如 std::hash 或 MD5)对输入极其敏感:图像像素哪怕改一个字节,输出就完全不一样。而 pHash(Perceptual Hash)是为“人眼看起来相似”设计的——缩放、轻微旋转、加水印、调亮度后,只要内容没变,pHash 值仍高度接近。
核心思路是:把图像转成灰度 → 缩放到 32×32(降噪+统一尺寸)→ DCT 变换 → 取左上 8×8 低频系数 → 计算中位数阈值 → 二值化生成 64 位指纹。
注意:pHash 不是加密哈希,不防篡改,只用于相似性比对;它依赖浮点 DCT 计算,不同平台/库若 DCT 实现有微小差异,结果可能差 1–2 位。
用 OpenCV + FFTW 快速实现 pHash 的关键步骤
OpenCV 自带 DCT,但默认用的是 cv::dct,它要求输入为 CV_32F 类型且尺寸是 2 的幂(32×32 满足)。FFTW 更快但需额外链接;多数场景用 OpenCV 就够了。
- 读图必须用
cv::IMREAD_GRAYSCALE,避免彩色通道干扰 - 缩放必须用
cv::INTER_AREA(下采样专用),不是INTER_LINEAR -
cv::dct输入必须是单通道CV_32F,记得.convertScaleAbs()后要再转回 float - DCT 结果取
coeffs(cv::Rect(0, 0, 8, 8)),不是前 64 个元素(DCT 系数是二维排布)
示例片段:
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
cv::Mat img = cv::imread("test.jpg", cv::IMREAD_GRAYSCALE);
cv::resize(img, img, cv::Size(32, 32), 0, 0, cv::INTER_AREA);
img.convertScaleAbs(img); // 归一化到 [0,255]
img.convertScaleAbs(img, 1.0/255.0); // 转为 [0.0, 1.0] 浮点
cv::Mat float_img;
img.convertTo(float_img, CV_32F);
cv::Mat dct_coeffs;
cv::dct(float_img, dct_coeffs);
cv::Mat top_left = dct_coeffs(cv::Rect(0, 0, 8, 8));
double median_val;
cv::medianBlur(top_left, top_left, 1); // 避免 medianOfEmpty 掉坑
cv::minMaxLoc(top_left, nullptr, nullptr, nullptr, &median_val);
uint64_t hash = 0;
for (int i = 0; i (i,j) > median_val) hash |= (1ULL
<h3>
<code>pHash</code> 比较时为什么不能直接用 ==,而要用汉明距离</h3>
<p><code>pHash</code> 是 64 位整数,但两个相似图像的哈希值通常不完全相等——光照变化、JPEG 压缩失真、缩放插值误差都会导致个别位翻转。硬比较 <code>hash1 == hash2</code> 几乎总为 false。</p>
<p>正确做法是计算汉明距离(异或后统计 1 的个数):<code>__builtin_popcountll(hash1 ^ hash2)</code>(GCC/Clang),MSVC 用 <code>__popcnt64</code>。</p>
- 距离 ≤ 5:基本可认为是同一图像(含常见失真)
- 距离 ≥ 15:大概率内容不同
- 注意:
__builtin_popcountll对 0 输入返回 0,无需特判 - 如果用
std::bitset,性能差一个数量级,别这么干
容易被忽略的兼容性与精度陷阱
OpenCV 的 cv::dct 在不同版本行为不一致:3.4.x 默认做归一化,4.x 默认不归一化。如果你从旧项目迁移,pHash 值会整体偏移。
- 显式传
cv::DCT_INVERSE或cv::DCT_ROWS无意义,cv::dct只支持正向二维 DCT - 输入尺寸非 2 的幂(如 31×31)会导致
cv::dct报错或静默截断,务必检查img.size() - 灰度图读取失败时
img.empty()为 true,后续 resize 会崩溃,必须先判断 - Windows 上若链接了多个 OpenCV 版本(如 conda 和手动编译混用),
cv::dct可能返回 NaN —— 用cv::checkRange提前验证
实际部署时,建议把 pHash 计算封装成独立函数,并在开头加 CV_Assert(!img.empty() && img.cols==32 && img.rows==32 && img.depth()==CV_32F),省得线上突然崩在奇怪的地方。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










