基于Python的图片及音频搜索引擎

LittleGirl

发布日期: 2019-05-23 11:42:23 浏览量: 346
评分:
star star star star star star star star star star_border
*转载请注明来自write-bug.com

在此次实验报告中将会分为五个部分进行单独分析,这五个部分分别是:爬虫、界面、文本搜索、图片搜索以及音频搜索。

一爬虫

1.1 库

  • requests

  • urllib

1.2 实现功能

  • 能够通过requests.get获得网页上的内容,并使用json.loads进行解析

  • 能够快速定位歌曲、专辑的信息,包括专辑名、歌手、歌词、专辑简介、发行时间、流派、发行公司等

  • 能够从网页上下载歌曲和图片

1.3 实现过程

  • 爬取QQ音乐网站的榜单,通过榜单下载歌曲

  • 分析发现搜索各首歌曲的 url 地址中的区别在于其中的一段内容代表的是搜索输入的文字的编码,利用这一特性模拟搜索从榜单上爬取下来的歌曲

  • 通过分析网页的 url 地址,发现每一首歌曲都有自己特定的”media_mid”属性,这个属性是各个歌曲的下载url地址唯一不同的地方。通过将该属性替换到url中的固定位置,可以完成歌曲、专辑、歌词定位

  • 定位到歌曲、专辑、歌词、图片地址后完成文本的写入

1.4 难点

  • reques 取得 url 地址是搜索页面下的单曲一栏,然而这里面并没有歌词,故将搜索歌曲的页面从单曲切换到歌词

  • 由于该页面中有大量中文字符,会出现json无法解析的编码,故使用正则表达式作类似处理

  • 专辑信息只能通过歌曲的 js 代码中获得的 media_mid 属性定位后,再通过正则表达式来提取

二、界面

2.1 库

  • Wow.min.js

  • animate.css

  • jquery.min.js

2.2 实现的功能

2.2.1 翻页

  • 由于文本搜索的结果较多,故使用javascript实现了分页的效果。设置最大显示量为20个结果,每页显示4个结果,共五页。 并实现了翻页、上一页、下一页的功能。

效果如下

代码如下

  1. <script language="JavaScript" type="text/javascript" >
  2. var total="20";//最大数据量
  3. var pagesize="4";//每页的显示量
  4. var pagenum=Math.ceil(total/pagesize);//一共有多少页
  5. //生成导航
  6. function initpage(nowpage){
  7. var pagenav='<a href="#" onclick=up('+ nowpage+');>上一页</a>';
  8. for(i=1;i<=pagenum;i++){
  9. pagenav += (i==nowpage)?'<span class="now">' + i + '</span>':'<a class="number" href="#" onclick=goToPage(' + i + ');>' + i + '</a> ';
  10. }
  11. pagenav += '<a href="#" onclick=down('+ nowpage+');>下一页</a>';
  12. document.getElementById('pages').innerHTML=pagenav;
  13. }
  14. //翻页
  15. function goToPage(page){
  16. var start=(page-1)*pagesize;
  17. var max=page*pagesize;
  18. for(vs=0;vs<total;vs++){
  19. var obj=document.getElementById('item_' + vs);
  20. obj.style.display="none";
  21. }
  22. if(max>total)max=total;
  23. for(v=start;v<max;v++){
  24. var obj=document.getElementById('item_' + v);
  25. obj.style.display="block";
  26. }
  27. initpage(page);
  28. }
  29. //上一页
  30. function up(nowpage){
  31. if(nowpage==1)return false;
  32. if(nowpage>1)goToPage((nowpage-1));
  33. }
  34. //下一页
  35. function down(nowpage){
  36. if(nowpage==pagenum)return false;
  37. if(nowpage<pagenum)goToPage((nowpage+1));
  38. }
  39. </script>

2.2.2 轮播图

可自动滚动播放海报,亦可点击左右切换按钮或下方圆形选择按钮切换海报。

效果如下

代码如下

  1. //轮播图函数
  2. function jAutoPlay() {
  3. if (curIndex < $jUlLis.length) {
  4. curIndex++;
  5. } else {
  6. curIndex = 1;
  7. $jUl[0].style.left = 0;
  8. }
  9. $jUl.stop().animate({
  10. left: -liWidth * curIndex
  11. });
  12. if (jOlIndex < $jOlLis.length-1) {
  13. jOlIndex++;
  14. console.log(jOlIndex)
  15. } else {
  16. jOlIndex = 0;
  17. }
  18. $jOl.children().eq(jOlIndex).addClass("current").siblings().removeClass("current");
  19. }

