基于ZYNQ-7000 ARM端的水声声呐图像压缩方案
摘要 :
在水下探测系统中,由于水声通信带宽极其有限(通常仅为几kbps甚至更低),原始声呐图像的大数据量传输成为了瓶颈。本文将详细介绍如何在Xilinx ZYNQ-7000平台的ARM处理器(PS端)上,构建一套高效的声呐图像压缩系统。文章涵盖了数据流架构、压缩算法选择(JPEG/JPEG2000)、基于OpenCV/Libjpeg-turbo的软件实现以及针对Cortex-A9的NEON加速优化策略。
1. 背景与挑战
声呐图像与普通光学图像不同,具有以下显著特征:
- 高动态范围:通常为16-bit甚至更高位宽的灰度数据。
- 噪声特性:存在大量的散斑噪声(Speckle Noise)。
- 实时性要求:需要在采集的同时进行压缩,以避免阻塞内存。
ZYNQ-7000的优势在于PL+PS架构:PL端负责高速采集和波束形成,通过AXI总线将数据通过DMA搬运至DDR,PS端(ARM)直接读取DDR数据进行压缩和传输。
2. 系统硬件架构设计
在软件编码前,必须明确数据是如何流向ARM的。
- PL端 (FPGA) :完成声呐回波采集、数字信号处理(滤波、波束形成),将生成的一帧声呐数据(Raw Data)通过AXI VDMA 或AXI 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工具链)。需要交叉编译以下库:
- libjpeg-turbo(关键!比普通libjpeg快2-4倍)
- 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++11std::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核。
- 方案 :
- PL端采集数据。
- PL端直接运行JPEG Encoder IP核。
- 将压缩后的
.jpg二进制流写入DDR。 - 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端硬件加速方案。
参考资料:
- UG585: Zynq-7000 SoC Technical Reference Manual
- Libjpeg-turbo GitHub Repository
- OpenCV Documentation