【ArchSummit】如何通过AIOps推动可量化的业务价值增长和效率提升?>>> 了解详情
写点什么

图片相似度计算:深入理解 DCT 变换以及感知哈希

  • 2018-11-21
  • 本文字数:3376 字

    阅读完需:约 11 分钟

图片相似度计算:深入理解DCT变换以及感知哈希

缘起

Android 上硬件编解码一直是个老大难问题,就解码来说,硬解码本身并不困难,只要按照 MediaCodec 的流程开发即可。但由于系统碎片化,硬件规格不一致,硬件解码会到黑屏,花屏,绿屏之类的显示问题。为了不招致用户投诉,我们在做视频解码的时候不太敢开启硬件加速,一般会采用保守策略,即以软解为主,优先保证画面正常,但牺牲了性能。


一般解决这个问题的方案是使用黑(白)机型名单下发配置(如:腾讯视频),或者暴露开关让用户手动去控制是否走硬件解码(如:bilibili)。


使用机型黑(白)名单有一定的作用,但其问题也显而易见:


  • 需要浪费大量的人力,精力进行机型测试,去维护,发布名单

  • 覆盖度偏低,一般只覆盖 TOP 机型

  • 缺乏时效性,例如新机型上市不能及时跟进

  • 不一定准确,因为硬解是否成功,跟视频源也有很大关系,同一个机型可能解这个视频不成功,另外一个视频又是成功的。按照机型"一刀切"的前提是要保证视频规格一致,这样才最健壮。


通过开关让用户切换体验太差,尤其是“抖音”之类的短视频 App,总不能每个界面加个开关让用户去切换解码器这么深奥的东西吧,从用户角度讲,我为什么要关心这个什么解码器怎么设置,只要视频能正常看就行。


仔细思考一下,我们其实可以实现自动化的检测,即模拟人工检测流程,把样本视频各软硬解一遍,然后对比他们的解码结果(图片)就能够知道硬解码是否能跑通。解码的流程轻车熟路,那么这里的关键问题就是我们如何进行图片对比?如何量化图片的差异度?

图片检索技术

图片对比其实跟"以图搜图"本质上是同一种技术,通常有几种做法 MSE,均值哈希,色彩直方图以及 OpenCV 里面一些提取图像特征的高级算法,如 Sift,Surf 等。基于移动端的运行效率,安装包大小,以及需求本身考虑,我们放弃引入 OpenCV。MSE 属于逐像素对比,对像素值要求过于严格,太简单粗暴,色彩直方图不太好量化差异度。基于以上考虑,我们选择图像哈希算法,它可以输出汉明距离,方便量化软硬解结果的差异度。


哈希算法有三种,平均哈希,感知哈希和差异度哈希,基于准确度考虑,我们选择实现较复杂一些的感知哈希算法。另基于性能考虑,我们在 Android 平台上使用 C++实现算法,通过 JNI 接口给 Java 调用。Java 层输入 Android 的 Bitmap 对象,输出为图片指纹,再通过对比指纹的汉明距离,我们即可判断出来解码是否正常。


Java 层接口签名如下:


public native long getHash(Bitmap bitmap, int algorithm);public native long getHammingDistance(long hash1, long hash2);
复制代码


下面分析一下感知哈希实现的方法和背后的原理,使之知其然,知其所以然。

图片的构成


图一



图二


我们知道图片由 RGB 三原色构成这称之为加色法,我们可以认为图片有三个维度(暂不考虑 Alpha)。分析上面两幅图,图一为原图,可以发现图片里蕴含的大部分信息都是低频的,比如天空,绿叶,树枝等,他们很少变化。高频信息是小鸟的眼睛,嘴巴等细节。图二是把图一经过缩放且只保留亮度信息,可以看到这有效的移除了图片的细节,即高频信息,展示了图片的低频部分。图片的低频部分决定了图片的大体结构,高频部分则完善了图片的细节。我们在对比图片是否相似的时候,其实更关注的是中低频部分的差异度。