2.2.3 NEW SONGS板块从上部飞入slideInDown的动画效果

当下拉至NEW SONGS这一板块时,图片从左到右依次从上部向下飞入指定位置。

当检测到鼠标滑过图片区域时,图片下部的歌曲信息由原先的一行专辑名再增加一行歌手名。

2.2.4 PROMOTION板块的bounceInDown弹跳飞入动画效果

当检测到鼠标进入图片区域时,浮于图片上显示图片的专辑名、歌手名等信息。

代码如下

  1. <!-- t-promot-start -->
  2. <div class="t-promot w">
  3. <div class="title">
  4. <h2>PROMOTION<br><span>热门推荐</span></h2>
  5. </div>
  6. <div class="t-promot-t clearfix">
  7. <a href="javascript:;" class="t-a-imgs wow bounceInDown" data-wow-delay="0.2s"><span><i>你身边还有我<br/>阿里郎</i></span><img src="/static/images/t-promot1.jpg"/></a>
  8. <a href="javascript:;" class="t-a-imgs wow bounceInDown" data-wow-delay="0.3s"><span><i>别对我动情<br/>关晓彤</i></span><img src="static/images/t-promot2.jpg"/></a>
  9. <a href="javascript:;" class="t-a-imgs wow bounceInDown" data-wow-delay="0.2s"><span><i>新世界<br/>小贱</i></span><img src="static/images/t-promot3.jpg"/></a>
  10. <a href="javascript:;" class="t-a-imgs wow bounceInDown" data-wow-delay="0.2s"><span><i>星星照亮回家的路<br/>庞龙</i></span><img src="/static/images/t-promot4.jpg"/></a>
  11. </div>
  12. </div>

2.2.5 回到顶部按钮

按钮置于页面右侧,点击即可返回到页面顶部。

代码如下

  1. //返回顶部
  2. var timer = null;
  3. $(".t-btn7").click(function(){
  4. var leader = scroll().top;
  5. clearInterval(timer);
  6. timer = setInterval(function(){
  7. var target = 0;
  8. var step = (target-leader)/10;
  9. step = step>0 ? Math.ceil(step):Math.floor(step);
  10. leader = leader+step;
  11. window.scrollTo(0,leader);
  12. if(target == leader){
  13. clearInterval(timer);
  14. }
  15. },25)
  16. })
  17. function scroll() { // 开始封装自己的scrollTop
  18. if(window.pageYOffset != null) { // ie9+ 高版本浏览器
  19. // 因为 window.pageYOffset 默认的是 0 所以这里需要判断
  20. return {
  21. left: window.pageXOffset,
  22. top: window.pageYOffset
  23. }
  24. }
  25. else if(document.compatMode === "CSS1Compat") { // 标准浏览器 来判断有没有声明DTD
  26. return {
  27. left: document.documentElement.scrollLeft,
  28. top: document.documentElement.scrollTop
  29. }
  30. }
  31. return { // 未声明 DTD
  32. left: document.body.scrollLeft,
  33. top: document.body.scrollTop
  34. }
  35. }

2.2.6 上传图片、音频

使用 GET 方法提交表单,用户浏览得到文件及其文件名,将文件名传递给处理函数以进行进一步的处理。

代码如下

  1. <!--search部分 start-->
  2. <div style="margin-top:50px;margin-left:50px">
  3. <form action="/a_s" method="GET">
  4. <input type="file" name="uploadfile" id="uploadfile" style="height:35px;color:#68838B" value="Browse..." />
  5. <button type="submit">上传</button>
  6. </form>
  7. </div>
  8. <!--search部分 end-->

2.3 问题及解决

