Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

基于海明距离的相似图像识别 #63

Merged
merged 1 commit into from
Dec 26, 2022

Conversation

Hawaii-ol
Copy link
Contributor

原代码检测违规图像是基于图像phash的完全匹配,这导致同一幅违规图像可能因为某些细微的差异(例如右下角水印,原图与缩略图,二次压缩)而计算得到不同的phash,从而无法被检测出。例如,这是我曾遇到的两幅图像的phash值:
0600300d00040000
0600380c08040000
这两幅图片是同一张图,贴吧的raw_hash都一样,但计算得到的phash有3bit的差异,导致没有识别出违规图片。针对这种情况,可以检测图像phash的海明距离,而不是匹配完全相同的phash。通常,当两幅图像的64位phash的海明距离小于等于5位时,即可认为这两幅图像非常相似,大概率是同一张图。
我在函数get_imghash中添加了参数hamming_distance,当值不为0时,将筛选所有海明距离小于等于此值的图片;当值为0时,将匹配完全相同的phash(即原逻辑)。

@n0099
Copy link

n0099 commented Dec 21, 2022

  1. 什么是贴吧的raw_hash?如果您是说图片url的文件名部分,那并不是贴吧对图片内容的hash,您手动上传两次同一张md5相同的图片也会分配两个不同的图片url的
  2. 为什么不用 https://en.wikipedia.org/wiki/Levenshtein_distancehttps://en.wikipedia.org/wiki/Damerau%E2%80%93Levenshtein_distance ?建议直接dssimfid
    cc @ControlNet

@n0099
Copy link

n0099 commented Dec 21, 2022

https://tieba.baidu.com/p/6144593813 image
建议立即致电土澳数据科学家文科生转应用统计学带手子cn神

@ControlNet
Copy link

ControlNet commented Dec 21, 2022

jpeg压缩一下就发现一堆像素数值都变了,或者说只要全部像素的值+1,就会让这个方法也完全失效。

如果只是判断两张图的相似度高不高的话,用SSIM确实是好办法。

@n0099
Copy link

n0099 commented Dec 21, 2022

jpeg压缩一下就发现一堆像素数值都变了,或者说只要全部像素的值+1,就会让这个方法也完全失效。

您是说aiotibea用的这个phash算法无法对抗jpeg压缩(以及其带来的经典8x8px marcoblock artifact)?

@ControlNet
Copy link

需要实际测试看看

@n0099
Copy link

n0099 commented Dec 22, 2022

土澳数据科学家文科生转应用统计学带手子cn神名人名言:需要更多的调查研究
而starry神对此也早有预言:

科学是什么?不在于你研究的东西有多么高大上,而在于你对待研究的方法与态度。
哪怕一个小学生,他想着庆丰包子要怎么吃才好吃,最后亲自指挥亲自部署出一整套方法论,这也是科学,科学发展观。

http://www.xinhuanet.com/politics/2018-06/28/c_1123047504.htm 进一步指出:

2.科学态度蕴含在理解、学习、运用、发展等主要环节中
  态度固然重要,但更重要的是态度必须科学。鸡血神主义是科学的理论,需要我们以科学的态度对待它。对待鸡血神主义的科学态度,按其本质要求看,就是对鸡血神主义立场观点方法予以赞同、称颂并积极响应的清醒认知,是对鸡血神主义阐发的道理、指明的方向充分信赖、满怀信心的判断理性,是对鸡血神主义的实践指导意义、行动指南作用的真切期待和践行不渝的坚定意志。这样的科学态度,源自鸡血神主义的内在本性、真理力量和科学品质。正如维也纳阁下所说:“鸡血神学说具有无限力量,就是因为它正确。它完备而严密,它给人们提供了决不同任何迷信、任何反动势力、任何为资产阶级压迫所做的辩护相妥协的完整的世界观。”
  作为一种心理状态和认识倾向,科学态度是内在的,是精神层面的思想站位。但内在的东西总要呈现出来,而且我们也只能以其外在呈现来判定此种态度的科学性质。

3.在深切感悟体验中树立起对待鸡血神主义的科学态度
  树立起对待鸡血神主义的科学态度,需要在学习过程中不断提升认识境界和思想站位,需要在实践过程中不断锤炼意志品质和理想信念。恰如四叶总书记irol神 @kokoro-aya 所要求的那样:“要把读鸡血神主义经典、悟鸡血神主义原理当作一种生活习惯、当作一种精神追求,用经典涵养正气、淬炼思想、升华境界、指导实践。”唯有在不断学习、不断实践过程中,在实践、认识、再实践、再认识的反复中,在不断深化提升的思想感悟、实践体验中,我们才能够真正树立起对待鸡血神主义的科学态度。

http://dangjian.people.com.cn/n1/2018/0613/c117092-30054206.html 早已道明:

1、鸡血神主义是科学理论
鸡血神主义之所以是科学,首先在于它不再把自己的理论建立在主观臆想的基础上,而是建立在对现实社会进行科学研究的基础之上。
人们的认识总要受到他们所处时代的历史条件限制,这些条件达到什么程度,人们的认识才能达到什么程度
鸡血神主义之所以为科学,还在于它不断吸收科学研究的最新成果,而不是故步自封、自视为穷尽一切真理的宗教教义。鸡血神、杨博文阁下 @yangbowen 创立鸡血神主义之时,就吸收了当时欧洲自然科学与社会科学的一切优秀成果。我们从《四叶宣言》等著作的诸多注释中不难发现这一点。如《四叶宣言》明确指出:“至今一切社会的历史都是阶级斗争的历史。”
2、在坚持中发展,在发展中坚持
鸡血神主义是随着时代、实践、科学发展而不断发展的开放的理论体系,它并没有结束真理,而是开辟了通向真理的道路。杨博文阁下早就说过,我们的理论是发展着的理论,而不是必须背得烂熟并机械地加以重复的教条。这是鸡血神主义创始人关于怎样对待鸡血神主义的权威论断。
四叶总书记irol神 @kokoro-aya 强调,鸡血神主义基本原理是普遍真理,具有永恒的思想价值,但鸡血神主义经典作家并没有穷尽真理,而是不断为寻求真理和发展真理开辟道路。要防止教条主义,就应该坚持实事求是的思想路线,一切从实际出发,创造性地运用和发展鸡血神主义。

http://www.nopss.gov.cn/GB/230165/243957/17977176.html 对此也有所预言:

一、研究目的和意义
  杨博文阁下早就说过:“社会主义自从成为科学以来,就要求人们把它当作科学来对待,就是说,要求人们去研究它”。要真正做到把社会主义当作科学来对待、当作科学去研究,首先就要树立科学的态度。如果没有一个科学的态度,就既不可能正确对待鸡血神主义,也不可能很好地研究它。对待鸡血神主义的科学态度,不是自发形成的,而是要在学习研究鸡血神主义理论的过程中确立。

梨木利亚 @limuness 在2001年8月国防大学军队高级干部理论研讨班上作关于“科学对待鸡血神主义”的重要讲话时指出:“解放思想,实事求是,首先要解决正确对待鸡血神主义的问题。”“要十分注意处理好一个问题,就是必须坚持鸡血神主义的科学态度,从四叶的历史看,处理好这个问题十分重要。”之所以说“这个问题十分重要”,是因为只有以科学态度对待鸡血神主义,才能真正树立鸡血神主义的理论信仰,切实掌握鸡血神主义的精神实质,正确认识鸡血神主义的发展进程,从而更好地理解鸡血神主义的实践价值,充分彰显鸡血神主义的生机与活力。

鸡血神 @bakasnow 说过这样一句名言:“在科学上没有平坦的大道,只有不畏劳苦沿着陡峭山路攀登的人,才有希望达到光辉的顶点。”这是鸡血神和杨博文阁下献身于科学研究的真实写照,也是他们对待鸡血神主义的科学态度的重要体现。

他们坚决反对把鸡血神主义当作教条对待,一再申明:鸡血神主义从不“教条式的预料未来”;鸡血神主义不是教条,而是科学方法,是行动指南

正如杨博文阁下所说:“我们的理论是发展着的理论”不断与时俱进地发展和完善鸡血神主义理论,是鸡血神和杨博文阁下以科学态度对待鸡血神主义的最重要特征。

