电路笔记(信号):相关匹配/模板匹配 (Correlation/Template Matching) 解码 + python实现示例

相关性解码

  • 相关匹配/模板匹配 (Correlation/Template Matching) 解码方法特别适用于信号质量较差(有噪声、失真)或需要高可靠性的场景。其核心思想是:将接收到的波形片段与预先定义的“理想”波形模板进行对比,计算它们的相似度(相关值),并选择相似度最高的模板作为解码结果。它的优势在低信噪比、高可靠性要求的场景下尤为突出。随着计算能力的提升,这种经典的信号处理方法在现代智能系统(如自动驾驶、智能医疗设备)中依然扮演着不可或缺的角色:
    • 数字调制解调
      • BPSK/QPSK 解调:接收端使用本地生成的载波和已知的符号波形作为模板,与接收到的信号做相关,以判决发送的是 0 还是 1 或哪个相位。
      • 扩频通信 (如 CDMA):利用伪随机码(PN码)作为模板进行相关解扩。只有使用相同码的接收机才能从宽带噪声中“拉出”有用信号,实现多址接入和抗干扰。
    • 同步字检测
      • 在数据帧开头插入特定的“同步头”(如 0101... 或巴克码)。接收端用此同步头作为模板,在接收流中滑动匹配,一旦找到高相关值,即确认帧起始位置,完成帧同步。
    • 雷达通信一体化:在通信信号中嵌入雷达探测功能,利用相关性检测目标回波。
优点缺点
抗噪声能力强:即使信号有失真,只要整体形状匹配,仍能正确解码。计算量大:每个位都需要 2 次点积运算(TEMPLATE_LEN 次乘加),对 CPU 要求高。
可靠性高:相比简单阈值法,误码率更低。需要预知采样率和位率:模板依赖于固定的 SAMPLE_RATEBIT_RATE
易于实现:逻辑清晰,代码结构简单。内存占用:需要存储模板数组和中间结果存储。
可扩展:可轻松添加同步头、状态字等更复杂的模板。对时钟漂移敏感:如果实际位周期与模板不匹配,性能下降。

实现逻辑

  • 模板生成(01 的曼彻斯特编码波形)
  • 滑动窗口相关匹配
  • 同步头检测(3 位同步头)
  • 帧解析
  • 奇校验验证
  • 完整的解码流程演示

python实现

在这里插入图片描述

import numpy as np
import matplotlib.pyplot as plt
from typing import List, Tuple, Optional
from scipy import signal

# ========================
# 配置参数
# ========================
# 采样信息
SAMPLE_RATE = 10_000_000      # 10 MHz 采样率
BIT_RATE = 1_000_000          # 1 Mbps (MIL-STD-1553B)
HALF_BIT_SAMPLES = SAMPLE_RATE // BIT_RATE // 2  # 半位采样点数   5
BIT_SAMPLES = 2 * HALF_BIT_SAMPLES               # 一位总采样点数 10

# 信号电平(模拟ADC值)
HIGH_LEVEL = 10000
LOW_LEVEL = -10000

# 帧结构   
ALL_DATA_BITS = 17  # 16个数据位和一个校验位
SYNC_LENGTH = 15 # 同步头

# ========================
# 模板生成
# ========================
def generate_manchester_templates() -> Tuple[np.ndarray, np.ndarray]:
    """
    生成 MIL-STD-1553B 曼彻斯特编码模板
    - '1': 正脉冲 + 负脉冲  -> [HIGH, LOW]
    - '0': 负脉冲 + 正脉冲  -> [LOW, HIGH]
    """
    template_1 = np.concatenate([
        np.full(HALF_BIT_SAMPLES, HIGH_LEVEL),
        np.full(HALF_BIT_SAMPLES, LOW_LEVEL)
    ]) # []
    
    template_0 = np.concatenate([
        np.full(HALF_BIT_SAMPLES, LOW_LEVEL),
        np.full(HALF_BIT_SAMPLES, HIGH_LEVEL)
    ])
    
    return template_0, template_1

# 生成同步头模板
def generate_sync_template(template_1: np.ndarray, num_sync_bits: int = 3) -> np.ndarray:
    """生成同步头模板"""
    return np.concatenate([np.zeros(SYNC_LENGTH, dtype=int), np.ones(SYNC_LENGTH, dtype=int)]) # 共30位,15个低位+15个高位。

# ========================
# 相关匹配查找同步头
# ========================
def find_sync_start_correlation(waveform: np.ndarray, 
                                length: int, 
                                sync_template: np.ndarray, 
                                threshold: float = 75000.0) -> int:
    """
    使用互相关查找同步头位置,仅当相关值超过阈值且为局部最大值时才认为是有效同步头。
    
    参数:
        waveform (np.ndarray): 输入的波形数据
        length (int): 搜索范围长度(从 waveform 开头搜索)
        sync_template (np.ndarray): 同步头模板
        threshold (float): 相关值阈值
    
    返回:
        int: 第一个有效同步头的起始位置(索引),未找到返回 -1
    """
    # 截取搜索范围
    search_wave = waveform[:length]
    
    # 计算互相关,mode='valid' 表示只有完全重叠的部分
    corr = np.correlate(search_wave, sync_template, mode='valid')
    
    # 如果相关结果为空,直接返回-1
    if len(corr) == 0:
        return -1
    
    # 存储局部最大值的位置
    peak_positions = []
    
    # 遍历相关值,寻找局部最大值
    for i in range(1, len(corr) - 1):  # 忽略边界
        if corr[i] > threshold:  # 先判断是否超过阈值
            # 判断是否为局部最大值(峰值)
            if corr[i] > corr[i-1] and corr[i] > corr[i+1]:
                peak_positions.append(i)
    
    # 如果没有找到任何峰值,返回 -1
    if not peak_positions:
        return -1
    
    # 返回第一个检测到的峰值位置(对应 waveform 的起始索引)
    # 注意:corr[i] 对应 waveform[i] 到 waveform[i + len(template)-1]
    return peak_positions[0]

