基于ZYNQ-7000 ARM端的水声声呐图像压缩方案

基于ZYNQ-7000 ARM端的水声声呐图像压缩方案

摘要

在水下探测系统中,由于水声通信带宽极其有限(通常仅为几kbps甚至更低),原始声呐图像的大数据量传输成为了瓶颈。本文将详细介绍如何在Xilinx ZYNQ-7000平台的ARM处理器(PS端)上,构建一套高效的声呐图像压缩系统。文章涵盖了数据流架构、压缩算法选择(JPEG/JPEG2000)、基于OpenCV/Libjpeg-turbo的软件实现以及针对Cortex-A9的NEON加速优化策略。


1. 背景与挑战

声呐图像与普通光学图像不同,具有以下显著特征:

  1. 高动态范围:通常为16-bit甚至更高位宽的灰度数据。
  2. 噪声特性:存在大量的散斑噪声(Speckle Noise)。
  3. 实时性要求:需要在采集的同时进行压缩,以避免阻塞内存。

ZYNQ-7000的优势在于PL+PS架构:PL端负责高速采集和波束形成,通过AXI总线将数据通过DMA搬运至DDR,PS端(ARM)直接读取DDR数据进行压缩和传输。


2. 系统硬件架构设计

在软件编码前,必须明确数据是如何流向ARM的。

  • PL端 (FPGA) :完成声呐回波采集、数字信号处理(滤波、波束形成),将生成的一帧声呐数据(Raw Data)通过AXI VDMAAXI DMA写入DDR3内存。
  • PS端 (ARM) :运行PetaLinux/嵌入式Linux。
    • 驱动层:通过UIO或DMA驱动获取DDR中的图像地址。
    • 应用层:读取Raw Data -> 预处理(降噪/归一化) -> 图像压缩 -> 网络发送。

3. 压缩算法选型

针对ARM Cortex-A9双核处理器(667MHz/800MHz),我们需要平衡压缩率CPU负载

算法 特点 适用场景 ZYNQ ARM实现建议
JPEG 计算量小,DCT变换,有损压缩 实时性要求高,对边缘模糊不敏感 推荐 。使用libjpeg-turbo库(支持NEON指令集加速)。
JPEG 2000 小波变换(DWT),支持无损/有损,压缩率高 需要保留图像细节,带宽极低 可用。使用OpenJPEG库,但CPU负载较高。
PNG 无损压缩,Deflate算法 必须无损传输 压缩率较低,不适合极低带宽。
自定义小波(SPIHT) 针对声呐特性的学术算法 特定科研需求 需要手写代码,开发周期长。

本文选用方案 :为了兼顾实时性和开发效率,采用JPEG算法 (基于libjpeg-turbo加速)。


4. 软件实现步骤

4.1 环境准备

在PC端搭建ZYNQ的交叉编译环境(通常使用Xilinx SDK或PetaLinux工具链)。需要交叉编译以下库:

  1. libjpeg-turbo(关键!比普通libjpeg快2-4倍)
  2. OpenCV(可选,方便图像矩阵操作,但需裁剪以减小体积)

交叉编译libjpeg-turbo示例:

bash 复制代码
./configure --host=arm-linux-gnueabihf \
            --prefix=/home/user/zynq_libs/libjpeg \
            CFLAGS="-O3 -mfpu=neon" \
            --with-jpeg8
make && make install

注意:务必开启NEON支持。

4.2 核心代码实现 (C++)

假设我们从PL端获取到的数据是 unsigned short (16-bit) 类型的Raw数据,分辨率为 1024x768。

步骤一:数据读取与预处理

声呐数据通常是16位的,而标准JPEG是8位的。我们需要进行动态范围压缩(归一化)或伪彩色映射。

cpp 复制代码
#include <opencv2/opencv.hpp>
#include <vector>
#include <iostream>

// 模拟从驱动/内存读取的声呐Raw数据指针
// 实际工程中这里通常是 mmap 映射后的物理地址
void* get_sonar_raw_data_ptr() {
    // TODO: 实现DMA内存读取
    return nullptr; 
}

