使用 Intel oneAPI 和 DPC++ 加速 Seam Carving 算法

简介

Seam carving 是一种基于内容的图像缩放技术,可以在调整图像大小的同时保留图像中最重要的特征。这种技术特别适用于在不扭曲主要内容的情况下调整图像大小。本文将演示如何使用 Intel oneAPI 和 Data Parallel C++ (DPC++) 加速 seam carving 算法。

Intel oneAPI 是一种统一的编程模型,简化了在不同架构(如 CPU、GPU 和 FPGA)上开发应用程序的过程。DPC++ 是 C++ 的扩展,使开发人员能够使用单一源代码方法为异构系统编写并行代码。

使用 DPC++ 和 OpenCV 实现 seam carving 算法,利用 Intel oneAPI 的并行处理能力来优化算法性能。

环境设置

首先安装以下工具:

  1. Intel oneAPI 基础工具包:从这里下载并安装。

  2. OpenCV 库:按照这里的安装说明进行操作。

Seam Carving 算法

Seam Carving算法是一种用于图像缩放的算法,它可以在不改变图像比例的情况下,自动删除或插入像素,从而实现图像的缩放。

其基本思路如下:

  1. 计算能量图:首先,需要计算出图像中每个像素的能量值。能量值可以根据像素的颜、梯度等特征来计算,用于衡量像素的重要性。

  2. 找到最小能量路径:接下来,需要找到一条从图像顶部到底部的路径,使得路径上的像素能量之和最小。这条路径称为最小能量路径。

  3. 删除最小能量路径:将最小能量路径上的像素删除,即可将图像宽度缩小1个像素。如果需要将图像高度缩小,则需要找到从左侧到右侧的最小能量路径,并删除路径上的像素。

重复步骤2和3:重复执行步骤2和3,直到达到所需的图像大小。

其中步骤2和3可以利用 DPC++ 进行并行加速。

找到能量最低的裂缝

FindPerSeam 函数的 DPC++ 实现如下:

cv::Mat FindPerSeam()
{
    cv::Mat energy_map = CalculateEnergy();
    int rows = energy_map.rows;
    int cols = energy_map.cols;

    cv::Mat M = energy_map.clone();
    M.convertTo(M, CV_32S);

    sycl::queue q(sycl::default_selector{});

    for (int i = 1; i < rows; ++i)
    {
        {
            sycl::buffer<int, 2> M_buf(M.ptr<int>(), sycl::range<2>(rows, cols));

            q.submit([&](sycl::handler &h)
                     {
                         auto M_acc = M_buf.get_access<sycl::access::mode::read_write>(h);

                         h.parallel_for(sycl::range<1>(cols), [=](sycl::id<1> j)
                         {
                             if (j == 0)
                             {
                                 M_acc[i][j] += std::min(M_acc[i - 1][j], M_acc[i - 1][j + 1]);
                             } else if (j == cols - 1)
                             {
                                 M_acc[i][j] += std::min(M_acc[i - 1][j - 1], M_acc[i - 1][j]);
                             } else
                             {
                                 M_acc[i][j] += std::min(
                                         {M_acc[i - 1][j - 1], M_acc[i - 1][j], M_acc[i - 1][j + 1]});
                             }
                         });
                     });
        }
        q.wait_and_throw();
    }

    cv::Mat seam(rows, 2, CV_32S);
    int min_j = 0;
    double min_val;
    cv::minMaxIdx(M.row(rows - 1), &min_val, nullptr, &min_j, nullptr);
    seam.at<int>(rows - 1, 1) = min_j;

    for (int i = rows - 1; i > 0; i--)
    {
        if (min_j == 0)
        {
            cv::minMaxIdx(M.row(i - 1).colRange(min_j, min_j + 2), &min_val, nullptr, &min_j, nullptr);
        } else if (min_j == cols - 1)
        {
            cv::minMaxIdx(M.row(i - 1).colRange(min_j - 1, min_j + 1), &min_val, nullptr, &min_j, nullptr);
            min_j -= 1;
        } else
        {
            cv::minMaxIdx(M.row(i - 1).colRange(min_j - 1, min_j + 2), &min_val, nullptr, &min_j, nullptr);
            min_j -= 1;
        }
        seam.at<int>(i, 0) = i;
        seam.at<int>(i, 1) = min_j;
    }

    return seam;
}