# ========================
# 相关匹配解码单个位
# ========================
def match_bit(signal_segment: np.ndarray, template_0: np.ndarray, template_1: np.ndarray) -> int:
    """
    使用点积相关匹配判断是 0 还是 1
    返回: 0, 1, 或 -1(无效)
    """
    if len(signal_segment) != len(template_0):
        raise ValueError("Signal segment length does not match template length")
    
    corr_0 = np.dot(signal_segment, template_0)
    corr_1 = np.dot(signal_segment, template_1)
    
    if corr_0 > corr_1:
        return 0
    elif corr_1 > corr_0:
        return 1
    else:
        return -1  # 平局,视为错误
    
def match_bit_r_corr(signal_segment: np.ndarray, template_0: np.ndarray, template_1: np.ndarray) -> int:
    """
    使用点积相关匹配判断是 0 还是 1
    返回: 0, 1, 或 -1(无效)
    """
    if len(signal_segment) != len(template_0):
        raise ValueError("Signal segment length does not match template length")
    
    corr_0 = np.dot(signal_segment, template_0) # -1*(signal_segment[0]+signal_segment[1]+signal_segment[2]+signal_segment[3]+signal_segment[4])+(signal_segment[5]+signal_segment[6]+signal_segment[7]+signal_segment[8]+signal_segment[9])
    corr_1 = np.dot(signal_segment, template_1) # (signal_segment[0]+signal_segment[1]+signal_segment[2]+signal_segment[3]+signal_segment[4])-1*(signal_segment[5]+signal_segment[6]+signal_segment[7]+signal_segment[8]+signal_segment[9])

    if corr_0 > corr_1:
        return 0, corr_0
    elif corr_1 > corr_0:
        return 1, corr_1
    else:
        return -1,0  # 平局,视为错误

# ========================
# 滑动窗口法寻找极值位
# ========================
def find_peaks_sliding_window(data, window_size=11, threshold=0.3):
    """
    使用滑动窗口检测局部极值
    
    参数:
    data: 输入时间序列
    window_size: 滑动窗口大小(必须为奇数)
    threshold: 相对幅度阈值
    
    返回:
    positive_peaks: 正峰值位置
    negative_peaks: 负峰值位置
    """
    if window_size % 2 == 0:
        window_size += 1  # 确保窗口大小为奇数
    
    half_window = window_size // 2
    positive_peaks = []
    negative_peaks = []
    
    # 计算全局统计量用于阈值
    data_range = np.max(data) - np.min(data)
    abs_threshold = threshold * data_range
    
    for i in range(half_window, len(data) - half_window):
        window = data[i - half_window:i + half_window + 1]
        center_value = data[i]
        
        # 检查是否是正峰值
        if center_value == np.max(window) and center_value > np.min(window) + abs_threshold:
            # 确保是严格的局部最大值
            is_peak = True
            for j in range(1, half_window + 1):
                if data[i - j] >= center_value or data[i + j] >= center_value:
                    is_peak = False
                    break
            if is_peak:
                positive_peaks.append(i)
        
        # 检查是否是负峰值
        elif center_value == np.min(window) and center_value < np.max(window) - abs_threshold:
            # 确保是严格的局部最小值
            is_valley = True
            for j in range(1, half_window + 1):
                if data[i - j] <= center_value or data[i + j] <= center_value:
                    is_valley = False
                    break
            if is_valley:
                negative_peaks.append(i)
    
    return np.array(positive_peaks), np.array(negative_peaks)

# ========================
# 滑动窗口解码位流
# ========================
def decode_bits_from_waveform(waveform: np.ndarray, 
                             template_0: np.ndarray, 
                             template_1: np.ndarray) -> List[int]:
    """从波形中解码出位流"""
    bits = []
    i = 0
    j = 0
    while (i <= len(waveform) - BIT_SAMPLES) & (j<17):
        segment = waveform[i:i + BIT_SAMPLES] 
        bit,corr = match_bit_r_corr(segment, template_0, template_1) # match_bit时平局,视为错误,返回-1
        if bit != -1:
            bits.append(bit)
        else:
            bits.append(-1)  # 标记错误位
        i += BIT_SAMPLES  # 移动一个位
        j += 1            # 解码的数据个数加一 
    return bits

# ========================
# 数据校验  如果符合偶数校验则返回True,否则返回False
# ========================
def even_parity_check(decoded_bits):
    """
    对给定的二进制列表进行偶数奇偶校验。
    
    参数:
        decoded_bits (list): 二进制位列表(0或1),最后一位被认为是奇偶校验位
    
    返回:
        bool: 如果符合偶数奇偶校验则返回True,否则返回False
    """
    # 计算除了校验位外的所有1的数目
    count_ones = sum(decoded_bits[:-1])
    
    # 判断包括校验位在内的所有1的数目是否为偶数
    return (count_ones + decoded_bits[-1]) % 2 == 0