void process_and_compress() {
    int width = 1024;
    int height = 768;
    
    // 1. 获取16位原始数据
    unsigned short* raw_data = (unsigned short*)get_sonar_raw_data_ptr();
    if(raw_data == nullptr) return;

    // 2. 构建OpenCV Mat (零拷贝,直接引用内存)
    cv::Mat src_16bit(height, width, CV_16UC1, raw_data);

    // 3. 预处理:16位转8位
    // 方法A:简单的线性缩放 (丢失细节)
    // cv::Mat src_8bit;
    // src_16bit.convertTo(src_8bit, CV_8UC1, 255.0 / 65535.0);
    
    // 方法B:直方图均衡化 (推荐,增强声呐目标对比度)
    // 先转8位再均衡化,或者使用CLAHE
    cv::Mat temp_8bit, final_img;
    double minVal, maxVal;
    cv::minMaxLoc(src_16bit, &minVal, &maxVal); // 寻找动态范围
    src_16bit.convertTo(temp_8bit, CV_8UC1, 255.0 / (maxVal - minVal), -minVal * 255.0 / (maxVal - minVal));
    
    // 应用伪彩色 (通常声呐图像习惯用伪彩色显示)
    cv::applyColorMap(temp_8bit, final_img, cv::COLORMAP_JET);

    // 4. 图像压缩
    std::vector<uchar> buf;
    std::vector<int> params;
    
    // 设置JPEG质量 (0-100),主要影响压缩率和画质
    params.push_back(cv::IMWRITE_JPEG_QUALITY);
    params.push_back(75); // 75通常是画质与体积的最佳平衡点

    // 编码 (OpenCV底层会自动调用libjpeg-turbo)
    double t_start = (double)cv::getTickCount();
    cv::imencode(".jpg", final_img, buf, params);
    double t_end = (double)cv::getTickCount();

    std::cout << "压缩耗时: " << (t_end - t_start) / cv::getTickFrequency() * 1000 << " ms" << std::endl;
    std::cout << "压缩后大小: " << buf.size() / 1024.0 << " KB" << std::endl;

    // 5. 发送数据 (UDP/TCP)
    // send_to_network(buf.data(), buf.size());
}

5. 性能优化技巧 (针对ZYNQ-7000)

在ARM Cortex-A9上跑图像压缩,往往会占用较高的CPU。以下优化至关重要:

5.1 开启NEON指令集加速

ZYNQ的Cortex-A9支持NEON SIMD指令。

  • 编译选项 :确保Makefile中包含 -mfpu=neon -mfloat-abi=hard -ftree-vectorize
  • 库支持:确认使用的OpenCV和libjpeg在编译时检测到了NEON。

5.2 多线程流水线 (Pipeline)

ZYNQ是双核CPU。不要让一个核干等。

  • Core 0:负责控制、读取DMA数据、预处理。
  • Core 1:负责JPEG压缩、网络封包发送。
  • 使用 pthread 或 C++11 std::thread 配合线程安全队列实现生产者-消费者模型。

5.3 零拷贝 (Zero-Copy)

  • 避免 memcpy
  • PL端通过DMA将数据写入预留的DDR内存(Reserved Memory)。
  • Linux下使用UIO驱动或 /dev/mem + mmap 直接将该物理地址映射到用户空间指针,传给OpenCV构造 cv::Mat

5.4 算法级优化

  • 感兴趣区域 (ROI) 压缩:声呐图像往往只有中间扇形区域或特定目标区域有效。可以裁剪出ROI区域进行压缩,背景直接丢弃或高倍压缩。

6. 进阶:利用PL端加速 (FPGA Offloading)

如果ARM端的帧率仍然无法满足需求(例如需要处理高频多波束声呐),则需要将压缩算法下沉到FPGA(PL端)。

  • Xilinx Vitis Video Library:Xilinx提供了JPEG Encoder的IP核。
  • 方案
    1. PL端采集数据。
    2. PL端直接运行JPEG Encoder IP核。
    3. 将压缩后的.jpg二进制流写入DDR。
    4. ARM端只需直接读取压缩好的流文件并发送。
  • 效果:ARM CPU负载几乎为0,压缩延迟极低。

7. Matlab验证代码

