Shazam音乐检索算法原理及实现

news2024/11/19 15:35:20

 

算法基本流程如下: 

1. 采集音乐库

 2. 音乐指纹采集

3. 采用局部最大值作为特征点

 4. 将临近的特征点进行组合形成特征点对

5. 对每个特征点对进行hash编码

编码过程:将f1和f2进行10bit量化,其余bit用来存储时间偏移合集形成32bit的hash码

Hash = f1|f2<<10|diff_t<<20,存储信息(t1,Hash)

实现:

import numpy as np
import librosa
from scipy import signal
import pickle
import os
fix_rate = 16000 
win_length_seconds = 0.5
frequency_bits = 10
num_peaks = 15

# 构造歌曲名与歌曲id之间的映射字典
def song_collect(base_path):
    index = 0
    dic_idx2song = {}
    for roots, dirs, files in os.walk(base_path):
        for file in files:
            if file.endswith(('.mp3', '.wav')):
                file_song = os.path.join(roots, file)
                dic_idx2song[index] = file_song
                index += 1
    return dic_idx2song

# 提取局部最大特征点
def collect_map(y, fs, win_length_seconds=0.5, num_peaks=15):
    win_length = int(win_length_seconds * fs)
    hop_length = int(win_length // 2)
    S = librosa.stft(y, n_fft=win_length, win_length=win_length, hop_length=hop_length)
    S = np.abs(S) # 获取频谱图
    D, T = np.shape(S)
    
    constellation_map = []  
    for i in range(T):
        spectrum = S[:, i]
        peaks_index, props = signal.find_peaks(spectrum, prominence=0, distance=200)
        # 根据显著性进行排序
        n_peaks= min(num_peaks, len(peaks_index))
        largest_peaks_index = np.argpartition(props['prominences'], -n_peaks)[-n_peaks:]
        for peak_index in peaks_index[largest_peaks_index]:
            frequency = fs / win_length * peak_index
            # 保存局部最大值点的时-频信息
            constellation_map.append([i, frequency])
    return constellation_map
        

# 进行Hash编码
def create_hash(constellation_map, fs, frequency_bits=10, song_id=None):
    upper_frequency = fs / 2
    hashes = {}
    for idx, (time, freq) in enumerate(constellation_map):
        for other_time, other_freq in constellation_map[idx: idx + 100]: # 从邻近的100个点中找点对
            diff = int(other_time - time)
            if diff <= 1 or diff > 10: # 在一定时间范围内找点对
                continue
            freq_binned = int(freq / upper_frequency * (2 ** frequency_bits))
            other_freq_binned = int(other_freq / upper_frequency * (2 ** frequency_bits))
            hash = int(freq_binned) | (int(other_freq) << 10) | (int(diff) << 20)
            hashes[hash] = (time, song_id)
    return hashes

特征提取:feature_collect.py

# 获取数据库中所有音乐
path_music = 'data'
current_path = os.getcwd()
path_songs = os.path.join(current_path, path_music)
dic_idx2song = song_collect(path_songs)

# 对每条音乐进行特征提取
database = {}
for song_id in dic_idx2song.keys():
    file = dic_idx2song[song_id]
    print("collect info of file", file)
    
    # 读取音乐
    y, fs = librosa.load(file, sr=fix_rate)
    
    # 提取特征对
    constellation_map = collect_map(y, fs, win_length_seconds=win_length_seconds, num_peaks=num_peaks)
    
    # 获取hash值
    hashes = create_hash(constellation_map, fs, frequency_bits=frequency_bits, song_id=song_id)
    
    # 把hash信息填充入数据库
    for hash, time_index_pair in hashes.items():
        if hash not in database:
            database[hash] = []
        database[hash].append(time_index_pair)
        
# 对数据进行保存
with open('database.pickle', 'wb') as db:
    pickle.dump(database, db, pickle.HIGHEST_PROTOCOL)
with open('song_index.pickle', 'wb') as songs:
    pickle.dump(dic_idx2song, songs, pickle.HIGHEST_PROTOCOL)
# 加载数据库
database = pickle.load(open('database.pickle', 'rb'))
dic_idx2song = pickle.load(open('song_index.pickle', 'rb'))
print(len(database))

# 检索过程
def getscores(y, fs, database):
    # 对检索语音提取hash
    constellation_map = collect_map(y, fs)
    hashes = create_hash(constellation_map, fs, frequency_bits=10, song_id=None)
    
    # 获取与数据库中每首歌的hash匹配
    matches_per_song = {}
    for hash, (sample_time, _) in hashes.items():
        if hash in database:
            maching_occurences = database[hash]
            for source_time, song_index in maching_occurences:
                if song_index not in matches_per_song:
                    matches_per_song[song_index] = []
                matches_per_song[song_index].append((hash, sample_time, source_time))
    scores = {}
    # 对于匹配的hash,计算测试样本时间和数据库中样本时间的偏差
    for song_index, matches in matches_per_song.items():
#         scores[song_index] = len(matches)
        song_scores_by_offset = {}
        # 对相同的时间偏差进行累计
        for hash, sample_time, source_time in matches:
            delta = source_time - sample_time
            if delta not in song_scores_by_offset:
                song_scores_by_offset[delta] = 0
            song_scores_by_offset[delta] += 1
            
        # 计算每条歌曲的最大累计偏差
        max = (0, 0)
        for offset, score in song_scores_by_offset.items():
            if score > max[1]:
                max = (offset, score)
        scores[song_index] = max
    scores = sorted(scores.items(), key=lambda x:x[1][1], reverse=True)
    return scores

音乐检索:music_research.py

import threading
from playsound import playsound

def cycle(path):
    while 1:
        playsound(path)
def play(path, cyc=False):
    if cyc:
        cycle(path)
    else:
        playsound(path)

path = 'test_music/record4.wav'
y, fs = librosa.load(path, sr=fix_rate)
# 播放待检索音频
music = threading.Thread(target=play, args=(path,))
music.start()

# 检索打分
scores = getscores(y, fs, database)

# 打印检索信息
for k, v in scores:
    file = dic_idx2song[k]
    name = os.path.split(file)[-1]
    # print("%s :%d"%(name, v))
    print("%s: %d: %d"%(name, v[0], v[1]))
   
# 打印结果
if len(scores) > 0 and scores[0][1][1] > 50:
    print("检索结果为:", os.path.split(dic_idx2song[scores[0][0]])[-1])
else:
    print("没有搜索到该音乐")

麦克风录音识别音乐:

import pyaudio
import wave

RATE = 48000 # 采样率
CHUNK = 1024 # 帧大小
record_seconds = 10 # 录音时长s
CHANNWLS = 2 # 通道数

# 创建pyaudio流
audio = pyaudio.PyAudio()

stream = audio.open(format=pyaudio.paInt16, # 使用量化位数16位
                   channels=CHANNWLS, # 输入声道数目
                   rate=RATE, # 采样率
                   input=True, # 打开输入流
                   frames_per_buffer=CHUNK) # 缓冲区大小

frames = [] # 存放录制的数据
# 开始录音
print('录音中。。。')
for i in range(0, int(RATE / CHUNK * record_seconds)):
    # 从麦克风读取数据流
    data = stream.read(CHUNK)
    # 将数据追加到列表中
    frames.append(data)

# 停止录音,关闭输入流
stream.stop_stream()
stream.close()
audio.terminate()

# 将录音数据写入wav文件中
with wave.open('test_music/test.wav', 'wb') as wf:
    wf.setnchannels(CHANNWLS)
    wf.setsampwidth(audio.get_sample_size(pyaudio.paInt16))
    wf.setframerate(RATE)
    wf.writeframes(b''.join(frames))
    
# 打开录音文件
path = 'test_music/test.wav'
y, fs = librosa.load(path, sr=fix_rate)

# 线程播放待检索音频
# music = threading.Thread(target=play, args=(path,))
# music.start()

# 音乐检索
print('检索中。。。')
scores = getscores(y, fix_rate, database)

# 打印检索信息
# for k, v in scores:
#     file = dic_idx2song[k]
#     name = os.path.split(file)[-1]
#     # print("%s :%d"%(name, v))
#     print("%s: %d: %d"%(name, v[0], v[1]))

   
# 打印结果
if len(scores) > 0 and scores[0][1][1] > 50:
    print("检索结果为:", os.path.split(dic_idx2song[scores[0][0]])[-1])
else:
    print("没有搜索到该音乐")

参考:音乐检索-Shazam算法原理_哔哩哔哩_bilibili

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1001092.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用LambdaQueryWrapper再也不担心字段拼写错误了 [MyBatis-Plus系列] - 第485篇

历史文章&#xff08;文章累计480&#xff09; 《国内最全的Spring Boot系列之一》 《国内最全的Spring Boot系列之二》 《国内最全的Spring Boot系列之三》 《国内最全的Spring Boot系列之四》 《国内最全的Spring Boot系列之五》 《国内最全的Spring Boot系列之六》 E…

数据结构--二叉树-堆(1)

文章目录 树概念相关的基本概念树的表示 二叉树概念特殊二叉树性质 堆二叉树的顺序结构堆的概念 堆的实现初始化数组初始化为堆向上调整向下调整插入删除打印、摧毁、判空、获取堆顶数据验证 堆的应用堆排序TopK问题 树 概念 树是一种常见的非线性的数据结构&#xff0c;&…

Docker 入门:如何打包、部署并运行你的应用

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f405;&#x1f43e;猫头虎建议程序员必备技术栈一览表&#x1f4d6;&#xff1a; &#x1f6e0;️ 全栈技术 Full Stack: &#x1f4da…

cmake构建和编译

什么是CMake&#xff1f; CMake本身是一个工具集&#xff0c;由五个可执行的程序组成&#xff1a;cmake、ctest、cpack、cmake-gui和ccmake&#xff0c;其中cmake可以说是出镜率最高的明星级别程序了&#xff0c;它用于在构建项目的第一步&#xff0c;进行项目的配置、生成和构…

SQL4 查询结果限制返回行数

描述 题目&#xff1a;现在运营只需要查看前2个用户明细设备ID数据&#xff0c;请你从用户信息表 user_profile 中取出相应结果。 示例&#xff1a; iddevice_idgenderageuniversityprovince12138male21北京大学Beijing23214male复旦大学Shanghai36543female20北京大学Beijin…

健身房预约小程序开发全攻略

随着健身行业的快速发展&#xff0c;健身房预约小程序成为了方便、快捷的预约方式&#xff0c;为健身爱好者提供了更好的服务。本文将介绍如何开发健身房预约小程序。 第一步&#xff1a;登录小程序制作平台 首先&#xff0c;进入后台&#xff0c;点击【轻应用小程序】中的【去…

MATLAB入门-数据的导入和导出

MATLAB入门-数据的导入和导出 注&#xff1a;本篇文章是课程学习笔记&#xff0c;课程链接为&#xff1a;头歌 常见的几个导入数据的方法 load函数 load函数专门用于引入MATLAB的.mat格式数据&#xff0c;十分的简单方便。 例如&#xff1a;一个-ASCII编码形式存储的数据文件…

usb学习笔记

框架 usb 驱动是基于usb core 的&#xff0c;设备插上之后&#xff0c;host 层自然会进行识别&#xff0c;设备驱动通过core层的接口操作设备&#xff0c;而不用直接面对usb硬件。对于应用层需要封装成一个usb 的设备。 驱动是基于urb 数据进行操作的。 49 static void usb_mo…

关于this

参考阮一峰老师对于this的原理理解 一&#xff1a;this的定义 在严格模式下和非严格模式下&#xff0c;this的在全局中会有差别 来自阮一峰老师对于this的由来的理解 由于函数可以在不同的运行环境中运行&#xff0c;所以需要一种机制&#xff0c;能够在函数的内部获取当前运…

大数据课程L6——网站流量项目的SparkStreaming

文章作者邮箱:yugongshiye@sina.cn 地址:广东惠州 ▲ 本章节目的 ⚪ 了解网站流量项目的SparkStreaming概述; ⚪ 掌握网站流量项目的SparkStreaming实现 Wordcount 底层流程; ⚪ 掌握网站流量项目的SparkStreaming实现历史批次的累积处理; ⚪ 掌握网站流…

Vue中使用Google的reCAPTCHA v3人机校验-demo

reCAPTCHA Google 提供了 reCAPTCHA&#xff08;v3 和 v2&#xff09;和 reCAPTCHA Enterprise&#xff0c;帮助您保护网站免受欺诈活动、垃圾内容和滥用行为的侵扰 reCAPTCHA v3 「所有的頁面都會有 reCaptcha 的追蹤功能」 不需做任何事&#xff0c;v3會針對使用者行為&#…

IDEA编写Java编程代码提示插件

网址&#xff1a; aiXcoder idea软件中使用代码提示&#xff1a; aixcoder

C语言实现扫雷小游戏

1.首先扫雷游戏要存储布置好的雷信息&#xff0c;需要一个二维数组 不是雷放* 雷&#xff1a;# 不是雷&#xff1a;0 雷&#xff1a;1 2. 给2个二维数组 9*9 一个存放雷的信息&#xff0c;一个存放布置好雷的信息 3.为了防止在统计坐标周围的…

回归与聚类算法系列④:岭回归

目录 1. 背景 2. 数学模型 3. 特点 4. 应用领域 5. 岭回归与其他正则化方法的比较 6、API 7、代码 8、总结 &#x1f343;作者介绍&#xff1a;双非本科大三网络工程专业在读&#xff0c;阿里云专家博主&#xff0c;专注于Java领域学习&#xff0c;擅长web应用开发、数…

OLED透明屏控制:引领未来交互技术的创新突破

OLED透明屏控制技术作为一项颠覆性的显示技术&#xff0c;正逐渐改变我们与屏幕互动的方式。 其高透明度、触摸和手势交互、高画质和图像显示效果等特点&#xff0c;为用户提供了更智能、便捷的交互体验&#xff0c;同时也在各行各业中展现出无限的应用可能性。 在此&#xf…

【算法】反悔贪心

文章目录 反悔贪心力扣题目列表630. 课程表 III871. 最低加油次数LCP 30. 魔塔游戏2813. 子序列最大优雅度 洛谷题目列表P2949 [USACO09OPEN] Work Scheduling GP1209 [USACO1.3] 修理牛棚 Barn RepairP2123 皇后游戏&#xff08;&#x1f6b9;省选/NOI− TODO&#xff09; 相关…

Mybatis---resultMap详解

目录 一、resultMap介绍 二、自定义映射关系 一、resultMap介绍 该标签的作用是自定义映射关系。 Mybatis可以将数据库结果封装到对象中&#xff0c;是因为结果集和对象属性名相同&#xff08;也就是你写的pojo类型的参数名和数据库的字段名相同&#xff09; 但是如果当他们不…

【计算机网络】 TCP——四次挥手

文章目录 流程考点 流程 主动方打算关闭连接&#xff0c;此时会发送一个TCP首部FIN标志位被置为1的报文&#xff0c;也即FIN报文&#xff0c;之后主动方进入FIN_WAIT_1状态。被动方收到该报文后&#xff0c;就向主动方发送ACK应答报文&#xff0c;接着被动方进入CLOSE_WAIT状态…

HTML + CSS 实践1

Hello,小伙伴们 这是一个十分精美的网站&#xff0c;可以拿着它去制作一些个人网站 index.html <!DOCTYPE html> <meta charset"utf-8"> <html> <head> <title>首页</title> </head> <style type"text/css"…

【业务功能篇105】 微服务-springcloud-springboot-电商订单模块--秒杀服务-定时任务【上篇】

秒杀服务 一、商品上架 秒杀活动的结构图 通过定时任务触发&#xff1a; 定时任务由spring提供&#xff0c;需要通过注解开启&#xff0c;这里通过定义一个配置类&#xff0c;注入spring,对其配置类进行相应的注解&#xff0c;当然也可以注解放在我们的服务启动类上cron表达…