# ========================
# 绘制数据
# ========================
def plot_data(data):
    plt.figure(figsize=(10, 5)) # 设置图表大小
    plt.plot(data, marker='o') # 绘制数据点,添加标记使数据点更明显
    plt.title('Data Plot') # 添加标题
    plt.xlabel('Index') # X轴标签
    plt.ylabel('Value') # Y轴标签
    plt.grid(True) # 显示网格线

    # 显示图表
    plt.show()
    print(data) 

# 绘制同步头的位置
def plot_data_mark(data,sync_positions):
    """
    绘制数据,并在 sync_positions 指定的位置添加垂直线标记(如同步头位置)

    参数:
        data (list or np.ndarray): 要绘制的数据
        sync_positions (list of int): 需要标记的位置索引列表,例如 [150, 450]
    """
    plt.figure(figsize=(12, 6))  # 设置图表大小
    plt.plot(data, marker='o', markersize=3, linewidth=1, label='Signal')  # 绘制数据

    plt.axvline(x=sync_positions, color='red', linestyle='--', linewidth=2, alpha=0.8)
    plt.axvline(x=sync_positions+30, color='red', linestyle='--', linewidth=2, alpha=0.8)
    plt.title('Data Plot with Sync Markers')  # 添加标题
    plt.xlabel('Sample Index')                # X轴标签
    plt.ylabel('Amplitude')                   # Y轴标签
    plt.grid(True, alpha=0.6)                 # 显示网格线

    # 图例:只显示一次 "Sync" 标签
    handles, labels = plt.gca().get_legend_handles_labels()
    if handles:
        plt.legend()

    # 显示图表
    plt.tight_layout()
    plt.show()

    # 输出数据(可选)
    print("Data:", data)
    print("Sync positions marked:", sync_positions)

# 绘制多个同步头的位置
def plot_data_mark_mut(data,sync_positions):
    """
    绘制数据,并在 sync_positions 指定的位置添加垂直线标记(如同步头位置)

    参数:
        data (list or np.ndarray): 要绘制的数据
        sync_positions (list of int): 需要标记的位置索引列表,例如 [150, 450]
    """
    plt.figure(figsize=(12, 6))  # 设置图表大小
    plt.plot(data, marker='o', markersize=3, linewidth=1, label='Signal')  # 绘制数据
    for pos in sync_positions:
        plt.axvline(x=pos, color='red', linestyle='--', linewidth=2, alpha=0.8)
        plt.axvline(x=pos+30, color='red', linestyle='--', linewidth=2, alpha=0.8)

    plt.title('Data Plot with Sync Markers')  # 添加标题
    plt.xlabel('Sample Index')                # X轴标签
    plt.ylabel('Amplitude')                   # Y轴标签
    plt.grid(True, alpha=0.6)                 # 显示网格线

    # 图例:只显示一次 "Sync" 标签
    handles, labels = plt.gca().get_legend_handles_labels()
    if handles:
        plt.legend()

    # 显示图表
    plt.tight_layout()
    plt.show()

    # 输出数据(可选)
    print("Data:", data)
    print("Sync positions marked:", sync_positions)

def plot_data_mark_mut_dot(data, sync_positions):
    """
    绘制数据,并在 sync_positions 指定的位置添加垂直线标记(如同步头位置)
    在第二个红线后以10为距离添加标记,直到下一个第一条红线为止,标记显示对应index
    """
    plt.figure(figsize=(12, 6))  # 设置图表大小
    plt.plot(data, marker='o', markersize=3, linewidth=1, label='Signal')  # 绘制数据
    
    # 先绘制所有红线
    red_lines = []
    for pos in sync_positions:
        # 第一条红线位置
        first_line = pos
        # 第二条红线位置
        second_line = pos + 30
        plt.axvline(x=first_line, color='red', linestyle='--', linewidth=2, alpha=0.8)
        plt.axvline(x=second_line, color='red', linestyle='--', linewidth=2, alpha=0.8)
        red_lines.append((first_line, second_line))
    
    # 为每个区间添加浮动文本标记
    for i, (first_line, second_line) in enumerate(red_lines):
        # 确定当前区间的结束位置(下一个第一条红线)
        if i < len(red_lines) - 1:
            end_pos = red_lines[i+1][0]  # 下一个第一条红线位置
        else:
            end_pos = len(data) - 1  # 最后一个区间以数据长度为终点
        
        # 从第二条红线后10个位置开始
        start_pos = second_line + 10
        
        # 确保起始位置不超过结束位置
        if start_pos > end_pos:
            continue
        
        # 以10为间隔添加浮动标记(显示index)
        current_pos = start_pos
        while current_pos <= end_pos:
            # 获取当前位置的数据值(用于定位文本)
            data_value = data[current_pos]
            
            # 添加浮动文本标记(显示当前index)
            # 位置:在点的右下方(可通过xytext调整偏移)
            # 仅第一个标记添加图例标签
            label = 'Index Mark' if (i == 0 and current_pos == start_pos) else ""
            if(current_pos in score_dict):
                plt.text(
                    current_pos, data_value,  # 文本锚点位置(对应数据点)
                    f'{score_dict[current_pos]}',         # 显示的文本内容(index值)
                    fontsize=8,               # 文本大小
                    color='blue',             # 文本颜色
                    ha='left', va='bottom',   # 水平/垂直对齐方式
                    bbox=dict(facecolor='white', alpha=0.3, boxstyle='round,pad=0.3'),  # 白色背景框
                    label=label
                )
                # print("----",current_pos)
            current_pos += 10

    plt.title('Data Plot with Sync Markers')  # 添加标题
    plt.xlabel('Sample Index')                # X轴标签
    plt.ylabel('Amplitude')                   # Y轴标签
    plt.grid(True, alpha=0.6)                 # 显示网格线

    # 处理图例,确保只显示唯一标签
    handles, labels = plt.gca().get_legend_handles_labels()
    by_label = dict(zip(labels, handles))
    plt.legend(by_label.values(), by_label.keys())

    # 显示图表
    plt.tight_layout()
    plt.show()

    # 输出数据(可选)
    print("Data:", data)
    print("Sync positions marked:", sync_positions)


