Intel's oneAPI 是一个强大的软件开发工具集,旨在支持跨平台的并行计算和异构计算。它提供了一种统一的编程模型,允许开发人员在各种硬件平台上进行高效的并行编程,包括 CPU、GPU 和 FPGA。
oneAPI 的核心是 Data Parallel C++ (DPC++),这是一种基于标准 C++ 的扩展编程模型。DPC++ 结合了 C++ 的灵活性和并行计算的能力,使开发人员能够利用多核 CPU、强大的 GPU 和可重构的 FPGA 进行高性能计算。通过使用 DPC++,开发人员可以轻松地编写并行代码,并利用硬件平台的计算资源来加速应用程序。
oneAPI 还提供了一组丰富的工具和库,用于开发和优化并行应用程序。例如,它包括用于向量化和优化计算的高性能数学库、用于加速图像处理和机器学习的库,以及用于构建和优化 FPGA 加速器的工具。这些工具和库可以帮助开发人员更轻松地实现高性能和高效能的并行计算。
另一个重要的特点是 oneAPI 的跨平台支持。它允许开发人员在不同的硬件平台上编写一次代码,并使用统一的编程模型进行开发。这种灵活性使开发人员能够在不同的硬件架构上实现最佳性能,并简化了跨平台开发的复杂性。
bmp图片的文件格式和中值滤波原理在各种网站上都有很多讲解,这里就不过多解释了。对bmp图片的中值滤波其实就是简单的提取并保存文件头信息,然后读取位图像素数据进行滤波即可。这里滤波的对象是RGB888的bmp图片,没有调色盘。
首先是使用C++实现:
#include <iostream>
#include <fstream>
#include <vector>
#include <algorithm>
using namespace std;
#pragma pack(push, 1)
struct BMPHeader {
char signature[2]; // BMP 文件的标识符(必须为 "BM")
uint32_t file_size; // BMP 文件的总大小
uint32_t reserved; // 保留字段
uint32_t data_offset; // 位图数据在文件中的偏移量
uint32_t header_size; // BMP 头的大小
uint32_t width; // 图像宽度
uint32_t height; // 图像高度
uint16_t planes; // 图像的颜色平面数(始终为 1)
uint16_t bits_per_pixel; // 每个像素的位数
uint32_t compression; // 压缩类型
uint32_t image_size; // 图像数据的大小
uint32_t x_pixels_per_meter; // 水平分辨率(像素/米)
uint32_t y_pixels_per_meter; // 垂直分辨率(像素/米)
uint32_t colors_used; // 调色板中实际使用的颜色数量
uint32_t important_colors; // 重要颜色数量
};
#pragma pack(pop)
struct RGB {
uint8_t blue; // 蓝色通道
uint8_t green; // 绿色通道
uint8_t red; // 红色通道
};
// 从 BMP 文件中读取图像数据并返回图像像素的向量
vector<RGB> readBMP(const string& filename, BMPHeader& header) {
ifstream file(filename, ios::binary);
if (!file.is_open()) {
cout << "无法打开bmp文件: " << filename << endl;
return {};
}
file.read(reinterpret_cast<char*>(&header), sizeof(BMPHeader));
if (header.signature[0] != 'B' || header.signature[1] != 'M') {
cout << "Invalid BMP file: " << filename << endl;
return {};
}
vector<RGB> image(header.width * header.height);
file.seekg(header.data_offset, ios::beg);
file.read(reinterpret_cast<char*>(image.data()), header.image_size);
file.close();
return image;
}
// 将图像数据写入 BMP 文件
void writeBMP(const string& filename, const BMPHeader& header, const vector<RGB>& image) {
ofstream file(filename, ios::binary);
if (!file.is_open()) {
cout << "Failed to create the file: " << filename << endl;
return;
}
file.write(reinterpret_cast<const char*>(&header), sizeof(BMPHeader));
file.write(reinterpret_cast<const char*>(image.data()), header.image_size);
file.close();
}
// 获取像素向量的中值像素
RGB getMedianPixel(const vector<RGB>& pixels) {
vector<uint8_t> reds, greens, blues;
for (const auto& pixel : pixels) {
reds.push_back(pixel.red);
greens.push_back(pixel.green);
blues.push_back(pixel.blue);
}
sort(reds.begin(), reds.end());
sort(greens.begin(), greens.end());
sort(blues.begin(), blues.end());
RGB medianPixel;
medianPixel.red = reds[reds.size() / 2];
medianPixel.green = greens[greens.size() / 2];
medianPixel.blue = blues[blues.size() / 2];
return medianPixel;
}
// 应用中值滤波器到图像上
vector<RGB> applyMedianFilter(const vector<RGB>& image, uint32_t width, uint32_t height) {
vector<RGB> filteredImage(image.size());
for (uint32_t y = 0; y < height; ++y) {
for (uint32_t x = 0; x < width; ++x) {
vector<RGB> pixels;
for (int i = -2; i <= 2; ++i) {
for (int j = -2; j <= 2; ++j) {
int newX = x + j;
int newY = y + i;
if (newX >= 0 && newX < width && newY >= 0 && newY < height) {
pixels.push_back(image[newY * width + newX]);
}
}
}
filteredImage[y * width + x] = getMedianPixel(pixels);
}
}
return filteredImage;
}
int main() {
const string inputFilename = "input.bmp"; // 输入文件
const string outputFilename = "output.bmp"; // 输出文件
BMPHeader header;
vector<RGB> image = readBMP(inputFilename, header); // 读取 BMP 文件
if (image.empty()) {
return 1;
}
vector<RGB> filteredImage = applyMedianFilter(image, header.width, header.height); // 进行中值滤波
writeBMP(outputFilename, header, filteredImage); // 将结果写入 BMP 文件
cout << "滤波完成,图像保存为: " << outputFilename << endl;
return 0;
}
处理前:
处理后:
使用oneAPI实现时,需要使用oneAPI的编程模型Data Parallel C++ (DPC++),该模型结合了传统的 C++ 编程和并行计算的能力,使开发人员能够在多种硬件平台上进行并行计算,如 CPU、GPU 和 FPGA。访问Intel官网下载即可。
代码如下:
#include <iostream>
#include <fstream>
#include <vector>
#include <CL/sycl.hpp>
using namespace std;
using namespace sycl;
struct BMPHeader {
char signature[2];
uint32_t file_size;
uint32_t reserved;
uint32_t data_offset;
uint32_t header_size;
uint32_t width;
uint32_t height;
uint16_t planes;
uint16_t bits_per_pixel;
uint32_t compression;
uint32_t image_size;
uint32_t x_pixels_per_meter;
uint32_t y_pixels_per_meter;
uint32_t colors_used;
uint32_t important_colors;
};
struct RGB {
uint8_t blue;
uint8_t green;
uint8_t red;
};
vector<RGB> readBMP(const string& filename, BMPHeader& header) {
ifstream file(filename, ios::binary);
if (!file.is_open()) {
cout << "无法打开bmp文件: " << filename << endl;
return {};
}
file.read(reinterpret_cast<char*>(&header), sizeof(BMPHeader));
if (header.signature[0] != 'B' || header.signature[1] != 'M') {
cout << "Invalid BMP file: " << filename << endl;
return {};
}
vector<RGB> image(header.width * header.height);
file.seekg(header.data_offset, ios::beg);
file.read(reinterpret_cast<char*>(image.data()), header.image_size);
file.close();
return image;
}
void writeBMP(const string& filename, const BMPHeader& header, const vector<RGB>& image) {
ofstream file(filename, ios::binary);
if (!file.is_open()) {
cout << "Failed to create the file: " << filename << endl;
return;
}
file.write(reinterpret_cast<const char*>(&header), sizeof(BMPHeader));
file.write(reinterpret_cast<const char*>(image.data()), header.image_size);
file.close();
}
RGB getMedianPixel(const vector<RGB>& pixels) {
vector<uint8_t> reds, greens, blues;
for (const auto& pixel : pixels) {
reds.push_back(pixel.red);
greens.push_back(pixel.green);
blues.push_back(pixel.blue);
}
sort(reds.begin(), reds.end());
sort(greens.begin(), greens.end());
sort(blues.begin(), blues.end());
RGB medianPixel;
medianPixel.red = reds[reds.size() / 2];
medianPixel.green = greens[greens.size() / 2];
medianPixel.blue = blues[blues.size() / 2];
return medianPixel;
}
vector<RGB> applyMedianFilter(const vector<RGB>& image, uint32_t width, uint32_t height) {
vector<RGB> filteredImage(image.size());
queue q(gpu_selector{});
buffer<RGB, 2> imageBuf(image.data(), range<2>(width, height));
buffer<RGB, 2> filteredImageBuf(filteredImage.data(), range<2>(width, height));
q.submit([&](handler& h) {
auto img = imageBuf.get_access<access::mode::read>(h);
auto filteredImg = filteredImageBuf.get_access<access::mode::write>(h);
h.parallel_for(range<2>(width, height), [=](id<2> idx) {
int x = idx[0];
int y = idx[1];
vector<RGB> pixels;
for (int i = -2; i <= 2; ++i) {
for (int j = -2; j <= 2; ++j) {
int newX = x + j;
int newY = y + i;
if (newX >= 0 && newX < width && newY >= 0 && newY < height) {
pixels.push_back(img[newY][newX]);
}
}
}
filteredImg[y][x] = getMedianPixel(pixels);
});
});
return filteredImage;
}
int main() {
const string inputFilename = "input.bmp";
const string outputFilename = "output.bmp";
BMPHeader header;
vector<RGB> image = readBMP(inputFilename, header);
if (image.empty()) {
return 1;
}
vector<RGB> filteredImage = applyMedianFilter(image, header.width, header.height);
writeBMP(outputFilename, header, filteredImage);
cout << "滤波完成,图像保存为: " << outputFilename << endl;
return 0;
}
保存文件并使用以下指令运行(这里我的文件名叫median_filter.cpp):
dpcpp median_filter.cpp -o median_filter
./median_filter
只从代码来看代码和使用C++相比差不太多,主要的区别是使用了 sycl 命名空间和 queue 类来创建执行队列,并使用 buffer 类来管理数据的显式内存分配和传输。此外,还使用了 parallel_for 函数来并行执行中值滤波操作。