遇到的问题主要是在界面的整合过程中。在运行代码过程中报错“Codec with name ‘Lucene410’does not exist”查阅一些资料后,发现大部分出现此类问题的情况是由于缺少 META-INF/services 这样的文件夹,可以通过将 jar 包中的META-INF复制到src目录的方式解决。但多次尝试之后发现并不可行,再次研究思考报错内容后,认为问题在于 整合界面的同学所使用的 Lucene 的版本为 4.9.0 而写音频搜索的同学的Lucene 的版本为4.10.0,两者版本不一,使用版本低的lucene时会报错。整合界面的同学使用了miniconda安装了4.9.0版本,利用conda update 更新
lucene 后,版本仍为 4.9.0,原因应是 miniconda 中的 lucene 最高版本号是4.9.0,故采取了卸载原来安装的4.9.0版本的lucene,通过lucene PPT中提到的第二种安装方式安装了4.10.0版本的lucene最终解决了这一问题。

三、文本搜索

3.1 库

  • lucene

  • jieba

3.2 原理

使用jieba与lucene对文本内容进行分词以此实现快速搜索

3.3 实现步骤

  • 建立索引

  • 实现搜索文件

四、图片搜索

4.1 库

  • H5py

  • OpenCV

  • Numpy

4.2 原理

利用 SIFT 算法对图片进行特征提取,然后利用 Hash 对图片特征分类建立数据库,在搜索过程中对待检索图片做同样操作以完成匹配。

由于sift特征具有尺度不变性、旋转不变性等良好特征,故图片进行匹配时,两张图片相似度要求并不是很高。

4.3 实现步骤

对爬虫得到的文件中的所有图片用OpenCV的内置sift进行特征提取:

  1. sift=cv2.SIFT()

将每张图片提取的sift特征做归一化处理

  1. r,c=des.shape
  2. argmaximum=np.argmax(des)
  3. argminimum=np.argmin(des)
  4. nmax=argmaximum/c
  5. nmin=argminimum/c
  6. maximum=des[nmax][argmaximum-nmax*c]
  7. minimum=des[nmix][argmiximum-nmix*c]

将归一化处理后的数据使用直方图统计,并将 bins 设为下图所示:

  1. b=[0.07, 0.1, 0.13, 0.16, 0.19, 0.22, 0.25]

以得到的直方图为依据做Hash处理,将得到的矩阵变为字符串储存为一个字典的 key 值,然后再将具有同样 Hash 值得图片信息作为 value 储存入该字典中:

  1. if nt dic.has_key(hashname):
  2. grphash=f.create_group(hashname)
  3. dic[hashname]=grphash
  4. imgname="img"+str(count)
  5. desname="des"+str(count)
  6. grpimg.create_dataset(imgname, data=imgori)
  7. grppath.create_dataset(imgname, data=imgpath)
  8. dic[hashname].create_dataset(imgname, data=des)

检索图片时,对待检索图片进行上述相同操作之后,从H5py中取得同样Hash值的图片信息,然后对这些图片一一使用欧氏距离匹配:

  1. bf=cv2.BFMatcher()
  2. keys=imggroup.keys()
  3. paths=[]
  4. matchimgs[]
  5. for k in keys:
  6. des1=imggroup[k][:]
  7. matches=bf.match(des1, des)
  8. d=0
  9. for j in matches:
  10. d+=j.distance
  11. if d<400
  12. path.append(imgpaths[k].value)
  13. matchimgs.append(imgs[k][:])

4.4 测试

从网上下载各类不同的专辑图片来对库中的图片进行匹配,可以得到:

  • 所使用的 bins 划分是能够比较均匀的将库中的图片划分在各个不同的 Hash值下面的

  • 网上的图片与库中原有的图片的欧式距离是小于400的

  • 库中有4913首歌曲,搜索到对应图片的时间需要3s,但若将搜索嵌入到搜索引擎中,页面上出来结果大致需要6s,猜测与电脑渲染页面速度有关

4.5 对比

与使用颜色空间创建Hash值相对比,该使用sift特征值来Hash的办法可以排除颜色的干扰,也就是即使亮度、灰度有变化,也不会影响图片的匹配。

五、音频搜索

5.1 库

  • ffmpeg

  • wave

  • pyaudio

  • mysqldb

5.2 原理

基于内容的音频检索与基于内容的图片检索在实际实现上面临着类似的问题,包括巨大的信息量以及特征点相对位置对于搜索结果的巨大影响,对于检索的准确度要求以及对于检索速度的要求同样限制着音频检索的实现。这里参照图片检索的相关方式对音频检索进行了尝试,主要思想是在音乐的频谱中提取频率峰值特征点,基于这些特征点对不同的歌曲进行匹配。(主要用lucene解决了音频匹配速度问题)