# ========================
# 数据滤波
# ========================
def lowpass_filter_scipy(data, cutoff_freq, sampling_rate, filter_type='butter', order=4):
    """
    使用SciPy对NumPy数组进行低通滤波
    
    参数:
    data: numpy数组,输入信号
    cutoff_freq: float, 截止频率 (Hz)
    sampling_rate: float, 采样频率 (Hz)
    filter_type: str, 滤波器类型 ('butter', 'cheby1', 'bessel')
    order: int, 滤波器阶数
    
    返回:
    filtered_data: 滤波后的numpy数组
    """
    # 计算归一化截止频率 (Nyquist频率的倍数)
    nyquist_freq = 0.5 * sampling_rate
    normal_cutoff = cutoff_freq / nyquist_freq
    
    if filter_type == 'butter':
        b, a = signal.butter(order, normal_cutoff, btype='low', analog=False)
    elif filter_type == 'cheby1':
        b, a = signal.cheby1(order, 0.5, normal_cutoff, btype='low', analog=False)
    elif filter_type == 'bessel':
        b, a = signal.bessel(order, normal_cutoff, btype='low', analog=False)
    else:
        raise ValueError("不支持的滤波器类型")
    
    # 应用滤波器
    filtered_data = signal.filtfilt(b, a, data)
    
    return filtered_data

def moving_average_filter(data, window_size):
    """
    使用移动平均进行低通滤波
    
    参数:
    data: numpy数组
    window_size: int, 窗口大小
    
    返回:
    filtered_data: 滤波后的数组
    """
    if window_size <= 1:
        return data.copy()
    
    # 使用卷积实现移动平均
    window = np.ones(window_size) / window_size
    filtered_data = np.convolve(data, window, mode='same')
    
    return filtered_data


res_dict = {}
score_dict = {}

if __name__ == "__main__":
    sync_head_num = 0 # 查找到同步头的个数
    paritiy_num = 0
    paritiy_list = []
    not_par_num = 0
    not_par_list = []

    data = np.loadtxt('***.dat', skiprows=1) # 加载数据文件,跳过第一行
    # 预处理数据
    data = -1*data 
    print(data)
    startindex = 0
    data = data[startindex:startindex+49000] # 100000
    # 0.预处理
    # data = moving_average_filter(data,2)
    data = lowpass_filter_scipy(data,40000,100000)
    # 1. 生成模板
    template_0, template_1 = generate_manchester_templates()
    sync_template = generate_sync_template(template_1, 3)
    
    # 2. 波形数据
    test_wave = data
    # plot_data(test_wave)
    data_len = data.size
    index = 0
    sync_list = [] # 标记同步头位置的列表
    while(index<(data_len-1000)): # 留1000个缓冲空间
        # 查找同步头
        sync_positions =  -1
        window_size = 300
        window_overlap = 100
        while(sync_positions ==-1):
            index = index + window_size - window_overlap # 窗口重叠
            segment = test_wave[index:-1]
            if(segment.size != 0):
                sync_positions = find_sync_start_correlation(segment, window_size, sync_template, threshold= 55000.0)
            else:
                print("已遍历所有数据")
                break

        if(segment.size != 0):   
            sync_head_num = sync_head_num +1
            print("第一个同步头的位置",index+sync_positions)    

            # plot_data_mark(test_wave[0:-1],index+sync_positions) # python 不支持函数重载
            sync_list.append(index+sync_positions)

            # 3. 解码位流
            data = test_wave[index+sync_positions+30:index+sync_positions+30+170] 
            print("+++",index+sync_positions+30)
            decoded_bits = decode_bits_from_waveform(data, template_0, template_1) # 解完17个就返回
            res_dict[index+sync_positions] = decoded_bits
            value = index+sync_positions # +30+10+0*10
            # score_dict[value] = r_list

            # 4. 对list数据decoded_bits进行奇偶校验
            is_valid = even_parity_check(decoded_bits)
            print(f"奇偶校验{'通过' if is_valid else '未通过'}")
            if is_valid:
                paritiy_num = paritiy_num + 1
                paritiy_list.append(index+sync_positions)
            else:
                not_par_num = not_par_num + 1
                not_par_list.append(index+sync_positions)

    print("-------------统计信息------------")
    print("找到同步头",sync_head_num)
    print("奇偶校验未通过个数",paritiy_num,paritiy_list)
    print("奇偶校验通过个数",not_par_num,not_par_list)
    print("---------------------------------")
    print("-------------绘制同步头位置------------")
    plot_data_mark_mut_dot(test_wave,sync_list)
    print("---------------------------------")

互相关解码

import numpy as np
import matplotlib.pyplot as plt
from typing import List, Tuple, Optional
from scipy import signal