从图像中移除裂缝

RemovePerSeam 函数的 DPC++ 实现如下:

void RemovePerSeam()
{
    cv::Mat seam = FindPerSeam();
    int rows = out_image.rows;
    int cols = out_image.cols;

    cv::Mat mask = cv::Mat::ones(rows, cols, CV_8U);

    for (int i = 0; i < seam.rows; ++i)
    {
        mask.at<uchar>(seam.at<int>(i, 0), seam.at<int>(i, 1)) = 0;
    }

    cv::Mat temp_image(rows, cols - 1, CV_8UC3);

    sycl::queue q(sycl::default_selector{});

    {
        sycl::buffer<uchar, 2> mask_buf(mask.ptr<uchar>(), sycl::range<2>(rows, cols));
        sycl::buffer<cv::Vec3b, 2> out_image_buf(out_image.ptr<cv::Vec3b>(), sycl::range<2>(rows, cols));
        sycl::buffer<cv::Vec3b, 2> temp_image_buf(temp_image.ptr<cv::Vec3b>(), sycl::range<2>(rows, cols - 1));

        q.submit([&](sycl::handler &h)
                 {
                     auto mask_acc = mask_buf.get_access<sycl::access::mode::read>(h);
                     auto out_image_acc = out_image_buf.get_access<sycl::access::mode::read>(h);
                     auto temp_image_acc = temp_image_buf.get_access<sycl::access::mode::write>(h);

                     h.parallel_for(sycl::range<2>(rows, cols - 1), [=](sycl::id<2> idx)
                     {
                         int i = idx[0];
                         int j = idx[1];

                         if (mask_acc[i][j])
                         {
                             temp_image_acc[i][j] = out_image_acc[i][j];
                         } else
                         {
                             temp_image_acc[i][j] = out_image_acc[i][j + 1];
                         }
                     });
                 });
    }
    q.wait_and_throw();

    out_image = temp_image.clone();
}

结论

本文演示了如何使用 Intel oneAPI 和 DPC++ 加速 seam carving 算法。通过利用 oneAPI 的并行处理能力,能够优化算法性能并实现更快的图像缩放。这种方法可以应用于其他计算机视觉算法和应用程序,以发挥 Intel oneAPI 和 DPC++ 的优势。

代码

#include <iostream>
#include <opencv2/opencv.hpp>
#include <ctime>

class SeamCarver {
public:
    SeamCarver(cv::Mat in_image, int out_height, int out_width)
            : in_image(in_image), out_height(out_height), out_width(out_width), count(0) {
        in_height = in_image.rows;
        in_width = in_image.cols;
        out_image = in_image.clone();
    }

    cv::Mat CalculateEnergy() {
        cv::Mat b, g, r;
        cv::Mat b_energy, g_energy, r_energy;
        cv::Mat energy_map;

        std::vector<cv::Mat> channels;
        cv::split(out_image, channels);
        b = channels[0];
        g = channels[1];
        r = channels[2];

        cv::Scharr(b, b_energy, CV_32F, 1, 0);
        cv::Scharr(b, b_energy, CV_32F, 0, 1);
        cv::Scharr(g, g_energy, CV_32F, 1, 0);
        cv::Scharr(g, g_energy, CV_32F, 0, 1);
        cv::Scharr(r, r_energy, CV_32F, 1, 0);
        cv::Scharr(r, r_energy, CV_32F, 0, 1);

        energy_map = cv::abs(b_energy) + cv::abs(g_energy) + cv::abs(r_energy);
        return energy_map;
    }

