简介
Seam carving 是一种基于内容的图像缩放技术,可以在调整图像大小的同时保留图像中最重要的特征。这种技术特别适用于在不扭曲主要内容的情况下调整图像大小。本文将演示如何使用 Intel oneAPI 和 Data Parallel C++ (DPC++) 加速 seam carving 算法。
Intel oneAPI 是一种统一的编程模型,简化了在不同架构(如 CPU、GPU 和 FPGA)上开发应用程序的过程。DPC++ 是 C++ 的扩展,使开发人员能够使用单一源代码方法为异构系统编写并行代码。
使用 DPC++ 和 OpenCV 实现 seam carving 算法,利用 Intel oneAPI 的并行处理能力来优化算法性能。
环境设置
首先安装以下工具:
Seam Carving 算法
Seam Carving算法是一种用于图像缩放的算法,它可以在不改变图像比例的情况下,自动删除或插入像素,从而实现图像的缩放。
其基本思路如下:
-
计算能量图:首先,需要计算出图像中每个像素的能量值。能量值可以根据像素的颜、梯度等特征来计算,用于衡量像素的重要性。
-
找到最小能量路径:接下来,需要找到一条从图像顶部到底部的路径,使得路径上的像素能量之和最小。这条路径称为最小能量路径。
-
删除最小能量路径:将最小能量路径上的像素删除,即可将图像宽度缩小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;
}