# ========================
# 配置参数
# ========================
# 采样信息
SAMPLE_RATE = 10_000_000      # 10 MHz 采样率
BIT_RATE = 1_000_000          # 1 Mbps (MIL-STD-1553B)
HALF_BIT_SAMPLES = SAMPLE_RATE // BIT_RATE // 2  # 半位采样点数   5
BIT_SAMPLES = 2 * HALF_BIT_SAMPLES               # 一位总采样点数 10

# 信号电平(模拟ADC值)
HIGH_LEVEL = 10000
LOW_LEVEL = -10000

# 帧结构   
ALL_DATA_BITS = 17  # 16个数据位和一个校验位
SYNC_LENGTH = 15 # 同步头

# ========================
# 模板生成
# ========================
def generate_manchester_templates() -> Tuple[np.ndarray, np.ndarray]:
    """
    生成 MIL-STD-1553B 曼彻斯特编码模板
    - '1': 正脉冲 + 负脉冲  -> [HIGH, LOW]
    - '0': 负脉冲 + 正脉冲  -> [LOW, HIGH]
    """
    template_1 = np.concatenate([
        np.full(HALF_BIT_SAMPLES, HIGH_LEVEL),
        np.full(HALF_BIT_SAMPLES, LOW_LEVEL)
    ]) # []
    
    template_0 = np.concatenate([
        np.full(HALF_BIT_SAMPLES, LOW_LEVEL),
        np.full(HALF_BIT_SAMPLES, HIGH_LEVEL)
    ])
    
    return template_0, template_1

# 生成同步头模板
def generate_sync_template(template_1: np.ndarray, num_sync_bits: int = 3) -> np.ndarray:
    """生成同步头模板"""
    return np.concatenate([np.zeros(SYNC_LENGTH, dtype=int), np.ones(SYNC_LENGTH, dtype=int)]) # 共30位,15个低位+15个高位。

# ========================
# 相关匹配查找同步头
# ========================
def find_sync_start_correlation(waveform: np.ndarray, 
                                length: int, 
                                sync_template: np.ndarray, 
                                threshold: float = 75000.0) -> int:
    """
    使用互相关查找同步头位置,仅当相关值超过阈值且为局部最大值时才认为是有效同步头。
    
    参数:
        waveform (np.ndarray): 输入的波形数据
        length (int): 搜索范围长度(从 waveform 开头搜索)
        sync_template (np.ndarray): 同步头模板
        threshold (float): 相关值阈值
    
    返回:
        int: 第一个有效同步头的起始位置(索引),未找到返回 -1
    """
    # 截取搜索范围
    search_wave = waveform[:length]
    
    # 计算互相关,mode='valid' 表示只有完全重叠的部分
    corr = np.correlate(search_wave, sync_template, mode='valid')
    
    # 如果相关结果为空,直接返回-1
    if len(corr) == 0:
        return -1
    
    # 存储局部最大值的位置
    peak_positions = []
    
    # 遍历相关值,寻找局部最大值
    for i in range(1, len(corr) - 1):  # 忽略边界
        if corr[i] > threshold:  # 先判断是否超过阈值
            # 判断是否为局部最大值(峰值)
            if corr[i] > corr[i-1] and corr[i] > corr[i+1]:
                peak_positions.append(i)
    
    # 如果没有找到任何峰值,返回 -1
    if not peak_positions:
        return -1
    
    # 返回第一个检测到的峰值位置(对应 waveform 的起始索引)
    # 注意:corr[i] 对应 waveform[i] 到 waveform[i + len(template)-1]
    return peak_positions[0]

# ========================
# 相关匹配解码单个位
# ========================
def match_bit(signal_segment: np.ndarray, template_0: np.ndarray, template_1: np.ndarray) -> int:
    """
    使用点积相关匹配判断是 0 还是 1
    返回: 0, 1, 或 -1(无效)
    """
    if len(signal_segment) != len(template_0):
        raise ValueError("Signal segment length does not match template length")
    
    corr_0 = np.dot(signal_segment, template_0)
    corr_1 = np.dot(signal_segment, template_1)
    
    if corr_0 > corr_1:
        return 0
    elif corr_1 > corr_0:
        return 1
    else:
        return -1  # 平局,视为错误
    
def match_bit_r_corr(signal_segment: np.ndarray, template_0: np.ndarray, template_1: np.ndarray) -> int:
    """
    使用点积相关匹配判断是 0 还是 1
    返回: 0, 1, 或 -1(无效)
    """
    if len(signal_segment) != len(template_0):
        raise ValueError("Signal segment length does not match template length")
    
    corr_0 = np.dot(signal_segment, template_0) # -1*(signal_segment[0]+signal_segment[1]+signal_segment[2]+signal_segment[3]+signal_segment[4])+(signal_segment[5]+signal_segment[6]+signal_segment[7]+signal_segment[8]+signal_segment[9])
    corr_1 = np.dot(signal_segment, template_1) # (signal_segment[0]+signal_segment[1]+signal_segment[2]+signal_segment[3]+signal_segment[4])-1*(signal_segment[5]+signal_segment[6]+signal_segment[7]+signal_segment[8]+signal_segment[9])

    if corr_0 > corr_1:
        return 0, corr_0
    elif corr_1 > corr_0:
        return 1, corr_1
    else:
        return -1,0  # 平局,视为错误