5.3 实现步骤

5.3.1 音频格式转码

网页爬虫下载后的音频格式为 m4a,为使用 wave 库,需对下载的音频进行转码,将其转为wav格式,这里使用安装在linux中的ffmpeg进行转码,转码后的文件存入与原文件相同的目录下,与原文件只有后缀名不同。

代码:

  1. try:
  2. subprocess.call(["ffmpeg", "-i", origin, newsr])
  3. sss.addsong(newsr2, extra, album)
  4. except:
  5. continue

目录下的文件组成:

5.3.2 提取音频特征

打开步骤1转码后的音频文件,该文件的一个声道是一个一维数组的形式,图形化如图:

这种时间对应幅值的一维数组信息量巨大,难以提取特征信息,故我们利用傅里叶变换将其转化到频域中:

转化的结果:

这样,音频信息转为了其对应的众多频率波形的叠加,而人耳正是靠着所听到的音乐的特定频率序列来区分音频的。在频域信号的众多特征中,我们认为峰值特征是最具代表性的,这种特征代表着这一频率能量最大,它容易提取,同时对于噪声有一定的容忍度。如果只是简单地提取整首歌曲中的一些峰值点,则我们会丧失重要的时间信息,故应当对整段音频进行分段,对每一段音频做傅里叶变换并提取特征峰值点,这样我们在得到特征点的同时也较好地保留了时间的信息。

这里将音频的1s分成40个块,在每一块的4个对应区间中得到4个峰值点,这4个峰值点组成的序列即是在一个块中得到的音频指纹。

5.3.3 数据存储

为了应用的方便,这里将得到的指纹序列存入了mysql数据库中,如图所示:

5.3.4 匹配方法

这里设计了两种匹配方法,dtw以及分词法匹配,考虑到对检验速度的要求,实际应用中选择了后者,但如果要求更高的精度,建议选择前者:

dtw,即动态时间规划,这种方法适于匹配两个不同长度的音频文件,主要思想是找出穿过m*n矩阵的最佳路径,图中上方和左方为我们要匹配的两个不同长度的音频文件,中间为这两个音频文件构造的矩阵,矩阵中每一个格点为两音频中对应点的相似度,中间的线即为找出的最佳路径:

具体实施原理及方法参照:http://blog.csdn.net/zouxy09/article/details/9140207

分词法匹配,这种方法的精度不高,但相比于dtw的匹配所花费的时间要大大减小。主要思想是把音频特征提取过程中提取出的特征点(即四个数字组成的序列)看作是文字检索中的一个词,如将2,40,13,77看作是文字搜索中的一个词,并用空格将这些词隔开,使用lucene中的whitespace分词器对特征点组成的“文章”进行分词以及语法分析,构建索引。这样,对特征点的检索就变成了简单的文字检索。

5.3.5 搜索

主要搜索过程:

  • 从本地上传一个音频文件

  • 对该音频文件进行转码,将其转化为wav格式

  • 对转码后的音频文件提取特征点

  • 用逻辑与连接这些特征点,构建query,与索引结果进行匹配,返回得分最高的50个结果

5.4 测试

测试结果显示,dtw将测试音频与库中的音频进行匹配时平均每一首歌需花费1.5s,这表明当库的容量非常大时其所消耗的时间将是难以想象的,因此,我们选择用lucene的分词系统对音频进行过滤。最终测试的结果是,其中百分之八十的正解将会出现在lucene打分结果的前五名中,有大概百分之15的正解将会出现在5至40名中,只有极少数的结果会出现在40名之后(出现这种现象的主要原因是不同的音频可能有相同的特征点,并且一些音频中含有的某种特征点的数量可能比正确结果的数量更大),故这种方法的精度可能不如dtw,但在一定程度上可以满足检索的基本要求。

上传的附件 cloud_download 基于Python的图片及音频搜索引擎.7z ( 1.84mb, 1次下载 )
error_outline 下载需要11点积分

发送私信

我是自己的太阳,无需仰仗谁的光芒

12
文章数
26
评论数
最近文章
eject