matlab 复制代码
function jpeg_compression_multi_quality()
    % 1. 打开图片
    [filename, pathname] = uigetfile({'*.jpg;*.jpeg;*.png;*.bmp;*.gif', 'Image Files (*.jpg, *.jpeg, *.png, *.bmp, *.gif)'; ...
                                      '*.*', 'All Files (*.*)'}, '选择一张图片');

    if isequal(filename, 0)
        disp('用户取消选择图片。');
        return;
    end

    full_path = fullfile(pathname, filename);
    original_image = imread(full_path);

    % 定义三种不同的压缩质量
    qualities = [80, 40, 5]; % 低质量, 中等质量, 高质量

    % 创建一个图窗来显示所有图片
    figure;
    
    % 显示原始图片
    subplot(2, 2, 1); % 2行2列,第1个位置
    imshow(original_image);
    title('原始图片');

    % 存储压缩信息以供显示
    compression_results = struct('quality', {}, 'size_kb', {}, 'compression_ratio', {});

    % 获取原始文件大小
    original_info = dir(full_path);
    original_size_kb = original_info.bytes / 1024;
    fprintf('原始文件大小: %.2f KB\n', original_size_kb);

    % 循环处理不同的压缩质量
    for i = 1:length(qualities)
        current_quality = qualities(i);
        
        % 创建一个临时文件来保存压缩后的图片
        temp_compressed_filename = sprintf('temp_compressed_image_q%d.jpg', current_quality);

        try
            imwrite(original_image, temp_compressed_filename, 'jpeg', 'Quality', current_quality);
            compressed_image = imread(temp_compressed_filename);
        catch ME
            fprintf('压缩或读取质量为 %d 的图片失败: %s\n', current_quality, ME.message);
            continue; % 跳过当前质量,继续下一个
        end

        % 显示压缩后的图片
        subplot(2, 2, i + 1); % 从第2个位置开始显示压缩图片
        imshow(compressed_image);
        title(sprintf('压缩图片 (质量: %d)', current_quality));

        % 比较文件大小并存储结果
        compressed_info = dir(temp_compressed_filename);
        compressed_size_kb = compressed_info.bytes / 1024;
        compression_ratio = (1 - compressed_size_kb / original_size_kb) * 100;

        compression_results(i).quality = current_quality;
        compression_results(i).size_kb = compressed_size_kb;
        compression_results(i).compression_ratio = compression_ratio;

        % 清理临时文件
        delete(temp_compressed_filename);
    end

    % 在命令窗口显示所有压缩结果
    fprintf('\n--- 压缩结果汇总 ---\n');
    for i = 1:length(compression_results)
        fprintf('质量 %d: 压缩文件大小: %.2f KB, 压缩率: %.2f%%\n', ...
                compression_results(i).quality, ...
                compression_results(i).size_kb, ...
                compression_results(i).compression_ratio);
    end
end



8. 总结

在ZYNQ-7000上实现声呐图像压缩,软件方案(ARM + libjpeg-turbo + NEON) 是开发周期最短、灵活性最高的选择,足以应对分辨率在1024x768以下、帧率15fps左右的应用场景。

如果项目对实时性有极端要求,建议逐步迁移至PL端硬件加速方案。

参考资料

  1. UG585: Zynq-7000 SoC Technical Reference Manual
  2. Libjpeg-turbo GitHub Repository
  3. OpenCV Documentation
相关推荐
飞易通16 小时前
WIFI6 SOC模块介绍解析
arm开发·wifi模块·soc模块
松涛和鸣19 小时前
DAY66 SPI Driver for ADXL345 Accelerometer
linux·网络·arm开发·数据库·驱动开发
代码游侠1 天前
学习笔记——LCD技术详解
arm开发·笔记·嵌入式硬件·学习·架构
切糕师学AI1 天前
ARM汇编器与GNU汇编器:详细介绍与核心区别
arm开发·gnu·汇编器
CQ_YM1 天前
ARM之lcd与pwm
arm开发·单片机·嵌入式硬件·arm
梁洪飞1 天前
解决摄像头驱动起不来的情况
linux·arm开发·图像处理·嵌入式硬件·arm
lysine_1 天前
实现ubuntu两个网口桥接
linux·服务器·网络·arm开发·ubuntu
代码游侠2 天前
ARM开发——阶段问题综述(二)
运维·arm开发·笔记·单片机·嵌入式硬件·学习
_chirs2 天前
编译不依赖动态库的FFMPEG(麒麟国防 V10)
arm开发·ffmpeg