# ========================
# 滑动窗口法寻找极值位
# ========================
def find_peaks_sliding_window(data, window_size=11, threshold=0.3):
    """
    使用滑动窗口检测局部极值
    
    参数:
    data: 输入时间序列
    window_size: 滑动窗口大小(必须为奇数)
    threshold: 相对幅度阈值
    
    返回:
    positive_peaks: 正峰值位置
    negative_peaks: 负峰值位置
    """
    if window_size % 2 == 0:
        window_size += 1  # 确保窗口大小为奇数
    
    half_window = window_size // 2
    positive_peaks = []
    negative_peaks = []
    
    # 计算全局统计量用于阈值
    data_range = np.max(data) - np.min(data)
    abs_threshold = threshold * data_range
    
    for i in range(half_window, len(data) - half_window):
        window = data[i - half_window:i + half_window + 1]
        center_value = data[i]
        
        # 检查是否是正峰值
        if center_value == np.max(window) and center_value > np.min(window) + abs_threshold:
            # 确保是严格的局部最大值
            is_peak = True
            for j in range(1, half_window + 1):
                if data[i - j] >= center_value or data[i + j] >= center_value:
                    is_peak = False
                    break
            if is_peak:
                positive_peaks.append(i)
        
        # 检查是否是负峰值
        elif center_value == np.min(window) and center_value < np.max(window) - abs_threshold:
            # 确保是严格的局部最小值
            is_valley = True
            for j in range(1, half_window + 1):
                if data[i - j] <= center_value or data[i + j] <= center_value:
                    is_valley = False
                    break
            if is_valley:
                negative_peaks.append(i)
    
    return np.array(positive_peaks), np.array(negative_peaks)

def find_alternating_peaks(time_series):
    """
    查找时间序列中交替出现的正负峰值,要求正负峰值间数据单调
    
    参数:
        time_series: 一维数组或列表,代表输入的时间序列
        
    返回:
        peaks: 列表,包含检测到的峰值信息,每个元素为元组(位置索引, 峰值类型, 峰值大小)
               其中峰值类型为'positive'(正峰)或'negative'(负峰)
    """
    if len(time_series) < 3:
        return []  # 太短的序列无法形成峰值
    
    # 转换为numpy数组便于处理
    ts = np.asarray(time_series)
    peaks = []
    zero_peak_type = 'positive'
    if ts[0]<0:
        zero_peak_type = 'negative'
    peaks.append((0, zero_peak_type, ts[0]))
    n = len(ts)
    i = 1  # 从第二个元素开始检查

    next_expected_type = 'negative' if zero_peak_type == 'positive' else 'positive'
    while i < n - 1:
        # 检查是否符合预期类型的峰值
        is_positive_peak = (ts[i] > ts[i-1] and ts[i] > ts[i+1])
        is_negative_peak = (ts[i] < ts[i-1] and ts[i] < ts[i+1])
        if (i>=3) & (i<=158):
            is_positive_peak = (ts[i] > ts[i-1] and ts[i] > ts[i+1]) & (ts[i] > ts[i-2] and ts[i] > ts[i+2])
            is_negative_peak = (ts[i] < ts[i-1] and ts[i] < ts[i+1]) & (ts[i] < ts[i-2] and ts[i] < ts[i+2])
        
        if (next_expected_type == 'positive' and is_positive_peak) or \
           (next_expected_type == 'negative' and is_negative_peak):
            peaks.append((i, next_expected_type, ts[i]))
            next_expected_type = 'negative' if next_expected_type == 'positive' else 'positive'
        i += 1
    
    return peaks # 感觉还是要用滑动窗口方法


# ========================
# 滑动窗口解码位流
# ========================
def decode_bits_from_waveform(waveform: np.ndarray, 
                             template_0: np.ndarray, 
                             template_1: np.ndarray) -> List[int]:
    """从波形中解码出位流"""
    bits = []
    i = 0
    j = 0
    while (i <= len(waveform) - BIT_SAMPLES) & (j<17):
        segment = waveform[i:i + BIT_SAMPLES] 
        bit,corr = match_bit_r_corr(segment, template_0, template_1) # match_bit时平局,视为错误,返回-1
        if bit != -1:
            bits.append(bit)
        else:
            bits.append(-1)  # 标记错误位
        i += BIT_SAMPLES  # 移动一个位
        j += 1            # 解码的数据个数加一 
    return bits

def decode_bits_from_waveform_r_peaks(waveform: np.ndarray, 
                             template_0: np.ndarray, 
                             template_1: np.ndarray):
    """从波形中解码出位流"""
    bits = []
    i = 0
    corr = np.correlate(waveform, template_0, mode='valid')
    res = find_alternating_peaks(corr) # 峰值数据
    if res[0][1] == 'positive':
        bits.append(0)
    else:
        bits.append(1)

    flag = False
    for i in range(1,len(res)):
        if(flag):
            if res[i][1] == 'positive':
                bits.append(0)
            else:
                bits.append(1)
        flag = not flag
        if ((res[i][0] - res[i-1][0]) > 6+1):
            flag = False
            if res[i][1] == 'positive':
                bits.append(0)
            else:
                bits.append(1)
        else:
            pass
    return bits[0:17],res

# ========================
# 数据校验  如果符合偶数校验则返回True,否则返回False
# ========================
def even_parity_check(decoded_bits):
    """
    对给定的二进制列表进行偶数奇偶校验。
    
    参数:
        decoded_bits (list): 二进制位列表(0或1),最后一位被认为是奇偶校验位
    
    返回:
        bool: 如果符合偶数奇偶校验则返回True,否则返回False
    """
    # 计算除了校验位外的所有1的数目
    count_ones = sum(decoded_bits[:-1])
    
    # 判断包括校验位在内的所有1的数目是否为偶数
    return (count_ones + decoded_bits[-1]) % 2 == 0

