搜索
查看
编辑修改
首页
UNITY
NODEJS
PYTHON
AI
GIT
PHP
GO
CEF3
JAVA
HTML
CSS
搜索
你好赵伟
这个屌丝很懒,什么也没留下!
关注作者
热门标签
jquery
HTML
CSS
PHP
ASP
PYTHON
GO
AI
C
C++
C#
PHOTOSHOP
UNITY
iOS
android
vue
xml
爬虫
SEO
LINUX
WINDOWS
JAVA
MFC
CEF3
CAD
NODEJS
GIT
Pyppeteer
article
热门文章
1
为“中国汽车价值”而战,中国星旗舰SUV星越L全球上市
2
Spring Security的Filter
3
开发无法复现是什么意思_【视频】洞穴潜水就是很有意思,你总是无法预知你会经历什么王远...
4
SpringBoot集成Redisson实现延迟队列_springboot 整合redisson 延时队列
5
基于JAVA+SpringBoot+Vue+uniApp小程序的心理健康测试平台
6
LineageOS的代码下载、编译及真机运行_lineageos国内源
7
stable-diffusion-webui指定GPU,device_stable diffusion webui 指定gpu
8
gRPC-第二代rpc服务_grpc底层协议
9
6种快速统计代码执行时间的方法,真香!(史上最全)_java 统计方法执行时间
10
C++求和函数之accumulate函数_c++ accumulate
当前位置:
article
> 正文
Java中的"感知哈希算法"——找出相似的图片_java感知哈希算法以图搜图
作者:你好赵伟 | 2024-07-09 17:47:03
赞
踩
java感知哈希算法以图搜图
[html]
view plain
copy
print
?
一、原理讲解
实现这种功能的关键技术叫做"感知哈希算法"(Perceptual Hash Algorithm), 意思是为图片生成一个指纹(字符串格式), 两张图片的指纹越相似, 说明两张图片就越相似. 但关键是如何根据图片计算出"指纹"呢? 下面用最简单的步骤来说明一下原理:
《1》、第一步 缩小图片尺寸
将图片缩小到8x8的尺寸, 总共64个像素. 这一步的作用是去除各种图片尺寸和图片比例的差异, 只保留结构、明暗等基本信息.
《2》、第二步 转为灰度图片
将缩小后的图片, 转为64级灰度图片.
《3》、第三步 计算灰度平均值
计算图片中所有像素的灰度平均值
《4》、第四步 比较像素的灰度
将每个像素的灰度与平均值进行比较, 如果大于或等于平均值记为1, 小于平均值记为0.
《5》、第五步 计算哈希值
将上一步的比较结果, 组合在一起, 就构成了一个64位的二进制整数, 这就是这张图片的指纹.
《6》、第六步 对比图片指纹
得到图片的指纹后, 就可以对比不同的图片的指纹, 计算出64位中有多少位是不一样的. 如果不相同的数据位数不超过5, 就说明两张图片很相似, 如果大于10, 说明它们是两张不同的图片.
二、源码
[java]
view plain
copy
print
?
[java]
view plain
copy
print
?
import
java.awt.AlphaComposite;
import
java.awt.Color;
import
java.awt.Font;
import
java.awt.Graphics2D;
import
java.awt.Image;
import
java.awt.RenderingHints;
import
java.awt.geom.AffineTransform;
import
java.awt.image.BufferedImage;
import
java.awt.image.ColorModel;
import
java.awt.image.WritableRaster;
import
java.io.File;
import
java.io.FileInputStream;
import
java.io.FileNotFoundException;
import
java.io.FileOutputStream;
import
java.io.IOException;
import
java.io.InputStream;
import
javax.imageio.ImageIO;
import
com.sun.image.codec.jpeg.ImageFormatException;
import
com.sun.image.codec.jpeg.JPEGCodec;
import
com.sun.image.codec.jpeg.JPEGImageDecoder;
import
com.sun.image.codec.jpeg.JPEGImageEncoder;
public
class
ImageHelper {
// 项目根目录路径
public
static
final
String path = System.getProperty(
"user.dir"
);
/**
* 生成缩略图 <br/>
* 保存:ImageIO.write(BufferedImage, imgType[jpg/png/...], File);
*
* @param source
* 原图片
* @param width
* 缩略图宽
* @param height
* 缩略图高
* @param b
* 是否等比缩放
* */
public
static
BufferedImage thumb(BufferedImage source,
int
width,
int
height,
boolean
b) {
// targetW,targetH分别表示目标长和宽
int
type = source.getType();
BufferedImage target =
null
;
double
sx = (
double
) width / source.getWidth();
double
sy = (
double
) height / source.getHeight();
if
(b) {
if
(sx > sy) {
sx = sy;
width = (
int
) (sx * source.getWidth());
}
else
{
sy = sx;
height = (
int
) (sy * source.getHeight());
}
}
if
(type == BufferedImage.TYPE_CUSTOM) {
// handmade
ColorModel cm = source.getColorModel();
WritableRaster raster = cm.createCompatibleWritableRaster(width,
height);
boolean
alphaPremultiplied = cm.isAlphaPremultiplied();
target =
new
BufferedImage(cm, raster, alphaPremultiplied,
null
);
}
else
target =
new
BufferedImage(width, height, type);
Graphics2D g = target.createGraphics();
// smoother than exlax:
g.setRenderingHint(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
g.drawRenderedImage(source, AffineTransform.getScaleInstance(sx, sy));
g.dispose();
return
target;
}
/**
* 图片水印
*
* @param imgPath
* 待处理图片
* @param markPath
* 水印图片
* @param x
* 水印位于图片左上角的 x 坐标值
* @param y
* 水印位于图片左上角的 y 坐标值
* @param alpha
* 水印透明度 0.1f ~ 1.0f
* */
public
static
void
waterMark(String imgPath, String markPath,
int
x,
int
y,
float
alpha) {
try
{
// 加载待处理图片文件
Image img = ImageIO.read(
new
File(imgPath));
BufferedImage image =
new
BufferedImage(img.getWidth(
null
),
img.getHeight(
null
), BufferedImage.TYPE_INT_RGB);
Graphics2D g = image.createGraphics();
g.drawImage(img,
0
,
0
,
null
);
// 加载水印图片文件
Image src_biao = ImageIO.read(
new
File(markPath));
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP,
alpha));
g.drawImage(src_biao, x, y,
null
);
g.dispose();
// 保存处理后的文件
FileOutputStream out =
new
FileOutputStream(imgPath);
JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
encoder.encode(image);
out.close();
}
catch
(Exception e) {
e.printStackTrace();
}
}
/**
* 文字水印
*
* @param imgPath
* 待处理图片
* @param text
* 水印文字
* @param font
* 水印字体信息
* @param color
* 水印字体颜色
* @param x
* 水印位于图片左上角的 x 坐标值
* @param y
* 水印位于图片左上角的 y 坐标值
* @param alpha
* 水印透明度 0.1f ~ 1.0f
*/
public
static
void
textMark(String imgPath, String text, Font font,
Color color,
int
x,
int
y,
float
alpha) {
try
{
Font Dfont = (font ==
null
) ?
new
Font(
"宋体"
,
20
,
13
) : font;
Image img = ImageIO.read(
new
File(imgPath));
BufferedImage image =
new
BufferedImage(img.getWidth(
null
),
img.getHeight(
null
), BufferedImage.TYPE_INT_RGB);
Graphics2D g = image.createGraphics();
g.drawImage(img,
0
,
0
,
null
);
g.setColor(color);
g.setFont(Dfont);
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP,
alpha));
g.drawString(text, x, y);
g.dispose();
FileOutputStream out =
new
FileOutputStream(imgPath);
JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
encoder.encode(image);
out.close();
}
catch
(Exception e) {
System.out.println(e);
}
}
/**
* 读取JPEG图片
* @param filename 文件名
* @return BufferedImage 图片对象
*/
public
static
BufferedImage readJPEGImage(String filename)
{
try
{
InputStream imageIn =
new
FileInputStream(
new
File(filename));
// 得到输入的编码器,将文件流进行jpg格式编码
JPEGImageDecoder decoder = JPEGCodec.createJPEGDecoder(imageIn);
// 得到编码后的图片对象
BufferedImage sourceImage = decoder.decodeAsBufferedImage();
return
sourceImage;
}
catch
(FileNotFoundException e) {
e.printStackTrace();
}
catch
(ImageFormatException e) {
e.printStackTrace();
}
catch
(IOException e) {
e.printStackTrace();
}
return
null
;
}
/**
* 读取JPEG图片
* @param filename 文件名
* @return BufferedImage 图片对象
*/
public
static
BufferedImage readPNGImage(String filename)
{
try
{
File inputFile =
new
File(filename);
BufferedImage sourceImage = ImageIO.read(inputFile);
return
sourceImage;
}
catch
(FileNotFoundException e) {
e.printStackTrace();
}
catch
(ImageFormatException e) {
e.printStackTrace();
}
catch
(IOException e) {
e.printStackTrace();
}
return
null
;
}
/**
* 灰度值计算
* @param pixels 像素
* @return int 灰度值
*/
public
static
int
rgbToGray(
int
pixels) {
// int _alpha = (pixels >> 24) & 0xFF;
int
_red = (pixels >>
16
) &
0xFF
;
int
_green = (pixels >>
8
) &
0xFF
;
int
_blue = (pixels) &
0xFF
;
return
(
int
) (
0.3
* _red +
0.59
* _green +
0.11
* _blue);
}
/**
* 计算数组的平均值
* @param pixels 数组
* @return int 平均值
*/
public
static
int
average(
int
[] pixels) {
float
m =
0
;
for
(
int
i =
0
; i < pixels.length; ++i) {
m += pixels[i];
}
m = m / pixels.length;
return
(
int
) m;
}
}
[java]
view plain
copy
print
?
import
java.awt.image.BufferedImage;
import
java.util.ArrayList;
import
java.util.List;
public
class
SimilarImageSearch {
/**
* @param args
*/
public
static
void
main(String[] args) {
List<String> hashCodes =
new
ArrayList<String>();
String filename = ImageHelper.path +
"\\images\\"
;
String hashCode =
null
;
for
(
int
i =
0
; i <
7
; i++)
{
hashCode = produceFingerPrint(filename +
"example"
+ (i +
1
) +
".jpg"
);
hashCodes.add(hashCode);
}
System.out.println(
"Resources: "
);
System.out.println(hashCodes);
System.out.println();
String sourceHashCode = produceFingerPrint(filename +
"source.jpg"
);
System.out.println(
"Source: "
);
System.out.println(sourceHashCode);
System.out.println();
for
(
int
i =
0
; i < hashCodes.size(); i++)
{
int
difference = hammingDistance(sourceHashCode, hashCodes.get(i));
if
(difference==
0
){
System.out.println(
"source.jpg图片跟example"
+(i+
1
)+
".jpg一样"
);
}
else
if
(difference<=
5
){
System.out.println(
"source.jpg图片跟example"
+(i+
1
)+
".jpg非常相似"
);
}
else
if
(difference<=
10
){
System.out.println(
"source.jpg图片跟example"
+(i+
1
)+
".jpg有点相似"
);
}
else
if
(difference>
10
){
System.out.println(
"source.jpg图片跟example"
+(i+
1
)+
".jpg完全不一样"
);
}
System.out.println(difference);
}
}
/**
* 计算"汉明距离"(Hamming distance)。
* 如果不相同的数据位不超过5,就说明两张图片很相似;如果大于10,就说明这是两张不同的图片。
* @param sourceHashCode 源hashCode
* @param hashCode 与之比较的hashCode
*/
public
static
int
hammingDistance(String sourceHashCode, String hashCode) {
int
difference =
0
;
int
len = sourceHashCode.length();
for
(
int
i =
0
; i < len; i++) {
if
(sourceHashCode.charAt(i) != hashCode.charAt(i)) {
difference ++;
}
}
return
difference;
}
/**
* 生成图片指纹
* @param filename 文件名
* @return 图片指纹
*/
public
static
String produceFingerPrint(String filename) {
BufferedImage source = ImageHelper.readPNGImage(filename);
// 读取文件
int
width =
8
;
int
height =
8
;
// 第一步,缩小尺寸。
// 将图片缩小到8x8的尺寸,总共64个像素。这一步的作用是去除图片的细节,只保留结构、明暗等基本信息,摒弃不同尺寸、比例带来的图片差异。
BufferedImage thumb = ImageHelper.thumb(source, width, height,
false
);
// 第二步,简化色彩。
// 将缩小后的图片,转为64级灰度。也就是说,所有像素点总共只有64种颜色。
int
[] pixels =
new
int
[width * height];
for
(
int
i =
0
; i < width; i++) {
for
(
int
j =
0
; j < height; j++) {
pixels[i * height + j] = ImageHelper.rgbToGray(thumb.getRGB(i, j));
}
}
// 第三步,计算平均值。
// 计算所有64个像素的灰度平均值。
int
avgPixel = ImageHelper.average(pixels);
// 第四步,比较像素的灰度。
// 将每个像素的灰度,与平均值进行比较。大于或等于平均值,记为1;小于平均值,记为0。
int
[] comps =
new
int
[width * height];
for
(
int
i =
0
; i < comps.length; i++) {
if
(pixels[i] >= avgPixel) {
comps[i] =
1
;
}
else
{
comps[i] =
0
;
}
}
// 第五步,计算哈希值。
// 将上一步的比较结果,组合在一起,就构成了一个64位的整数,这就是这张图片的指纹。组合的次序并不重要,只要保证所有图片都采用同样次序就行了。
StringBuffer hashCode =
new
StringBuffer();
for
(
int
i =
0
; i < comps.length; i+=
4
) {
int
result = comps[i] * (
int
) Math.pow(
2
,
3
) + comps[i +
1
] * (
int
) Math.pow(
2
,
2
) + comps[i +
2
] * (
int
) Math.pow(
2
,
1
) + comps[i +
3
];
hashCode.append(binaryToHex(result));
}
// 得到指纹以后,就可以对比不同的图片,看看64位中有多少位是不一样的。
return
hashCode.toString();
}
/**
* 二进制转为十六进制
* @param int binary
* @return char hex
*/
private
static
char
binaryToHex(
int
binary) {
char
ch =
' '
;
switch
(binary)
{
case
0
:
ch =
'0'
;
break
;
case
1
:
ch =
'1'
;
break
;
case
2
:
ch =
'2'
;
break
;
case
3
:
ch =
'3'
;
break
;
case
4
:
ch =
'4'
;
break
;
case
5
:
ch =
'5'
;
break
;
case
6
:
ch =
'6'
;
break
;
case
7
:
ch =
'7'
;
break
;
case
8
:
ch =
'8'
;
break
;
case
9
:
ch =
'9'
;
break
;
case
10
:
ch =
'a'
;
break
;
case
11
:
ch =
'b'
;
break
;
case
12
:
ch =
'c'
;
break
;
case
13
:
ch =
'd'
;
break
;
case
14
:
ch =
'e'
;
break
;
case
15
:
ch =
'f'
;
break
;
default
:
ch =
' '
;
}
return
ch;
}
}
声明:
本文内容由网友自发贡献,转载请注明出处:
【wpsshop博客】
推荐阅读
article
java
-
php
-
python
-
宠物
救助网站的设计与实现计算机
毕业设计
_
php
宠物
救助网站测试过程...
springboot基于springboot的学生社团管理系统的研究设计。
java
-
php
-
python
-
宠物
救助网站的...
赞
踩
article
基于微信
小
程序
电影推荐
系统
视频
播放器
系统
python+
java
+node.js+php...
电影
播放器
小
程序
能够通过互联网得到广泛的、全面的宣传,让尽可能多的用户了解和熟知电影
播放器
小
程序
的便捷高效,不仅为群众提...
赞
踩
article
springboot/
java
/php/node/python
微信
小
程序
的
电影
推荐
系统
【计算机毕设】...
电影
推荐
系统
的实现对于用户而言,意味着更加个性化和智能化的观影体验。通过对用户历史观影数据的分析,结合用户评分、社交网络...
赞
踩
article
最小
矩阵
宽度【华为OD机试】(
JAVA
&Pytho
n
&C++&JS题解)_给定
一
个
矩阵
,
包含
n
*m
个
...
最小
矩阵
宽度【华为OD机试】(
JAVA
&Pytho
n
&C++&JS题解)给定
一
个
矩阵
,
包含
N*M
个
整数
,和
一
个
包含
K
个
整...
赞
踩
article
考勤
系统
设计与
实现
(JSP+
java
+springmvc+
mysql
+MyBatis)_
考勤
系统
的设...
随着企业规模的扩大和员工数量的增加,传统的手工记录
考勤
方式已经无法满足现代企业的需求。
系统
将采用现代化的技术手段,包括数...
赞
踩
article
2023计算机
毕业设计
-
电影
推荐
评分榜单查看
系统
springboot
-
JAVA
-
JAVA
(论文+开题...
网络
的
广泛应用给生活带来了十分
的
便利。所以把影片
推荐
管理与现在网络相结合,利用java技术建设影片
推荐
系统
,实现影片
推荐
...
赞
踩
article
Java
+
Swing
+
mysql
学生考勤
管理系统
(高分课程项目)_
java
mysql
swing...
该系统实现系统管理员:登陆、添加教务人员教务管理员:登陆、、添加学生信息、添加教师信息、查看考勤数据辅导员:登陆、、查看...
赞
踩
article
java
-jsp
基于
协同
过滤的
个性化
电影
推荐
系统
ud975
[独有源码]如何找到适合自己的毕业设计的指...
选题背景:随着互联网的快速发展和数字娱乐产业的兴起,人们对于
电影
的需求也越来越多样化。然而,在海量的
电影
资源面前,用户往...
赞
踩
article
微信
小
程序
java
ssm
电影
迷
爱好者
交流平台
_基于
微信
小
程序
的
电影
爱好者
交流平台
...
本设计分为用户和管理员两个角色,其中用户可以登陆
微信
端,查看
电影
信息,查看
电影
分类,对
电影
在线评论,留言反馈,修改个人资...
赞
踩
article
华为OD机试C卷--
字符串
比较
(
Java
& JS &
Python
)_(
c
卷
,
200分)- 字符...
给定
字符串
A、B和正整数V,A的长度与B的长度相等, 请计算A中满足如下条件的最大连续子串的长度: - 该连续子串在A和...
赞
踩
article
Java
线程
、
进程
_
java
进程
和
线程
...
1、什么是
进程
:process
进程
时操作系统中运行的一个任务(一个应用程序运行在一个
进程
中)。
进程
时一块包含了某些资源的...
赞
踩
article
【
JAVA
】
线程
和
进程
_
java
线程
和
进程
...
多
线程
是提升程序性能非常重要的一种方式,也是Java 编程中的一项重要技术。在程序设计中,多
线程
就是指一个应用程序中有多...
赞
踩
article
华为
OD机试C卷-- 最长子
字符串
的
长度
(二)(
Java
& JS &
Python
& C)_
华为
...
给你一个
字符串
s,
字符串
s 首尾相连成一个环形,请你在环中找出 'l'、'o'、'x' 字符都恰好出现了偶数次最长子...
赞
踩
article
华为
OD机试-
求
满足条件
的
最长
子串
的
长度
(Java/Python/C++)_只包含
字母
和数字,按要
求
...
华为
OD机试-
求
满足条件
的
最长
子串
的
长度
-
华为
OD机试-
求
满足条件
的
最长
子串
的
长度
:给定一个,只包含
字母
和数字,按要
求
找...
赞
踩
article
华为
O
D
机试统一考试
D
卷
C
卷
- 最长子
字符串
的长度(二)(C++
Java
Java
Script ...
华为
O
D
机试统一考试
D
卷
C
卷
- 最长子
字符串
的长度(二)(C++
Java
Java
Script
Python
) ...
赞
踩
article
【2023华为
od
-C卷-第三题-最长
字符串
的长度(二)】
100%
通过率
(
Java
Script&Ja...
意义上的加法就是异或操作,可以把(cntL[i]\ (m
od
\ 2), cntO[i]\ (m
od
\ 2), cntX[...
赞
踩
article
Hive
安装与
配置
实战指南_
hive
java
环境变量
...
通过按照本文的指导进行操作,您应该能够顺利地搭建起自己的
Hive
环境,并开始使用
Hive
进行数据分析和处理。当然,Hiv...
赞
踩
article
JAVA
命令
行
运行
java
项目
_
java
运行
命令
...
多年以来 一直使用的是IDE 来写
java
项目
,导致很多的最基础的东西都渐渐模糊了.最近遇到一个问题就是如果
命令
行来运...
赞
踩
article
【
小沐学
Java
】
VSCode
搭建
Java
开发
环境_
vscode
java
...
Visual Studio Code 是一个轻量级但功能强大的源代码编辑器,可在桌面上运行,适用于 Windows、ma...
赞
踩
article
redis
java
hash
存储
对象
_
java
redis
存储
对象
使用
hash
并查询...
前言:
redis
缓存的
hash
数据类型可以让用户将多个key-value对存储到一个
redis
键里,适合用来存储
对象
。本...
赞
踩
相关标签
java
php
宠物
微信小程序
音视频
小程序
spring boot
矩阵
华为od
python
c++
mysql
mybatis
开发语言
课程设计
c语言
javascript