    cv::Mat FindPerSeam() {
        cv::Mat energy_map = CalculateEnergy();
        int rows = energy_map.rows;
        int cols = energy_map.cols;

        cv::Mat M = energy_map.clone();
        M.convertTo(M, CV_32S);

        for (int i = 1; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                if (j == 0) {
                    M.at<int>(i, j) += std::min(M.at<int>(i - 1, j), M.at<int>(i - 1, j + 1));
                } else if (j == cols - 1) {
                    M.at<int>(i, j) += std::min(M.at<int>(i - 1, j - 1), M.at<int>(i - 1, j));
                } else {
                    M.at<int>(i, j) += std::min({M.at<int>(i - 1, j - 1), M.at<int>(i - 1, j), M.at<int>(i - 1, j + 1)});
                }
            }
        }

        cv::Mat seam(rows, 2, CV_32S);
        int min_j = 0;
        double min_val;
        cv::minMaxIdx(M.row(rows - 1), &min_val, nullptr, &min_j, nullptr);
        seam.at<int>(rows - 1, 1) = min_j;

        for (int i = rows - 1; i > 0; i--) {
            if (min_j == 0) {
                cv::minMaxIdx(M.row(i - 1).colRange(min_j, min_j + 2), &min_val, nullptr, &min_j, nullptr);
            } else if (min_j == cols - 1) {
                cv::minMaxIdx(M.row(i - 1).colRange(min_j - 1, min_j + 1), &min_val, nullptr, &min_j, nullptr);
                min_j -= 1;
            } else {
                cv::minMaxIdx(M.row(i - 1).colRange(min_j - 1, min_j + 2), &min_val, nullptr, &min_j, nullptr);
                min_j -= 1;
            }
            seam.at<int>(i, 0) = i;
            seam.at<int>(i, 1) = min_j;
        }

        return seam;
    }

    void RemovePerSeam() {
        cv::Mat seam = FindPerSeam();
        int rows = out_image.rows;
        int cols = out_image.cols;

        cv::Mat mask = cv::Mat::ones(rows, cols, CV_8U);

        for (int i = 0; i < seam.rows; i++) {
            mask.at<uchar>(seam.at<int>(i, 0), seam.at<int>(i, 1)) = 0;
        }

        cv::Mat temp_image(rows, cols - 1, CV_8UC3);

        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols - 1; j++) {
                if (mask.at<uchar>(i, j)) {
                    temp_image.at<cv::Vec3b>(i, j) = out_image.at<cv::Vec3b>(i, j);
                } else {
                    temp_image.at<cv::Vec3b>(i, j) = out_image.at<cv::Vec3b>(i, j + 1);
                }
            }
        }

        out_image = temp_image.clone();
    }

    void SeamCarving() {
        for (int i = 0; i < in_width - out_width; i++) {
            clock_t t = clock();
            RemovePerSeam();
            count++;
            std::cout << "[INFO]: 一轮耗时 " << static_cast<double>(clock() - t) / CLOCKS_PER_SEC << ",已移除 " << count << " 列" << std::endl;
        }

        std::cout << out_image.size() << std::endl;
        cv::imwrite("./output/out_image.png", out_image);
    }

private:
    cv::Mat in_image;
    int out_height;
    int out_width;
    int count;
    int in_height;
    int in_width;
    cv::Mat out_image;
};

int main() {
    clock_t t = clock();
    std::string file_name = "./src/image.png";
    cv::Mat in_image = cv::imread(file_name, cv::IMREAD_COLOR);

    int out_height = in_image.rows;
    int out_width = in_image.cols / 2;
    SeamCarver obj(in_image, out_height, out_width);
    obj.SeamCarving();
    std::cout << "[INFO]: 总耗时 " << static_cast<double>(clock() - t) / CLOCKS_PER_SEC << std::endl;

    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值