# ========================
# 绘制数据
# ========================
def plot_data(data):
    plt.figure(figsize=(10, 5)) # 设置图表大小
    plt.plot(data, marker='o') # 绘制数据点,添加标记使数据点更明显
    plt.title('Data Plot') # 添加标题
    plt.xlabel('Index') # X轴标签
    plt.ylabel('Value') # Y轴标签
    plt.grid(True) # 显示网格线

    # 显示图表
    plt.show()
    print(data) 

# 绘制同步头的位置
def plot_data_mark(data,sync_positions):
    """
    绘制数据,并在 sync_positions 指定的位置添加垂直线标记(如同步头位置)

    参数:
        data (list or np.ndarray): 要绘制的数据
        sync_positions (list of int): 需要标记的位置索引列表,例如 [150, 450]
    """
    plt.figure(figsize=(12, 6))  # 设置图表大小
    plt.plot(data, marker='o', markersize=3, linewidth=1, label='Signal')  # 绘制数据

    plt.axvline(x=sync_positions, color='red', linestyle='--', linewidth=2, alpha=0.8)
    plt.axvline(x=sync_positions+30, color='red', linestyle='--', linewidth=2, alpha=0.8)
    plt.title('Data Plot with Sync Markers')  # 添加标题
    plt.xlabel('Sample Index')                # X轴标签
    plt.ylabel('Amplitude')                   # Y轴标签
    plt.grid(True, alpha=0.6)                 # 显示网格线

    # 图例:只显示一次 "Sync" 标签
    handles, labels = plt.gca().get_legend_handles_labels()
    if handles:
        plt.legend()

    # 显示图表
    plt.tight_layout()
    plt.show()

    # 输出数据(可选)
    print("Data:", data)
    print("Sync positions marked:", sync_positions)

# 绘制多个同步头的位置
def plot_data_mark_mut(data,sync_positions):
    """
    绘制数据,并在 sync_positions 指定的位置添加垂直线标记(如同步头位置)

    参数:
        data (list or np.ndarray): 要绘制的数据
        sync_positions (list of int): 需要标记的位置索引列表,例如 [150, 450]
    """
    plt.figure(figsize=(12, 6))  # 设置图表大小
    plt.plot(data, marker='o', markersize=3, linewidth=1, label='Signal')  # 绘制数据
    for pos in sync_positions:
        plt.axvline(x=pos, color='red', linestyle='--', linewidth=2, alpha=0.8)
        plt.axvline(x=pos+30, color='red', linestyle='--', linewidth=2, alpha=0.8)

    plt.title('Data Plot with Sync Markers')  # 添加标题
    plt.xlabel('Sample Index')                # X轴标签
    plt.ylabel('Amplitude')                   # Y轴标签
    plt.grid(True, alpha=0.6)                 # 显示网格线

    # 图例:只显示一次 "Sync" 标签
    handles, labels = plt.gca().get_legend_handles_labels()
    if handles:
        plt.legend()

    # 显示图表
    plt.tight_layout()
    plt.show()

    # 输出数据(可选)
    print("Data:", data)
    print("Sync positions marked:", sync_positions)

def plot_data_mark_mut_dot(data, sync_positions):
    """
    绘制数据,并在 sync_positions 指定的位置添加垂直线标记(如同步头位置)
    在第二个红线后以10为距离添加标记,直到下一个第一条红线为止,标记显示对应index
    """
    plt.figure(figsize=(12, 6))  # 设置图表大小
    plt.plot(data, marker='o', markersize=3, linewidth=1, label='Signal')  # 绘制数据
    
    # 先绘制所有红线
    red_lines = []
    for pos in sync_positions:
        # 第一条红线位置
        first_line = pos
        # 第二条红线位置
        second_line = pos + 30
        plt.axvline(x=first_line, color='red', linestyle='--', linewidth=2, alpha=0.8)
        plt.axvline(x=second_line, color='red', linestyle='--', linewidth=2, alpha=0.8)
        red_lines.append((first_line, second_line))
    
    # 为每个区间添加浮动文本标记
    for i, (first_line, second_line) in enumerate(red_lines):
        # 确定当前区间的结束位置(下一个第一条红线)
        if i < len(red_lines) - 1:
            end_pos = red_lines[i+1][0]  # 下一个第一条红线位置
        else:
            end_pos = len(data) - 1  # 最后一个区间以数据长度为终点
        
        # 从第二条红线后10个位置开始
        start_pos = second_line + 10
        
        # 确保起始位置不超过结束位置
        if start_pos > end_pos:
            continue
        
        # 以10为间隔添加浮动标记(显示index)
        current_pos = start_pos
        while current_pos <= end_pos:
            # 获取当前位置的数据值(用于定位文本)
            data_value = data[current_pos]
            
            # 添加浮动文本标记(显示当前index)
            # 位置:在点的右下方(可通过xytext调整偏移)
            # 仅第一个标记添加图例标签
            label = 'Index Mark' if (i == 0 and current_pos == start_pos) else ""
            if(current_pos in score_dict):
                plt.text(
                    current_pos, data_value,  # 文本锚点位置(对应数据点)
                    f'{score_dict[current_pos]}',         # 显示的文本内容(index值)
                    fontsize=8,               # 文本大小
                    color='blue',             # 文本颜色
                    ha='left', va='bottom',   # 水平/垂直对齐方式
                    bbox=dict(facecolor='white', alpha=0.3, boxstyle='round,pad=0.3'),  # 白色背景框
                    label=label
                )
                # print("----",current_pos)
            current_pos += 10

    plt.title('Data Plot with Sync Markers')  # 添加标题
    plt.xlabel('Sample Index')                # X轴标签
    plt.ylabel('Amplitude')                   # Y轴标签
    plt.grid(True, alpha=0.6)                 # 显示网格线

    # 处理图例,确保只显示唯一标签
    handles, labels = plt.gca().get_legend_handles_labels()
    by_label = dict(zip(labels, handles))
    plt.legend(by_label.values(), by_label.keys())

    # 显示图表
    plt.tight_layout()
    plt.show()

    # 输出数据(可选)
    print("Data:", data)
    print("Sync positions marked:", sync_positions)


# ========================
# 数据滤波
# ========================
def lowpass_filter_scipy(data, cutoff_freq, sampling_rate, filter_type='butter', order=4):
    """
    使用SciPy对NumPy数组进行低通滤波
    
    参数:
    data: numpy数组,输入信号
    cutoff_freq: float, 截止频率 (Hz)
    sampling_rate: float, 采样频率 (Hz)
    filter_type: str, 滤波器类型 ('butter', 'cheby1', 'bessel')
    order: int, 滤波器阶数
    
    返回:
    filtered_data: 滤波后的numpy数组
    """
    # 计算归一化截止频率 (Nyquist频率的倍数)
    nyquist_freq = 0.5 * sampling_rate
    normal_cutoff = cutoff_freq / nyquist_freq
    
    if filter_type == 'butter':
        b, a = signal.butter(order, normal_cutoff, btype='low', analog=False)
    elif filter_type == 'cheby1':
        b, a = signal.cheby1(order, 0.5, normal_cutoff, btype='low', analog=False)
    elif filter_type == 'bessel':
        b, a = signal.bessel(order, normal_cutoff, btype='low', analog=False)
    else:
        raise ValueError("不支持的滤波器类型")
    
    # 应用滤波器
    filtered_data = signal.filtfilt(b, a, data)
    
    return filtered_data

def moving_average_filter(data, window_size):
    """
    使用移动平均进行低通滤波
    
    参数:
    data: numpy数组
    window_size: int, 窗口大小
    
    返回:
    filtered_data: 滤波后的数组
    """
    if window_size <= 1:
        return data.copy()
    
    # 使用卷积实现移动平均
    window = np.ones(window_size) / window_size
    filtered_data = np.convolve(data, window, mode='same')
    
    return filtered_data


res_dict = {}
score_dict = {}

if __name__ == "__main__":
    sync_head_num = 0 # 查找到同步头的个数
    paritiy_num = 0
    paritiy_list = []
    not_par_num = 0
    not_par_list = []

    data = np.loadtxt('***.dat', skiprows=1) # 加载数据文件,跳过第一行
    # 预处理数据
    data = -1*data 
    print(data)
    startindex = 0
    data = data[startindex:startindex+49000] # 100000
    # 0.预处理
    # data = moving_average_filter(data,2)
    data = lowpass_filter_scipy(data,40000,100000)
    # 1. 生成模板
    template_0, template_1 = generate_manchester_templates()
    sync_template = generate_sync_template(template_1, 3)
    
    # 2. 波形数据
    test_wave = data
    # plot_data(test_wave)
    data_len = data.size
    index = 0
    sync_list = [] # 标记同步头位置的列表
    while(index<(data_len-1000)): # 留1000个缓冲空间
        # 查找同步头
        sync_positions =  -1
        window_size = 300
        window_overlap = 100
        while(sync_positions ==-1):
            index = index + window_size - window_overlap # 窗口重叠
            segment = test_wave[index:-1]
            if(segment.size != 0):
                sync_positions = find_sync_start_correlation(segment, window_size, sync_template, threshold= 55000.0)
            else:
                print("已遍历所有数据")
                break

        if(segment.size != 0):   
            sync_head_num = sync_head_num +1
            print("第一个同步头的位置",index+sync_positions)    

            # plot_data_mark(test_wave[0:-1],index+sync_positions) # python 不支持函数重载
            sync_list.append(index+sync_positions)

            # 3. 解码位流
            data = test_wave[index+sync_positions+30:index+sync_positions+30+170] 
            print("+++",index+sync_positions+30)
            decoded_bits,r_list = decode_bits_from_waveform_r_peaks(data, template_0, template_1) # 解完17个就返回
            res_dict[index+sync_positions] = decoded_bits
            value = index+sync_positions # +30+10+0*10
            score_dict[value] = r_list

            # 4. 对list数据decoded_bits进行奇偶校验
            is_valid = even_parity_check(decoded_bits)
            print(f"奇偶校验{'通过' if is_valid else '未通过'}")
            if is_valid:
                paritiy_num = paritiy_num + 1
                paritiy_list.append(index+sync_positions)
            else:
                not_par_num = not_par_num + 1
                not_par_list.append(index+sync_positions)
                

    
    print("-------------统计信息------------")
    print("找到同步头",sync_head_num)
    print("奇偶校验未通过个数",paritiy_num,paritiy_list)
    print("奇偶校验通过个数",not_par_num,not_par_list)
    print("---------------------------------")
    print("-------------绘制同步头位置------------")
    plot_data_mark_mut_dot(test_wave,sync_list)
    print("---------------------------------")
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值