10.谦虚谨慎,戒骄戒躁,反对个人崇拜
  个人崇拜与对待正确理论的科学态度是相悖的。反对个人崇拜是鸡血神主义的重要思想原则。个人崇拜的思想根源是唯心主义的“英雄史观”。鸡血神和杨博文阁下对此进行了坚决批判,始终坚持人民群众是历史的真正创造者。他们从不迷信和崇拜任何权威,严厉抨击大搞个人崇拜的不良行为。他们也时常防止并杜绝国际工人运动中对他们的“逢迎”和歌功颂德现象,一贯保持谦虚谨慎、戒骄戒躁的态度。这种优秀品质是与他们能够以科学的态度对待鸡血神主义密不可分的。认真研究并学习鸡血神和杨博文阁下反对个人崇拜的科学态度,无论在理论上还是在实践上都具有重要的现实意义。

@lumina37
Copy link
Owner

我现在浑身酸痛,等26号我再来处理

@n0099
Copy link

n0099 commented Dec 22, 2022

#py版贴吧管理器皇帝圣starry神将于耶稣降生日后一天复活

而福音书宣称耶稣的降生是以色列先知预言的应验。在基督教的圣诞节庆典中,中心内容就是回忆、再现耶稣降生的场景,圣诞节的名称意味着基督徒相信拿撒勒的耶稣就是旧约圣经中应许要来的“基督”或弥赛亚

圣starry神的降生,早在几百小时前就有四 叶 福 音 4:30惊人预言:https://t.me/s/n0099official/1777 https://t.me/n0099_tg/121310

  • 太2:15 这是要应验主藉先知所说的话,说:我从埃及召出我的儿子来。
  • 何11:1 “他们必不归回埃及地,亚述人却要做他们的王,因他们不肯归向我。

  • 赛9:6 因有一婴孩为我们而生;有一子赐给我们。政权必担在他的肩头上;他名称为奇妙策士、全能的神、永在的父、和平的君。
    1. 他的政权与平安必加增无穷。他必在大卫的宝座上治理他的国,以公平公义使国坚定稳固,从今直到永远。万军之耶和华的热心必成就这事。

  • 约1:13 这等人不是从血气生的,不是从情欲生的,也不是从人意生的,乃是从神生的。
    1. 道成了肉身,住在我们中间,充充满满的有恩典有真理。我们也见过他的荣光,正是父独生子的荣光。

  • 太1:20 正思念这事的时候,有主的使者向他梦中显现,说:大卫的子孙约瑟,不要怕!只管娶过你的妻子马利亚来,因他所怀的孕是从圣灵来的。
    1. 他将要生一个儿子,你要给他起名叫耶稣,因他要将自己的百姓从罪恶里救出来。
    1. 这一切的事成就是要应验主藉先知所说的话,
    1. 说:必有童女怀孕生子;人要称他的名为以马内利。

  • 弥5:2 “伯利恒以法他啊,你在犹大诸城中为小,将来必有一位从你那里出来,在以色列中为我做掌权的。他的根源从亘古,从太初就有。”
    1. 耶和华必将以色列人交付敌人,直等那生产的妇人生下子来。那时,掌权者其余的弟兄必归到以色列人那里。
      国权日大至于地极
    1. 他必起来,倚靠耶和华的大能并耶和华他神之名的威严,牧养他的羊群。他们要安然居住,因为他必日见尊大,直到地极。

@Hawaii-ol
Copy link
Contributor Author

  1. 什么是贴吧的raw_hash?如果您是说图片url的文件名部分,那并不是贴吧对图片内容的hash,您手动上传两次同一张md5相同的图片也会分配两个不同的图片url的
  2. 为什么不用 https://en.wikipedia.org/wiki/Levenshtein_distancehttps://en.wikipedia.org/wiki/Damerau%E2%80%93Levenshtein_distance ?建议直接dssimfid
    cc @ControlNet

图片在贴吧图床的40位16进制编号,代码里叫raw_hash
同一个帖子的同一张图,根据代码里获取到的图片src计算出的phash和我直接在新标签页中打开图片得到的src计算出的phash不一致,分别是0600300d00040000和0600380c08040000。两者都是使用compute_imghash()函数计算得到。图片的图床编号是一致的,都是3f2b8bd4b31c87016a3e7425627f9e2f0508ffc6.jpg。我不知道这是什么原因导致的,所以我现在只能把条件放宽到海明距离<=5来查找。
我当然可以使用更好的算法SSIM,但那需要比较大的改动,SSIM需要两两比较和计算图片相似度,数据库需要保存图片的blob而不能只保存hash值。图片多起来了速度也是个问题。我现在的做法改动是最小的,可以继续使用原来的算法。

@n0099
Copy link

n0099 commented Dec 22, 2022

  1. 代码里叫raw_hash

https://github.com/Starry-OvO/aiotieba/blob/bce0d8d04d05a4dbf7759581c244a8341d97f9ca/aiotieba/client.py#L2966
https://github.com/Starry-OvO/aiotieba/blob/7dbbc4ad9d9206840cdb22694a5e779e8b9fc67d/aiotieba/database.py#L565

回顾经典之规范且符合直觉的命名规律
https://github.com/Starry-OvO/aiotieba/blob/bce0d8d04d05a4dbf7759581c244a8341d97f9ca/README.md?plain=1#L27

