Intel’s oneAPI implements 5*5 median filtering of bmp images

Intel’s oneAPI is a powerful software development toolset designed to support cross-platform parallel computing and heterogeneous computing. It provides a unified programming model that allows developers to efficiently program in parallel on a variety of hardware platforms, including CPUs, GPUs, and FPGAs.

At the heart of oneAPI is Data Parallel C++ (DPC++), an extended programming model based on standard C++. DPC++ combines the flexibility of C++ with the power of parallel computing, enabling developers to take advantage of multi-core CPUs, powerful GPUs, and reconfigurable FPGAs for high-performance computing. By using DPC++, developers can easily write parallel code and take advantage of the computing resources of the hardware platform to accelerate applications.

oneAPI also provides a rich set of tools and libraries for developing and optimizing parallel applications. For example, it includes high-performance math libraries for vectorization and optimized computing, libraries for accelerating image processing and machine learning, and tools for building and optimizing FPGA accelerators. These tools and libraries help developers more easily implement high-performance and efficient parallel computing.

Another important feature is oneAPI’s cross-platform support. It allows developers to write code once on different hardware platforms and use a unified programming model for development. This flexibility enables developers to achieve optimal performance on different hardware architectures and simplifies the complexities of cross-platform development.

The file format of bmp images and the principle of median filtering have been explained a lot on various websites, so I won’t explain too much here. The median filtering of bmp images is actually to simply extract and save the file header information, and then read the bitmap pixel data for filtering. The object of filtering here is the bmp image of RGB888, there is no palette.

The first is implemented in C++:

#include <iostream>
#include <fstream>
#include <vector>
#include <algorithm>

using namespace std;

#pragma pack(push, 1)
struct BMPHeader {
    char signature[2]; // identifier of the BMP file (must be "BM")
    uint32_t file_size; // total size of BMP file
    uint32_t reserved; // Reserved field
    uint32_t data_offset; // offset of bitmap data in the file
    uint32_t header_size; // BMP header size
    uint32_t width; // image width
    uint32_t height; // image height
    uint16_t planes; // Number of color planes for the image (always 1)
    uint16_t bits_per_pixel; // bits per pixel
    uint32_t compression; // compression type
    uint32_t image_size; // size of image data
    uint32_t x_pixels_per_meter; // horizontal resolution (pixels/meter)
    uint32_t y_pixels_per_meter; // vertical resolution (pixels/meter)
    uint32_t colors_used; // the number of colors actually used in the palette
    uint32_t important_colors; // number of important colors
};
#pragma pack(pop)

struct RGB {
    uint8_t blue; // blue channel
    uint8_t green; // green channel
    uint8_t red; // red channel
};

// Read the image data from the BMP file and return a vector of image pixels
vector<RGB> readBMP(const string & amp; filename, BMPHeader & amp; header) {
    ifstream file(filename, ios::binary);

    if (!file.is_open()) {
        cout << "Unable to open bmp file: " << filename << endl;
        return {};
    }

    file.read(reinterpret_cast<char*>( & amp;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;
}

// write image data to BMP file
void writeBMP(const string & amp; filename, const BMPHeader & amp; header, const vector<RGB> & amp; 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*>( & amp;header), sizeof(BMPHeader));
    file.write(reinterpret_cast<const char*>(image.data()), header.image_size);

    file. close();
}

// Get the median pixel of the pixel vector
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;
}

// apply the median filter to the image
vector<RGB> applyMedianFilter(const vector<RGB> & amp; 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 & amp; & amp; newX < width & amp; & amp; newY >= 0 & amp; & amp; newY < height) {
                        pixels.push_back(image[newY * width + newX]);
                    }
                }
            }

            filteredImage[y * width + x] = getMedianPixel(pixels);
        }
    }

    return filteredImage;
}

int main() {
    const string inputFilename = "input.bmp"; // input file
    const string outputFilename = "output.bmp"; // output file

    BMP Header header;
    vector<RGB> image = readBMP(inputFilename, header); // read BMP file

    if (image.empty()) {
        return 1;
    }

    vector<RGB> filteredImage = applyMedianFilter(image, header.width, header.height); // Perform median filtering

    writeBMP(outputFilename, header, filteredImage); // write the result to a BMP file

    cout << "filtering completed, the image is saved as: " << outputFilename << endl;

    return 0;
}

Before processing:

? After processing:

?When using oneAPI to implement, you need to use oneAPI’s programming model Data Parallel C++ (DPC++), which combines traditional C++ programming and parallel computing capabilities, enabling developers to perform on a variety of hardware platforms Parallel computing such as CPUs, GPUs, and FPGAs. Visit Intel official website to download.

code show as below:

#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 & amp; filename, BMPHeader & amp; header) {
    ifstream file(filename, ios::binary);

    if (!file.is_open()) {
        cout << "Unable to open bmp file: " << filename << endl;
        return {};
    }

    file.read(reinterpret_cast<char*>( & amp;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 & amp; filename, const BMPHeader & amp; header, const vector<RGB> & amp; 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*>( & amp;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> & amp; 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([ & amp;](handler & amp; 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 & amp; & amp; newX < width & amp; & amp; newY >= 0 & amp; & amp; 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";

    BMP Header 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 << "filtering completed, the image is saved as: " << outputFilename << endl;

    return 0;
}

Save the file and run it with the following command (my file is called median_filter.cpp here):

dpcpp median_filter.cpp -o median_filter
./median_filter

From the code point of view, the code is not much different from using C++. The main difference is that the sycl namespace and the queue class are used to create the execution queue, and the buffer class is used to manage the explicit memory allocation and transmission of data. Additionally, the parallel_for function is used to perform the median filtering operation in parallel.