在实践中,我们可以把图片从 RGB 转换为 YCbCr 格式,只提取 Y 的部分参与计算,实现降维,以减少运算量。再把图片缩放到 32*32 大小,丢弃掉大部分高频信息。由于进行了降维和缩放,后续步骤我们的运算量已大大减少。


把图片从空域转换到频域,我们需要使用 DCT(二维离散余弦变换)。DCT 也是 JPEG 以及 H264 压缩算法的核心部分,感兴趣的可以继续深入了解视频压缩算法的相关研究。

感知哈希与 DCT(离散余弦变换)

为了让大家深入了解背后的原理,这里打算展开介绍一下 DCT,以及它为什么能检测出来图片的相似程度。本文恐怕是网络上能找到讲解 DCT 最详细的一篇文章了,如果你对背后的原理不感兴趣,也可以直接跳过。



从空域到频域


DCT 由如下的公式定义,N 和 M 为矩阵的行数和列数


\begin{equation}F(u, v) = \left (\frac{2}{N} \right )^{\frac{1}{2}} \left (\frac{2}{M} \right )^{\frac{1}{2}} C(u)C(v) \sum_{i=0}^{N-1}\sum_{j=0}^{M-1} f(i,j)cos\left [\frac{\left(2i+1\right)u\pi}{2N} \right ]cos\left [\frac{\left(2j+1\right)v\pi}{2N} \right ]\end{equation}



其中:


  • u,v = 离散频率变量(0,1,2…7)

  • f(i,j) = 图像在 i 行 j 列的像素值

  • F(u,v) = DCT 结果


我们先研究一个最简单的场景,假设图片像素值如下:



当 N 和 M 都为 2 时,上述公式可简化为:


\begin{equation}F(u,v) = C(u)C(v) \sum_{i=0}^{1}\sum_{j=0}^{1} f(i,j)cos\left [\frac{\left(2i+1\right)u\pi}{4} \right] cos\left [\frac{\left(2j+1\right)v\pi}{4} \right]\end{equation}


下面我们来计算二维 DCT



即,结果是:



用 Python 的相关模块可以交叉验证我们的计算结果:


>>> import numpy as np>>> from scipy.fftpack import dct>>> d=np.matrix([[1,3],[2,0]])>>> dct(dct(d,axis=0,norm="ortho"),axis=1,norm="ortho")array([[ 3.,  0.],       [ 1., -2.]])
复制代码


在实践上,上述方式的计算效率不高,更加简便的计算方式是使用 DCT 矩阵:



若 N 取 2,得到 DCT 矩阵



若 N 取 8,得到的矩阵是这样的



其中 i=0 时,即第 0 行,称为 DC 分量,i=1-7 成为 AC 分量,用图表形式表示如下



i=1



i=2



i=3



i=4



i=5



i=6



i=7


有这样一个矩阵的话,我们再进行 DCT 变换就非常简单了:



其中:


  • T = DCT 矩阵

  • M = 图像数据

  • D = DCT 结果


这是对一张 8*8 图片应用 DCT 变换得到的矩阵结果:



这个矩阵最左上角是低频信息,右下角是高频信息。有个神奇的东西叫基准样式。



信不信由你,任意一张 8*8 的图,都可以由标准图中的的小图以一定比例叠加而合成,而每个小图的权重,由 DCT 变换得到的矩阵决定,是不是很有意思。DCT 变换后左上角一般是比较大的数字,而右下角一般是比较小的数字,这暗含了图片中低频信息占的比重较多,因此我们在做图片或者视频编码压缩的时候,正是通过量化舍弃右下角的高频信息,来实现信息的压缩。

图片差异度

我们在对比图片差异的时候,正是通过对比频域信息来实现的。在我们的实现中,首先把软硬件解码后的图片转成 YCbCr 格式,只提取其中的 Y,实现降维,再把图片缩放到 32*32 大小,进一步减少运算量,同时舍弃了一部分高频信息。再应用 32*32 的 DCT 把图片转换到频域,从频域矩阵中提取 8*8 中低频区域的系数,计算算数平均值。



矩阵中的每个值再与 D 比较,大于 D 计 1,小于 D 计 0,按位存储,我们即可得到一个图片指纹。通过计算两个图片指纹的差异,我们就可以得到可以量化图片差异度的数字。


当差异为 0 时,我们认为两张图片完全一样,差异越大,表明图片越不相似。对于解码出现绿屏,花屏的情况,我们可以有效的检测出来。


绿屏案例,相似度 24:



花屏案例,相似度 20:



检测 Demo 截图:



2018-11-21 17:003376

评论 1 条评论

发布
暂无评论
发现更多内容

绕过技术聊"跨端"......

hiisea

前端架构 前端框架 跨端开发 小程序框架 跨端框架

模块五(微博评论)

Geek_701557

语法"陷阱"

fy

c 语法陷阱

读配置、讲原理、看面试真题,我只能帮你到这了。。。

阿Q说代码

redis 主从架构 增量同步 全量同步 过期key处理

不要小看了积分商城,它的作用可以很大!

CRMEB

商城风格也可以很多变,DIY 了解一下!

CRMEB

《睡眠公式》:怎么治睡不好?

郭明

读书笔记

解析数仓lazyagg查询重写优化

华为云开发者联盟

数据库 后端

Linux网络编程_网卡操作指令与TCP协议介绍

DS小龙哥

6月月更 socket网络编程

说下你对方法区演变过程和内部结构的理解

阿Q说代码

Java JVM 方法区 元空间 永久代

面试官:你简历上说精通mysql,那你说下聚簇/联合/覆盖索引、回表、索引下推

Java全栈架构师

MySQL 数据库 程序员 面试 java架构

MyBatis 执行流程及源码解析

码农参上

mybatis java实战

词法陷阱(C)

fy

c C陷阱与缺陷

地理位置数据存储方案——Redis GEO

程序员架构进阶

redis 源码剖析 geo 6月日更 6月月更

Wireshark的HTTP请求包和响应包如何对应

程序员欣宸

Wireshark 6月月更

记一次给OpenHarmony提交代码的过程

坚果

OpenHarmony 6月月更

SDN系统方法 | 9. 接入网

俞凡

架构 网络 sdn SDN系统方法

牛客java选择题每日打卡Day2

京与旧铺

6月月更

Neat Syntax Design of an ETL Language (Part 2)

Bright

数据开发 ETL 大数据开发 EasySQL

Rxjs TakeUntil 操作符的学习笔记

Jerry Wang

typescript 响应式编程 angular RXJS 6月月更

16 种企业架构策略

涛哥 数字产品和业务架构

企业架构

DDD概念复杂难懂,实际落地如何设计代码实现模型?

Java全栈架构师

程序员 后端 领域驱动设计 DDD 架构师

Java代码优化请求合并与分而治之

码农参上

Java 大数据 高并发 java实战

学习React源码的五个层次

郭明

React

2022年首期Techo Day腾讯技术开放日将于6月28日线上举办

科技热闻

揭秘GaussDB(for Redis):全面对比Codis

华为云开发者联盟

数据库 后端 算力

编程技巧│提高 Javascript 代码效率的技巧

JavaScript 编程效率 6月月更 美化程序

Mysql 源码阅读(二)登录连接调试

MySQL 源码

Google I/O 2022: Android Jetpack 最新进展

fundroid

android Google android jetpack

vue项目的创建和托管【ForVue3】

Python研究所

6月月更

Object.defineProperty也能监听数组变化?

德育处主任

JavaScript Vue 原理分析 数据变更跟踪 6月月更

图片相似度计算:深入理解DCT变换以及感知哈希_移动_柳永峰_InfoQ精选文章