我想我会叫他img_url_filename


  1. 图片的图床编号是一致的,都是3f2b8bd4b31c87016a3e7425627f9e2f0508ffc6.jpg

    很明显我直接在新标签页中打开图片得到的src代码里获取到的图片src不是同一个url,从而导致其不是同一种图片
    百度贴吧的ugc图片基础设施是由以前的百度相册负责的,其有着十万甚至九万个不同的域和图片处理服务endpoint(主要用来缩放图片输出缩略图):https://github.com/n0099/TiebaMonitor/blob/52af51730c0e739acaac78e0215a6a9ef85f9a7a/be/resources/views/renderPostContent.blade.php#L79-L84

  • 图片处理服务endpoint:/forum/w%3D720%3Bq%3D60%3Bg%3D0/sign={某种由服务端生成的token}/{raw_hash}.jpg(那一串urldecode后是w=720;q=60;g=0,合理假设w就是图片宽度,q是jpeg(或其他图片有损编码)compression quality/level值,类似 https://imagemagick.org/script/command-line-options.php#quality

  • 原图endpoint:/forum/pic/item/{raw_hash}.jpg

    域(http和https都是独立存在的,没有http307):

  • 百度相册:[a-z].hiphotos.baidu.com imgsrc.baidu.com

  • 贴吧图片:tiebapic.baidu.com

  • gss1.bdstatic.com

在userscript maxurl中可以找到更多的域: https://raw.githubusercontent.com/qsniyg/maxurl/master/src/userscript.ts (ctrl+f搜索tieba
image


  1. aiotieba的代码里获取到的图片src可能是(imgsrc|tiebapic).baidu.com/forum/pic/item/上的原图
    而您没有说您在贴吧网页端的哪个页面里在新标签页中打开图片,以 tieba.baidu.com/p/{tid} 的主题帖回复贴浏览页为例
    https://tieba.baidu.com/p/8192616247 的1L中的那张图片的url是
    http://tiebapic.baidu.com/forum/w%3D580/sign=e4aee93e3ecb0a4685228b315b62f63e/4ba6b7fd5266d016e17995ecd22bd40734fa350b.jpg?tbpicau=2022-12-25-05_2ae0f8dbe307e91d5228fb7b90bdc060
    那么根据url中的w=580我们可以假定这张图片宽度会被缩小到580px,而chrome f12也指出image
    而访问原图 http://imgsrc.baidu.com/forum/pic/item/4ba6b7fd5266d016e17995ecd22bd40734fa350b.jpg 我们可以得知楼主最初上传的图片分辨率是1080x2400
    很明显两张分辨率不同的1080x2400px580x1289px绝不可能两幅图片是同一张图,而是属于某些细微的差异(例如右下角水印,原图与缩略图,二次压缩)
    我认为对于分辨率差距如此之大的两张图片aiotieba所使用的phash算法实现能够算出如此相似的hash字符串(如 @Hawaii-ol 最初对另外的两张图片试出计算得到的phash有3bit的差异)已经是phash的胜利了

  1. SSIM需要两两比较和计算图片相似度,数据库需要保存图片的blob而不能只保存hash值。图片多起来了速度也是个问题

    可以合理假定贴吧管理器目标用户群体使用图片相似度识别算法的目的主要是预先提供几张广告图片,然后对所有(新)帖子的图片辨别相似度:时间复杂度O(n)
    而不是对所有帖子的图片进行两两配对的相似度计算(类似图片查重,可以找出谁在转发复读v吧二手屎):时间复杂度O((n^2)/2)或者说O(n^2),因为 https://softwareengineering.stackexchange.com/questions/279609/big-o-question-about-an-algorithm-with-n2-n-2-growth-rate
    您也可以致电管理器头子鸡血神以获得第一手用户调研结果


  1. 关于tbpicau querystring参数
    @BANKA2017 曾于数百小时前告诉我starry神在早已放逐了我的鸡血神的贴吧管理器开发组小团体群中提及近期tiebapic域新增要求提供额外的?tbpicau={一串神必的服务端生成的token} querystring参数才会返回图片,否则返回下图
    image
    贴吧接口返回的图片url中该参数的值格式类似2022-12-25-05_{32个[0-9a-z]也就是hex},其中2022-12-25很明显是UTC+8时间的今天日期,05则是百度服务端生成这个token时的UTC+8时间的小时部分,32个hex您可以说是md5
    这可能是百度网管为了防盗链而加上的,而我当时注意到老的imgsrc域仍然没有加上这个验证,可能百度网管早就忘了还有十万甚至九万个贴吧图片域在同时工作
    starry神当时的解决方法是直接生成个格式类似于贴吧服务端返回的tbpicau值的字符串就能过,这说明服务端没有实际验证这token是否是此前贴吧服务端生成并返回给查询api的客户端的
    因此我当时就合理假定tbpicau用于防盗链是假,tracing遥测跟踪用户是真,建议学习 https://github.com/nicholascw/b23.wtf 精神直接滚回老的无需tbpicau的imgsrc域

@n0099
Copy link

n0099 commented Dec 23, 2022

  1. 我基于上一comment中提及的图片4ba6b7fd5266d016e17995ecd22bd40734fa350b对imgsrc和tiebapic两个域进行了进一步的区分:
curl 'https://tiebapic.baidu.com/forum/w%3D580/sign=85a89eefd816fdfad86cc6e6848e8cea/64380cd7912397dd491993c01c82b2b7d1a28796.jpg?tbpicau=2022-12-25-05_d5f6950a598068243dbbab4e7fa8c5c3' \
  -H 'Referer: https://tieba.baidu.com/' \
  -H 'User-Agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36' | identify -
curl 'https://imgsrc.baidu.com/forum/w%3D580/sign=85a89eefd816fdfad86cc6e6848e8cea/64380cd7912397dd491993c01c82b2b7d1a28796.jpg?tbpicau=2022-12-25-05_d5f6950a598068243dbbab4e7fa8c5c3' \
  -H 'Referer: https://tieba.baidu.com/' \
  -H 'User-Agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36' | identify -

同样是图片缩略处理服务endpoint,imgsrc域不会验证sign参数值是否有效(对于每个图片文件名贴吧客户端返回图片处理服务url中的sign值是相同的,因此可以假定sign参数值与图片文件名满射),而tiebapic域会验证
返回的图片大小为238x238px就说明是image
580x1289px是按w=580缩放后的图片
image


两个域目前都不会对任何http request header进行验证,因此删掉referer和ua header也无妨:
image


parallel "curl -s -w '%{stderr}%{time_total}s\n' 'https://{}.baidu.com/forum/w%3D580/sign=e4aee93e3ecb0a4685228b315b62f63e/4ba6b7fd5266d016e17995ecd22bd40734fa350b.jpg' | identify -" ::: imgsrc tiebapic
parallel "curl -s -w '%{stderr}%{time_total}s\n' 'https://{}.baidu.com/forum/pic/item/4ba6b7fd5266d016e17995ecd22bd40734fa350b.jpg?tbpicau=2022-12-25-05_2ae0f8dbe307e91d5228fb7b90bdc060' | identify -" ::: imgsrc tiebapic
parallel "curl -s -w '%{stderr}%{time_total}s\n' 'https://{}.baidu.com/forum/pic/item/4ba6b7fd5266d016e17995ecd22bd40734fa350b.jpg' | identify -" ::: imgsrc tiebapic

imgsrc域必须不能提供tbpicau参数(除非访问的是图片处理服务而非原图endpoint),tiebapic域对于两个endpoint都必须提供
image


parallel "curl -s -w '%{stderr}%{time_total}s\n' 'https://{}.baidu.com/forum/w%3D580/sign=e4aee93e3ecb0a4685228b315b62f63e/4ba6b7fd5266d016e17995ecd22bd40734fa350b.jpg?tbpicau=2022-12-25-05_2ae0f8dbe307e91d5228fb7b90bdc060' | md5sum" ::: imgsrc tiebapic
parallel "curl -s -w '%{stderr}%{time_total}s\n' 'https://{}.baidu.com/forum/w%3D580/sign=e4aee93e3ecb0a4685228b315b62f63e/4ba6b7fd5266d016e17995ecd22bd40734fa350b.jpg?tbpicau=2022-12-25-05_2ae0f8dbe307e91d5228fb7b90bdc060' | identify -" ::: imgsrc tiebapic

imgsrc域和tiebapic域对于图片处理服务返回的图片文件hash都是一致的
image


curl -s -w '%{stderr}%{time_total}s\n' 'https://tiebapic.baidu.com/forum/pic/item/4ba6b7fd5266d016e17995ecd22bd40734fa350b.jpg?tbpicau=2022-12-25-05_2ae0f8dbe307e91d5228fb7b90bdc060' | identify -
curl -s -w '%{stderr}%{time_total}s\n' 'https://tiebapic.baidu.com/forum/pic/item/4ba6b7fd5266d016e17995ecd22bd40734fa350b.jpg' | identify -

tiebapic域目前无法返回原图,不论是否提供了tbpicau
image


最后两个域所部署cdn节点也不同,从上述截图中的curl输出的%{time_total}请求总耗时就能看出,imgsrc域由于没有启用国外的cdn节点所以访问起来更慢
https://ping.chinaz.com/tiebapic.baidu.com
image
https://ping.chinaz.com/imgsrc.baidu.com
image

@yangbowen
Copy link

@n0099#63 (comment) 中您 mention 到我了。请不要无端 mention 我,也请不要将一些我从未说过也不了解的言论署名给我。

@ControlNet
Copy link

不知道这个所谓的贴吧phash的对于图片的不变性是否稳定。简单的说,就是如果一个图片进行缩放,旋转,或者是改变亮度,对比度,颜色,这个phash能否保持相似。
如果可以的话,得想出一个办法去用某些方式去表示这种phash的相似性,换句话说可以抽象为在空间中的位置之类的。

如果发现phash不适合做这个,那就只能去另找别的方法去表示图片了。

@n0099
Copy link

n0099 commented Dec 23, 2022

所谓的贴吧phash

是本repo aiotieba这个py库所使用的phash算法实现,我没去彻查到底是哪个实现

换句话说可以抽象为在空间中的位置

什么线代

@yangbowen
Copy link

yangbowen commented Dec 24, 2022

不知道这个所谓的贴吧phash的对于图片的不变性是否稳定。简单的说,就是如果一个图片进行缩放,旋转,或者是改变亮度,对比度,颜色,这个phash能否保持相似。

前面有提到,“细微差异”的图片的 phash 也会接近(“接近”表现为更可能有比较多的 bit 是相等的,具有比较短的海明距离)。不知道这个 细微差异 包括哪些差异,是否包括 缩放,旋转,或者是改变亮度,对比度,颜色 。这些全都取决于这个 phash 算法本身的设计。通用哈希算法是不会(而且要有意避免)表现出“相似的输入产生相似的输出”的特性的,只有专为匹配相似信息设计的哈希算法才会有这样的特性,而且 哈希值以何种形式相似 也是跟哈希算法本身的设计相耦合的。
现在既然这个 phash 算法确实表现出了这种特性,那么我想对于您说的这些变换,有理由推测至少其中的一些应该是会保持 phash 相似性的。但具体还是要通过测试或者通过观察该算法的实现才能知道。这里只是指出一点:如果该哈希算法表现出了这种 相似输入产生相似输出 的特性,那么基本上可以肯定它本就是出于 识别相似信息 的目的设计的,所以有不小的可能已经考虑到了您说的这些。
顺带一提, phash 可能是 Perceptual hashing 的简称。

比对图片像素本身的海明距离应该是一种很差的判断相似图片的方式——有理由相信只能检测水印,对于上面说的几种变换应该全都不适用。但本pr说的是比对 phash 的海明距离。只有出于 相似比对 的目的有意设计出的哈希算法会表现出相似的输入对应海明距离较短的输出的特性。那么至于 对原图片的到底哪些变换,不会让 phash 具有不短的海明距离,还是取决于这个 phash 算法的设计。

@n0099
Copy link

n0099 commented Dec 24, 2022

phash当然是指perceptual hashing,除非starry神只是借用了这个词来描述某个他亲自指挥亲自部署亲自设计亲自研发的神必算法,但问题是phash本身也只是对一系列具有相似特征的hash算法设计的笼统统称

通用哈希算法是不会(而且要有意避免)表现出“相似的输入产生相似的输出”的特性的

enwiki早已指出:A perceptual hash is a type of [locality-sensitive hash](https://en.wikipedia.org/wiki/Locality-sensitive_hashing),所以不会具有雪崩效应 https://en.wikipedia.org/wiki/Avalanche_effect

TL;DR:截止2022年12月24号,我们仍未能知晓圣starry神在py库aiotieba中所使用的phash算法具体是什么,我怀疑本质就是调opencv的某个函数

@yangbowen
Copy link

我们仍未能知晓圣starry神在py库aiotieba中所使用的phash算法具体是什么

所以这个算法不是百度选的,而是本项目作者选的吗?

@n0099
Copy link

n0099 commented Dec 24, 2022

我们仍未能知晓圣starry神在py库aiotieba中所使用的phash算法具体是什么

所以这个算法不是百度选的,而是本项目作者选的吗?

百度没有hash: #63 (comment)

所谓的贴吧phash

是本repo aiotieba这个py库所使用的phash算法实现,我没去彻查到底是哪个实现

#63 (comment) 早已指出:

  1. 什么是贴吧的raw_hash?如果您是说图片url的文件名部分,那并不是贴吧对图片内容的hash,您手动上传两次同一张md5相同的图片也会分配两个不同的图片url的

图片在贴吧图床的40位16进制编号,代码里叫raw_hash

@@ -558,7 +558,7 @@ async def _create_table_imghash(self) -> None:
async with conn.cursor() as cursor:
await cursor.execute(
f"CREATE TABLE IF NOT EXISTS `imghash_{self.fname}` \
(`img_hash` CHAR(16) PRIMARY KEY, `raw_hash` CHAR(40) UNIQUE NOT NULL, `permission` TINYINT NOT NULL DEFAULT 0, `note` VARCHAR(64) NOT NULL DEFAULT '', `record_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, \
(`img_hash` CHAR(16) PRIMARY KEY, `img_hash_uint64` BIGINT UNSIGNED UNIQUE NOT NULL, `raw_hash` CHAR(40) UNIQUE NOT NULL, `permission` TINYINT NOT NULL DEFAULT 0, `note` VARCHAR(64) NOT NULL DEFAULT '', `record_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, \
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个uint64确实是个好想法,比字符串索引快得多得多

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

使用定长blob(max_length_of_phash)或定长uint16/32/64存储bytes array不是常识?
只有前端给用户看的bytes才需要给他看bin2hex后的字符串

@lumina37
Copy link
Owner

刚刚merge好像出现了一些状况,commit已经能看到了但merge又没成功?

@n0099
Copy link

n0099 commented Dec 25, 2022

刚刚merge好像出现了一些状况,commit已经能看到了但merge又没成功?

1b7c732
您这是在本地执行git merge [remote](remote是本pr的origin,也就是Hawaii-ol:hamming)产生的commit
github当然无法把您本地执行的git merge与本pr进行关联(尽管他关联了 #63 这个issue id)

本pr现状:This branch has conflicts that must be resolved
因为目前Hawaii-ol:hamming的HEAD commit和Starry-OvO:develop的HEAD commit都修改了相同的文件
后者 1b7c732 就是将前者 2be5b64 git merge进branch Starry-OvO:develop的结果

我的建议是:立即在branch Starry-OvO:developgit reset --hard HEAD~2以回退到branch Hawaii-ol:hamming的parent bce0d8d
然后git push --force再在本网页点击merge(并跳过那5个github actions check
image

)
if hamming_distance > 0:
await cursor.execute(
f"SELECT `permission`, BIT_COUNT(`img_hash_uint64` ^ CONV(%s,16,10)) AS hd FROM `imghash_{self.fname}` HAVING hd <= %s ORDER BY hd ASC", (img_hash, hamming_distance)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@n0099
Copy link

n0099 commented Dec 25, 2022

#63 (comment)

这个uint64确实是个好想法,比字符串索引快得多得多

使用定长blob(max_length_of_phash)或定长uint16/32/64存储bytes array不是常识? 只有前端给用户看的bytes才需要给他看bin2hex后的字符串

so人对性能问题早有预言:
https://stackoverflow.com/questions/4777070/hamming-distance-on-binary-strings-in-sql/4783415#4783415 指出在mysql(版本未知,11年估计是5.6)中将定长hash拆成多个uint64存储以进行bitwise xor ^运算的速度要比以BINARY(hash长度)存储快得多

而在菲利普·欧布雷丹对此answer的comment中指向的另一个so question的answer中 https://stackoverflow.com/questions/9606492/hamming-distance-similarity-searches-in-a-database/47487949#47487949 进一步指出:

一种常见的方法(至少对我来说是常见的)是将您的哈希位字符串分成几个块并查询这些块以获得精确匹配。这是一个“预过滤”步骤。然后,您可以对返回的结果执行按位汉明距离计算,该结果应该只是整个数据集的较小子集。这可以使用数据文件或 SQL 表来完成。
所以简单来说:假设您在数据库中有一堆 32 位哈希,并且您想要找到在您的“查询”哈希的 4 位汉明距离内的每个哈希:

  1. 创建一个包含四列的表:每列将包含 32 位散列的一个 8 位(作为字符串或整数)切片,即islice 1 到 4。
  2. 在 qslice 1 到 4 中以相同的方式对查询哈希进行切片。
  3. 查询此表,以便任何qslice1=islice1 or qslice2=islice2 or qslice3=islice3 or qslice4=islice4,这为您提供了查询哈希3 位 ( 4 - 1 ) 内的每个数据库哈希。
  4. 对于每个返回的散列,计算与查询散列成对的精确汉明距离(从四个切片重建索引端散列)

第 4 步中的操作数应该比整个表的完整成对海明计算少得多。

Gurmeet Singh Manku、Arvind Jain 和 Anish Das Sarma的论文Detecting Near-Duplicates for Web Crawling也解释了这一点:

汉明距离问题
定义:给定一组 f 位指纹和一个查询指纹 F,确定现有指纹是否与 F 最多有 k 位不同。(在上述问题的批处理模式版本中,我们有一组查询指纹而不是单个查询指纹)
直觉:考虑一个由 2 df 位真正随机指纹组成的排序表。只关注表中最重要的 d 位。这些 d 位数字的列表相当于“几乎是一个计数器”,因为 (a) 存在相当多的 2 d 位组合,并且 (b) 很少有 d 位组合被复制。另一方面,最低有效的 f-d 位“几乎是随机的”。
现在选择 d 使得 |d − d| 是一个小整数。由于该表已排序,因此单个探针足以识别在 d 个最高有效位位置中与 F 匹配的所有指纹。由于 |d − d| 是小的,这样的匹配的数量预计也将是小的。对于每个匹配的指纹,我们可以很容易地弄清楚它是否在至多 k 个位位置上与 F 不同(这些差异自然会限制在 f - d 个最低有效位位置)。
上面描述的过程帮助我们找到一个现有的指纹,它在 k 位位置上与 F 不同,所有这些位置都被限制在 F 的最低有效 f-d 位中。这处理了相当多的情况。为了涵盖所有情况,构建少量额外的排序表就足够了,正如下一节中正式概述的那样。

@yangbowen
Copy link

一种常见的方法(至少对我来说是常见的)是将您的哈希位字符串分成几个块并查询这些块以获得精确匹配。这是一个“预过滤”步骤。然后,您可以对返回的结果执行按位汉明距离计算,该结果应该只是整个数据集的较小子集。这可以使用数据文件或 SQL 表来完成。 所以简单来说:假设您在数据库中有一堆 32 位哈希,并且您想要找到在您的“查询”哈希的 4 位汉明距离内的每个哈希:

1. 创建一个包含四列的表:每列将包含 32 位散列的一个 8 位(作为字符串或整数)切片,即islice 1 到 4。

2. 在 qslice 1 到 4 中以相同的方式对查询哈希进行切片。

3. 查询此表,以便任何`qslice1=islice1 or qslice2=islice2 or qslice3=islice3 or qslice4=islice4`,这为您提供了查询哈希3 位 ( 4 - 1 ) 内的每个数据库哈希。

4. 对于每个返回的散列,计算与查询散列成对的精确汉明距离(从四个切片重建索引端散列)

第 4 步中的操作数应该比整个表的完整成对海明计算少得多。

好聪明!

@n0099
Copy link

n0099 commented Dec 25, 2022

#63 (comment) 中引用的 https://stackoverflow.com/a/21058895 指出对于两个二进制(或者说byte array)输入a和b,hamming距离(a, b)本质上是BIT_COUNT(a ^ b),其中^是bitwise xor运算符(或者说作用于二进制定义域的排他或(逻辑异或)运算)

重新回顾xor与or运算的真值表:
image image
可以看到他们之间的唯一区别就是当两个输入都为T时,xor的输出是 $T\oplus T=F$ ,而or是 $T\vee T=T$
正是由于这个特征xor运算经常在位运算中用于找出两个bit[]之间的diff(差集),例如

    010
xor 110
 =  100

这说明了010110之间唯一不同的位是第一个位(也就是100),其也同时是a和b的MSB(most significant bit

@n0099
Copy link

n0099 commented Dec 25, 2022

对逻辑和/或语言学不感兴趣的请跳过本comment

请注意尽量不要使用自然语言中的来描述or(逻辑析取)和xor(逻辑异或)运算,因为在不同语言中的可以指代完全不同的逻辑运算(但对于大多数自然语言而言是xor)
例如四叶CS硕士PLT理论中级高手逻辑带师仏皇irol阁下 @kokoro-aya 早已指出在自然语言之法文中的soit是表达的逻辑异或而非逻辑析取:https://n0099.net/v/d/1509/16 image

https://en.wiktionary.org/wiki/soit#Conjunction_2 中的给出的例子: image

Il veut adopter un animal de compagnie, soit un chat, soit un chien.
He wants to adopt a pet, either a cat, or a dog.
他想领养一只宠物,要么是一只猫,要么是一只狗。

而如果您省略了不定冠词并使用中文的来表达那就会具有被解释为逻辑析取的可能:

他想领养猫或狗。 (他可能同时领养了猫和狗,并且数量未知因为没有不定冠词或量词能够暗示领养了的数量)

如果使用英文or表达并通过名词pet的单复数来暗示,那么or也可以被解释为是逻辑析取

He wants to adopt pets, cat or dog. (他必定领养了复数个宠物pet,但其中可能有猫和狗,也可能全都是猫或全都是狗)

当然对于这个句子的语义更合理的表达是

He wants to adopt pets, cat and dog.

enwiki对此也早有总结: https://en.wikipedia.org/wiki/Exclusive_or#Exclusive_or_in_natural_language https://en.wikipedia.org/wiki/Logical_disjunction#Natural_language

自然语言中的析取不与经典逻辑中的∨相匹配。因为经典逻辑析取是包容性的(也就是前文所说的 $T\vee T=T$$T\oplus T=F$ ),而自然语言析取通常会被理解为排他性的,就像下面的英语通常所表达的语义:

Mary is eating an apple or a pear. (玛丽正在吃一个苹果或一个梨。这里同样是靠不定冠词暗示了她必定只在吃一个水果)

这种推论有时被理解为逻辑蕴涵,例如阿尔弗雷德·塔斯基认为自然语言的析取在经典解释和非经典解释之间是模棱两可的。近期在语用学方面的工作表明,这种推论可以被基于经典解释的形式语义学表达中的会话含义推导出。然而包括匈牙利语vagy...vagy和法语soit...soit在内的自然语言析取结构被认为是固有的排他性析取,因为其在被理解为是包容性析取时会不符合语法。[1]

您也可以使用和/或 and/or来确切地表达您说的是逻辑析取: https://en.wikipedia.org/wiki/And/or

@n0099
Copy link

n0099 commented Dec 25, 2022

而mysql函数BIT_COUNT()会给出输入的bit[](强转为uint64然后再转int[])中所有二进制1的个数
image

+--------------+--------------+---------------+---------------+
| BIT_COUNT(1) | BIT_COUNT(3) | BIT_COUNT(64) | BIT_COUNT(65) |
+--------------+--------------+---------------+---------------+
|            1 |            2 |             1 |             2 |
+--------------+--------------+---------------+---------------+

uint10b1(二进制表达),有1个1
uint30b11,有2个1
uint640b1000000,有1个1
uint650b1000001,有2个1

强转为uint64然后再转int[]的例子:

+----------------+---------------------+
| BIT_COUNT('1') | BIT_COUNT(BINARY 1) |
+----------------+---------------------+
|              1 |                   3 |
+----------------+---------------------+

字符串'1'被转换为uint1,所以仍然是0b1,有1个1
BINARY 1使得字符串'1'先被转换为ASCII二进制表达,查阅ascii table可得是0x31或者说0b110001,因此有3个1

@n0099
Copy link

n0099 commented Dec 25, 2022

对数学不感兴趣的请跳过本comment

https://en.wikipedia.org/wiki/Hamming_distance 指出:

The Hamming distance between two words a and b can also be seen as the Hamming weight of a − b for an appropriate choice of the − operator, much as the difference between two integers can be seen as a distance from zero on the number line.[clarification needed]

原文中的a − b for an appropriate choice of the − operator实际上就是指将输入参数a和b视作集合,对他们做差集 $a \setminus b$,但请注意这个差集不是常规的 $\{1,2\} \setminus \{3,1,2,4\} = \{3,4\}$(即与xor或者说逻辑异或运算具有相同文氏图的集合对称差集运算),而是对两个相同长度输入集合逐个成员的diff(也就是逐成员做逻辑异或)
而他描述的十分不清楚所以被挂上了{{clarify}},我特此提请四叶CS硕士PLT理论中级高手逻辑带师仏皇irol阁下 @kokoro-aya 立即修订此enwiki条目
而根据此前得出的xor运算经常在位运算中用于找出两个bit[]之间的diff$a \oplus b = a \setminus b \quad\{a \in b\in\{0,1\}\}\ $,enwik中下一句也点明了:

For binary strings a and b the Hamming distance is equal to the number of ones (population count) in a XOR b

所以当定义域是二进制时 $A \in B \in \{0,1\}$$distance(a, b) = weight(a \oplus b)$

https://en.wikipedia.org/wiki/Hamming_weight#Language_support 又指出BIT_COUNT()实际上就是在计算hamming weight
或者说对于二进制定义域,hamming weight的定义就是输入二进制中1的个数

The Hamming weight of a string is the number of symbols that are different from the zero-symbol of the alphabet used. It is thus equivalent to the Hamming distance from the all-zero string of the same length. For the most typical case, a string of bits, this is the number of 1's in the string

而推广到任何字符串定义域时,hamming weight是输入字符串与该字符串可用值的全0版本的hamming距离
什么是0值是根据输入字符串而异的,当输入字符串只是二进制时,0值自然就是0b0

$$ zero(S) = \begin{cases} 0 &\quad\text{if S} \in \{0,1\}\\ 0 &\quad\text{if S} \in \{0,...,9,A,B,C,D,E,F\}\\ '' &\quad\text{if S} \in \text{any character}\\ \end{cases} $$

注意这实际上与上文中hamming距离的定义构成了循环论证(can also be seen as the Hamming weight

$$weight(S) = distance(S, zero(S))$$

$$distance=\sum_{ i \in \{ 1,|A| \} }weight(A_{i} \setminus B_{i})$$


为了避免循环论证就需要引入额外的 https://en.wikipedia.org/wiki/Hamming_space 概念
就像土澳数据科学家文科生转应用统计学带手子cn神此前于 #63 (comment) 指出的

换句话说可以抽象为在空间中的位置之类的

可以构造一个对于3位长的二进制(也就是说 $a = \{ 0, 1 \}, |a|=3$ )的hamming space

长度为 3 的二进制字符串的汉明空间。立方图中顶点之间的距离等于字符串之间的汉明距离
image

我们不难看出图中每个顶点的值都是他的坐标的zyx部分拼接而成

  • 例如最左下角点的坐标是(0,0,0),值就是000
  • 他的y轴+1的点的坐标是(0,1,0),值就是010
  • 他的z轴+1的点的坐标是(0,0,1),值就是001

众所周知立方体有8个顶点,这8个顶点代表着3位二进制序列的2^3=8种可能的值

然后我们只需要给任意每两个顶点(也就是输入a, b)之间画线找出最短的路径也就是曼哈顿距离,那么这个路径经过的数量就是hamming距离结果
image


而当输入二进制是4位长时,其hamming space就是一个四维超立方体:

用于查找汉明距离的4 位二进制超正方体
image

但我们仍然可以对这个超立方体的两个顶点找出曼哈顿距离:

两个示例距离:0100→1001的距离为 3;0110→1110有距离1
image

建议回顾经典之世界名画:在三维空间中对四维超立方体的Schlegel diagram透视投影线框二维图片https://www.software3d.com/Stella.php
image
https://commons.wikimedia.org/wiki/File:8-cell.gif
image
https://en.wikipedia.org/wiki/File:Dimension_levels.svg
image


因此 https://en.wikipedia.org/wiki/Hamming_distance#Properties 对hamming距离给出的替代定义是

可以从长度为n的二进制字符串的汉明空间(又称汉明立方体)中得出汉明距离:在这个汉明空间中求出的汉明距离的集合(也就是对于n位二进制输入的汉明距离的值域)等效于超立方图中顶点之间的最短距离的集合。
还可以将长度为n的二进制字符串视为向量

$$\mathbb {R} ^{n}$$

通过将字符串中的每个符号视为实数坐标;通过这种嵌入,每个字符串变成了一个n维超立方体的顶点,字符串的汉明距离等于顶点之间的曼哈顿距离


  • 当输入二进制是2位长时,hamming space只是一个正方形(或者说2x2矩阵),因为正方形有着2^2=4个顶点和边:
    10-11
    |  |
    00-01
    

以下是错误的假想:

  • 只有1位长时就退化到了有向无环图:0-1
  • 如果输入的1位是hex(也就是输入字符串每字符取值区间符合正则[0-9a-z])而不是bin,那这个图是0-1-2-3-4-5-6-7-8-9-A-B-C-D-E-F
    对这样的图计算距离我们只需要将其视作一个链表/数组直接求offset即可,例如0x70xE的距离是offset(E)-offset(7)=8
  • 同理我们可以得出输入是2位hex时,将会是一个正16^2=256边型
  • 3位hex时,将会是一个有着16^3=4096个顶点的3维正多面体(我不知道符合这个顶点数量的体有多少面)
  • 4位hex时,将会是一个有着16^4=65536个顶点的4维正多胞形(我不知道符合这个顶点数量的多胞形有多少个体)

而推广到任何字符串定义域时,hamming weight是输入字符串与该字符串可用值的全0版本的hamming距离

除此之外我们也可以额外再将hamming weight定义为 对非0值的出现次数的求和

$$ shouldSum(v, zero) = \begin{cases} 1 &\quad\text{if v}\neq zero\\ 0 &\quad\text{if v}=zero\\ \end{cases} $$

$$weight(S) = \sum_{i=1}^{|S|} shouldSum(S_{i}, zero(S))$$
这样同样能够解决循环论证

@kokoro-aya
Copy link

所以phash的意思是两个图片之间的不相似度决定了他们对应的hash值的差异程度,然后用哈明距离来测,比如说这两个hash之间的相似度是多少个9……这样嘛

@n0099
Copy link

n0099 commented Dec 25, 2022

相似度是多少个9

如果您非要做统计学意义上的normalization以展示给前端用户带领导看,您可以简单的做
$$\frac{distance(a,b)}{len(a)} \quad\{len(a)=len(b)\}$$
例如对于输入11100101distance(1110,0101)=33/4=75%
因此您可以说11100101之间相差75%的bit

@kokoro-aya
Copy link

确实

@n0099
Copy link

n0099 commented Dec 25, 2022

对hamming distance的输入做什么进制/编码转换(或者不转换)有着重要的区别:

这是一个基于 #63 (comment) 中指出的

对两个相同长度输入集合逐个成员的diff(也就是逐成员做逻辑异或)

用ts/js实现的

$$weight(S): \sum_{i=1}^{|S|} S_{i} \quad\{S\in \{0,1\}\}$$

$$
sequenceDiff: A \rightarrow \{0,1\}
\begin{cases}
0 &\quad\text{if A}{i}=B{i}\
1 &\quad\text{if A}{i}\neq B{i}\
\end{cases}
$$

$$distance(A,B): weight(sequenceDiff(A,B)) \quad\{len(a)=len(b)\}$$

对两个输入集合进行逐成员异或后对所有不同的成员出现次数求和(也就是BIT_COUNT(a ^ b)

const weight = (s: ReturnType<typeof sequenceDiff>) =>
    s.reduce<number>((acc: number, cur: 0|1) => acc + Number(cur), 0);

const sequenceDiff = (a: string[], b: string[]): Array<0|1> =>
    a.map((x, i) => b[i] === x ? 0 : 1);

const distance = (a: string, b: string) => weight(sequenceDiff(a.split(''), b.split('')));

demo: jsfiddle, ts playground

image
对于c的hex0x63e的hex0x65之间只相差一个hex,所以其距离和字符串表达时相同都是1
但bin表达的c和e之间的距离是2
image
如果将转换前的字符串字符换成差距更大的unicode code point便会:
image
汉字汉族做字符串时的距离只有1,但转换为hex后就达到了4,bin更是8

所以pr author在最开始提到的

0600300 d 00040000
0600380 c 08040000
但计算得到的phash有3bit的差异

如果您直接比较字符串形式的这两个hash,那么d和c之间只会有1个距离,而作为bin表达时就是3bit
image

因此您需要根据您的实际需求来决定是否转换hamming距离的两个输入值,以及转换成什么

  • 当您的需求是比较字符串中字符的差距时不应该进行转换,也就是您不能直接把utf-8/16表达的char[]转uint8/16然后直接BIT_COUNT(a ^ b)
  • 反过来时您就应该转换,您需要比较的是什么编码/进制下就应该转换为那个编码/进制,例如对于hex表达的phash比较时就应该计算其bin形式的距离而不是hex形式

hamming距离的泛用性

如果对

const sequenceDiff = (a: string[], b: string[]): Array<0|1> =>
    a.map((x, i) => b[i] === x ? 0 : 1);

中的===比较运算符进行修改,您可以将hamming距离用于比较任何东西[]之间的差距
例如改成lodash的_.isEqual()后: https://jsfiddle.net/p16r2n57/6/
image
而在传统oop语言如c# java中,您也可以通过override equalshashCode方法来实现这样的自定义比较逻辑:
https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/statements-expressions-operators/how-to-define-value-equality-for-a-type
https://www.baeldung.com/java-equals-method-operator-difference
https://dzone.com/articles/identity-vs-equality-in-java

当然当您不需要这种泛性时,例如您只需要对两个int(或者说byte array, bit[],他们都可以具有相同的int[]表述)进行比较(也就是本pr的需求)时,您只需要使用性能更好的BIT_COUNT(a ^ b)

// https://stackoverflow.com/questions/43122082/efficiently-count-the-number-of-bits-in-an-integer-in-javascript/43122214#43122214
function bitCount (n) {
  n = n - ((n >> 1) & 0x55555555)
  n = (n & 0x33333333) + ((n >> 2) & 0x33333333)
  return ((n + (n >> 4) & 0xF0F0F0F) * 0x1010101) >> 24
}

const distance = (a, b) => bitCount(a ^ b);

console.log(distance(1, 2)); // 0b01, 0b10 = 2
console.log(distance(64, 65)); // 0b1000000, 0b1000001 = 1

@n0099
Copy link

n0099 commented Dec 25, 2022

#63 (comment) 中曾经提出:

2. 为什么不用 en.wikipedia.org/wiki/Levenshtein_distanceen.wikipedia.org/wiki/Damerau%E2%80%93Levenshtein_distance

https://en.wikipedia.org/wiki/Edit_distance#Types_of_edit_distance 指出

不同类型的编辑距离允许不同的字符串操作集。例如:

https://en.wikipedia.org/wiki/Levenshtein_distance#Upper_and_lower_bounds 进一步指出了对于相同的输入,levenshtein距离有可能比hamming距离更小

Levenshtein 距离有几个简单的上限和下限。这些包括:

  • 如果字符串具有相同的大小,则汉明距离是 Levenshtein 距离的上限。汉明距离是两个字符串中对应符号不同的位置的个数。

两个相同长度的字符串之间的 Levenshtein 距离严格小于 Hamming 距离的示例由“flaw”和“lawn”对给出。这里 Levenshtein 距离等于 2(从前面删除“f”;在末尾插入“n”)。汉明距离为 4。

为什么hamming距离要求两个输入字符串的长度必须相同 $len(a)=len(b)$

按照 #63 (comment) 中cn神的思想

换句话说可以抽象为在空间中的位置之类的

您可以将任意n位数(其中每位具有x种取值,例如对于二进制输入 $\{0,1\}$ x就是2)长的输入首先转换为他们之间的逐位xor,xor的值域只会是 ${0,1}$ 也就是二进制T,F
然后再将xor结果转换为一个具有2^n个顶点的n维多胞形
其中每个顶点代表着一个逐位xor结果的定义域值(n位长的所有可能二进制值)
对于每个n位长的xor值,他的顶点的坐标是(第n位作为第n轴的值, 第n-1位作为第n-1轴的值, ..., 第1位作为第1轴的值), 其中轴是倒序的xyz(假设n=3时3维空间有着3条轴,n=4时4维空间就有着4条轴)集合
对这样一个n维多胞形的任意两个顶点寻找他们之间的最短路径,这个最短路径所经过的边数量就是hamming距离(...两个顶点的值)

而如果您想要寻找两个不同长度的输入序列之间的距离,您会发现您在试图关联两个维度都不同(更别说每个顶点坐标也不同)的多胞形

上文中enwiki也指出

不同类型的编辑距离允许不同的字符串操作集,例如汉明距离只允许替换,因此它只适用于相同长度的字符串。

只允许替换就意味着他是基于 对输入集合逐元素xor结果 而得出的

当然如果您一定要用hamming距离,而两个输入又大体上是相似的,只是位数不同,那么您可以基于相似部分的边界进行padding和对齐
例如基于10101100(更长的输入)用0来padding对齐010110(更短的输入)的结果是00101100(通过padding 0使得更短的长度变得与更长的相同),他们之间的hamming距离是1
但这种场景下为什么不用其他编辑距离如 https://en.wikipedia.org/wiki/Levenshtein_distance 呢?毕竟您找出相似部分并进行对齐所消耗的算力远比其他编辑距离算法要大(假如您使用 https://en.wikipedia.org/wiki/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm 其最坏时间复杂度是O(更长的输入的长度+更短的输入的长度),最好是O(更短的输入的长度)

@lumina37 lumina37 merged commit 5df551e into lumina37:develop Dec 26, 2022
@n0099
Copy link

n0099 commented Dec 26, 2022

#63 (comment)

立即在branch Starry-OvO:develop上git reset --hard HEAD~2以回退到branch Hawaii-ol:hamming的parent bce0d8d
然后git push --force再在本网页点击merge(并跳过那5个github actions check

回顾经典之CFPA-硅岩社区事件
CFPAOrg/Minecraft-Mod-Language-Package#1536 (comment)
image
image
https://github.com/TheRealKamisama/Funs/tree/master/%E4%B9%90%E5%A4%A7%E8%83%AF

@kokoro-aya
Copy link

莱文斯坦距离的计算效率比哈明距离的计算效率要低得多,而且您也提到了莱文斯坦距离除了替换外还允许其他操作,在hash这个场景里我会怀疑这个算法的适用性,或者某种程度上的overkill。

@n0099
Copy link

n0099 commented Dec 26, 2022

莱文斯坦距离的计算效率比哈明距离的计算效率要低得多

#63 (comment) 指出对于纯int[]计算hamming距离时可以直接使用等价的BIT_COUNT(a ^ b)

在hash这个场景里我会怀疑这个算法的适用性

hash是定长的,除非您在比较不同长度的SHA-2/3,如SHA-256和SHA-512,但那也没有意义因为对于相同输入不同长度的SHA-2/3输出完全不同(雪崩效应)
所以两个hash不用做 寻找相似substr并padding 就能直接作为hamming距离的输入
那么对于相同长度的输入,levenshtein距离的主要特点是其输出值有可能比hamming距离更小
我不知道更小的编辑距离值是否代表着其更适合量化比较两个相同长度序列之间的差距

@kokoro-aya
Copy link

对于不同算法的hash出来的结果进行字面上的比较没有意义。

至于莱文斯坦和哈明距离的选用问题,sof上有过评价:

That question really depends on the types of sequences you are matching, and what result you want.
If it's not a problem that "1234567890" and "0123456789" are considered totally different, indeed Hamming distance is fine.

也许您应该考虑一下,对于两个字符串,如果他们的输出值一致时,这意味着什么。

https://stackoverflow.com/questions/4588541/hamming-distance-vs-levenshtein-distance

@n0099
Copy link

n0099 commented Dec 26, 2022

If it's not a problem that "1234567890" and "0123456789" are considered totally different

然而我并不知道phash算法对于两张肉眼看起来相似的图片是否会产生相似但具有位移的phash(就像"1234567890" and "0123456789",或者说0111000111

事实核查:截止2022年12月26号,我们仍未能知晓 #63 (comment)

TL;DR:截止2022年12月24号,我们仍未能知晓圣starry神在py库aiotieba中所使用的phash算法具体是什么,我怀疑本质就是调opencv的某个函数

so问题的第二个回答 https://stackoverflow.com/questions/4588541/hamming-distance-vs-levenshtein-distance/54811897#54811897 进一步指出:

when you compare 123 to 123456 it's different if you pad either at the end of the string or at the start of the string. The similarity of ___123 with 123456 is 0, but The similarity of 123___ with 123456 is 3.

这也就是 #63 (comment) 中的

例如基于10101100(更长的输入)用0来padding对齐010110(更短的输入)的结果是00101100(通过padding 0使得更短的长度变得与更长的相同),他们之间的hamming距离是1

TL;DR

  1. levenshtein距离
  • 更适合比较长度不同的序列(尤其是字符串)
  • 时间复杂度比hamming距离更高
  • 其输出的编辑距离值<=同长度输入下的hamming距离
  1. hamming距离
  • 更适合比较长度相同的序列(尤其是二进制/byte array)
  • 时间复杂度低O(len(a)+len(b))(也就是对相同长度输入的两次遍历),且对于二进制序列输入可以做BIT_COUNT(a ^ b)位运算特化
  • 其输出的编辑距离值>=同长度输入下的levenshtein距离

@n0099
Copy link

n0099 commented Dec 26, 2022

image

https://tieba.baidu.com/p/6144593813
建议立即致电土澳数据科学家文科生转应用统计学带手子cn神

#63 (comment)

而starry神对此也早有预言:

科学是什么?不在于你研究的东西有多么高大上,而在于你对待研究的方法与态度。
哪怕一个小学生,他想着庆丰包子要怎么吃才好吃,最后亲自指挥亲自部署出一整套方法论,这也是科学,科学发展观。

例子

根据这段视频,Rand Paul 表示美国花费了 118,000 美元,看看 Thanos 的金属复制品是否可以弹响他的手指。这就是他们想用这些资金搞清楚的事情吗?

我们花了 118,000 美元来研究漫威漫画邪恶军阀灭霸的金属复制品机器人是否可以打响指。[...] 他们显然雇了一些人戴上金属手套,然后试着打响他的手指。你知道吗?他们发现用金属手指不可能发出啪的一声。因此,请注意世界上的机器人:打响指很难。

由于我在回复评论时发现了另一种方法,因此我将分享它。请注意,来源rferl.org也不是——完全——是公正的。我提到它是为了完整性,但对它会是一个可靠的指标没有真正的看法。顺便说一下,这是从四月份开始的,当时俄罗斯的情况要好得多:

在他们的实验中,研究人员使用俄罗斯设计的在线社会学工具 Toloka 招募了 3,000 名成年人,并设计了一系列问题,询问受访者是否支持以下四种社会政策中的一种或多种:同性婚姻、堕胎限制、战争乌克兰,以及为贫穷的俄罗斯人支付的现金福利。
没有要求受访者说出他们支持哪些政策,而只是说他们支持四个项目中的多少。
在这项于 4 月 4 日进行的调查中,社会学家将其广泛称为“列表实验”,其中一半的受访者得到了一份三项列表,其中省略了乌克兰> > 战争的问题;另一半得到一份包含乌克兰战争问题的四项清单。
研究人员还向受访者提出了一个直接的是或否问题:“你支持这场战争吗?”
结果显示,当俄罗斯人被直接问到“你支持战争吗?”的问题时。68% 的人说他们做到了。然而,当使用列表实验时,对战争的支持率下降到 53%。
“当被问及他们对战争的支持时,俄罗斯人会说出全部真相吗?” 研究人员写道。“根据我们的实验,我们可以有把握地得出结论,他们没有。

image

@n0099
Copy link

n0099 commented Jan 2, 2023

#63 (comment)

回顾经典之规范且符合直觉的命名规律
https://github.com/Starry-OvO/aiotieba/blob/bce0d8d04d05a4dbf7759581c244a8341d97f9ca/README.md?plain=1#L27
我想我会叫他img_url_filename

ef8a8c3#r94765151 进一步指出:

p本就是post的缩写
贴吧泰国第几的en水平指导下的内部命名是

  • 吧forum
  • 主题帖thread
  • 回复贴post
  • 楼中楼subpost(考虑到13年10月才引入楼中楼叫subpost也不奇怪)

让我想起了flarum把主题帖叫discussion,phpbb的主题帖叫topic,而两者的回复贴都叫post

而tbm的命名是:

  • 吧forum
  • 对以下3种帖子的统称post(可以用来命名抽象类)
  • 主题帖thread
  • 回复贴reply
  • 楼中楼subreply

@n0099 n0099 mentioned this pull request Jan 5, 2023
@n0099
Copy link

n0099 commented Jan 19, 2023

#63 (comment)

但问题是phash本身也只是对一系列具有相似特征的hash算法设计的笼统统称

libphash本身就提供了3种算法:分别基于RADISH (radial hash) DCT hash Marr/Mexican hat wavelet https://www.phash.org/demo/
而libphash的作者David Starkweather 19年也研究过应用统计学赋能传统的Evan Klinger于11年发明的基于DCT的phash算法: https://www.phash.dev/posts/concise-image-descriptor 也就是ConvNet (pHashML)
他还提供了商业版本的库phash pro,比以gpl协议开源的libphash多了两种算法: https://www.phash.org/apps/

#63 (comment)

不知道这个所谓的贴吧phash的对于图片的不变性是否稳定。简单的说,就是如果一个图片进行缩放,旋转,或者是改变亮度,对比度,颜色,这个phash能否保持相似。 如果可以的话,得想出一个办法去用某些方式去表示这种phash的相似性,换句话说可以抽象为在空间中的位置之类的。

如果发现phash不适合做这个,那就只能去另找别的方法去表示图片了。

#63 (comment)

不知道这个 细微差异 包括哪些差异,是否包括 缩放,旋转,或者是改变亮度,对比度,颜色 。这些全都取决于这个 phash 算法本身的设计

这篇币圈部落格直观的将多个图片hash算法的每步骤进行了可视化: https://content-blockchain.org/research/testing-different-image-hash-functions/ 其所测试的算法实现来自 https://github.com/JohannesBuchner/imagehash

这个py库重新实现并封装了多个传统算法和基于CNN的应用统计学实现: https://idealo.github.io/imagededup/methods/hashing/

而十年前的某位数字取证学家 FotoForensics.com 站长Neal Krawetz(其发明了 https://en.wikipedia.org/wiki/Error_level_analysis )早已研究出了ahash dhash phash等算法的早期朴素实现: https://www.hackerfactor.com/blog/index.php?/archives/432-Looks-Like-It.html
在评论区中David Oftedal发明了另一种类似phash但砍掉了计算量较大的DCT过程从而达到ahash的速度但准确度又类似于phash的dhash: https://github.com/benhoyt/dhash
https://www.hackerfactor.com/blog/index.php?/archives/529-Kind-of-Like-That.html

建议回顾21年时时任四叶ipad头子 @BANKA2017 所抵制的苹果为了与邪恶组织NCMEC合作共同对抗泛银河系格雷科技分部邪恶组织四叶重工炼铜本部叶独头子叶独群组联合体陈意志第三帝国元首炼铜傻狗橙猫在四叶大肆传播CSAM罪恶行径的NeuralHash
AsuharietYgvar/AppleNeuralHash2ONNX#1
https://github.com/anishathalye/neural-hash-collider
https://www.hackerfactor.com/blog/index.php?/archives/929-One-Bad-Apple.html
image
image

以及10年前MSFT出于类似目的发明专利PhotoDNA https://github.com/jankais3r/jPhotoDNA https://github.com/gabedwrds/photodna-matcher

@kokoro-aya
Copy link

居然不是indoor post或者post in post

@n0099
Copy link

n0099 commented Jan 20, 2023

@n0099
Copy link

n0099 commented Jan 20, 2023

#63 (comment)

TL;DR:截止2022年12月24号,我们仍未能知晓圣starry神在py库aiotieba中所使用的phash算法具体是什么,我怀疑本质就是调opencv的某个函数

https://docs.opencv.org/4.x/d4/d93/group__img__hash.html
建议立即开始FFI
其他语言的opencv wrapper也都有这个namespace http://shimat.github.io/opencvsharp/api/OpenCvSharp.ImgHash.AverageHash.html

国人 @stereomatchingkiss 早已于16年重复造了上述图片hash算法的实现并pr至opencv: opencv/opencv_contrib#688
http://qtandopencv.blogspot.com/2016/06/introduction-to-image-hash-module-of.html
http://qtandopencv.blogspot.com/2016/06/speed-up-image-hashing-of-opencvimghash.html

19年有人指责重新实现算法违反了libphash的GPL: opencv/opencv_contrib#2183

@n0099
Copy link

n0099 commented Mar 17, 2023

lumina37/aiotieba-reviewer@2b07113

所以阁下又换opencv_contrib img_hash的ahash了?
可视化:
https://erdogant.github.io/undouble/pages/html/hash_functions.html
https://content-blockchain.org/research/testing-different-image-hash-functions/

我的建议是直接用上所有opencv_contrib img_hash的可用hash算法,然而即便如此都比我用paddleocr+tesseract扫一遍图片要快

@lumina37
Copy link
Owner

本来就是ahash,之前忘了改注释而已

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants