基础入门
在OpenCV中,图像的裁剪与拼接是非常基础但又非常实用的操作,在许多计算机视觉和图像处理任务中扮演着关键角色。
图像裁剪可以让我们聚焦于图像中的某个特定部分,这对于分析局部特征非常重要。通过裁剪掉无关紧要的部分,可以减少后续处理的数据量,从而提高处理速度和效率。另外,裁剪可以用于创建固定大小的输入,这对于训练深度学习模型尤其重要。
在某些应用场景下,单张图像的视场有限,通过拼接可以扩展视场范围,创建出更大视野的全景图。在图像融合和多模态数据处理中,拼接可以用来整合不同来源的信息。
图像裁剪
图像裁剪是指从原图中选取一个矩形区域,并将其提取出来作为新的图像。在OpenCV中,这可以通过访问图像矩阵的特定子集来实现,其接口原型类似于以下形式。
cpp
cv::Mat cropped_image = original_image(cv::Rect(x, y, width, height));
各个参数的含义如下。
original_image:原始图像,类型为cv::Mat。
x:裁剪区域的左上角横坐标。
y:裁剪区域的左上角纵坐标。
width:裁剪区域的宽度。
height:裁剪区域的高度。
在下面的实战代码中,我们使用imread读取了一张风景图片,并将其分割成16个相等的小图块。其中,水平方向和垂直方法均被分成4个。接着,程序会在一个更大的黄色画布上重新排列这些小图块,并在小图块之间留有15个像素的间距。最后,我们在窗口中显示了这个画布。
cpp
#include <opencv2/opencv.hpp>
using namespace cv;
#include <iostream>
using namespace std;
int main()
{
Mat image = imread("Landscape.jpg");
if(image.empty())
{
cout << "Can not open or find the image" << endl;
return -1;
}
// 计算每个小图块的尺寸
int image_width = image.cols;
int image_height = image.rows;
int tile_width = image_width / 4;
int tile_height = image_height / 4;
// 计算画布的尺寸,包括小图块之间的间隔
int canvas_width = 4 * (tile_width + 15) - 15;
int canvas_height = 4 * (tile_height + 15) - 15;
// 创建一个黄色背景的大画布,用于放置裁剪后的小图块
Mat canvas(canvas_height, canvas_width, CV_8UC3, Scalar(0, 255, 255));
// 将小图块放置到画布上
for (int row = 0; row < 4; ++row)
{
for (int col = 0; col < 4; ++col)
{
// 计算每个小图块的位置
int x_offset = col * (tile_width + 15);
int y_offset = row * (tile_height + 15);
// 裁剪原始图像
Rect roi(col * tile_width, row * tile_height, tile_width, tile_height);
Mat tile = image(roi);
// 将小图块复制到画布上的指定位置
tile.copyTo(canvas(Rect(x_offset, y_offset, tile_width, tile_height)));
}
}
imshow("Canvas", canvas);
waitKey(0);
destroyAllWindows();
return 0;
}
执行上面的示例代码,运行效果可参考下图。
横向纵向拼接
图像拼接通常涉及将两个或多个图像水平或垂直地连接在一起。OpenCV提供了多种方法来实现这一点,包括直接使用cv::hconcat和cv::vconcat函数。
cv::hconcat函数主要用于将多张具有相同高度的图像按照水平方向合并成一张新的图像,这对于并排比较不同图像非常有用。其接口原型如下。
cpp
// 用于两张图像的水平拼接
void hconcat(InputArray src1, InputArray src2, OutputArray dst);
// 用于多张图像的水平拼接
void hconcat(const vector<InputArray>& imgs, OutputArray dst);
cv::vconcat函数主要用于将多张具有相同宽度的图像按照垂直方向合并成一张新的图像,这对于上下比较不同图像非常有用。其接口原型如下。
cpp
// 用于两张图像的垂直拼接
void vconcat(InputArray src1, InputArray src2, OutputArray dst);
// 用于多张图像的垂直拼接
void vconcat(const vector<InputArray>& imgs, OutputArray dst);
需要特别注意的是,所有输入图像必须具有相同的深度和通道数。如果输入图像的尺寸不匹配,则需要先调整尺寸以满足要求。
下面的实战代码首先尝试加载两张图片,如果图片未能正确加载,则输出错误信息并退出。接下来,检查两张图片的高度是否一致,如果不一致,则调整第二张图片的高度以匹配第一张图片的高度,并进行横向拼接。然后,程序再次检查两张图片的宽度是否一致,如果不一致,则调整第二张图片的宽度以匹配第一张图片的宽度,并进行纵向拼接。最后,我们使用窗口分别显示了横向拼接和纵向拼接后的结果。
cpp
#include <opencv2/opencv.hpp>
using namespace cv;
#include <iostream>
using namespace std;
int main()
{
Mat img1 = imread("Hello.png");
Mat img2 = imread("C++.png");
if (img1.empty() || img2.empty())
{
cout << "Can not open or find the image" << endl;
return -1;
}
// 调整尺寸以匹配高度,用于横向拼接
Mat img2_resized;
if (img1.rows != img2.rows)
{
// 调整img2的高度以匹配img1
double scale = static_cast<double>(img1.rows) / img2.rows;
resize(img2, img2_resized, Size(), scale, scale);
img2 = img2_resized;
}
else
{
img2_resized = img2;
}
// 横向拼接
Mat img_h_concat;
hconcat(img1, img2_resized, img_h_concat);
// 调整尺寸以匹配宽度,用于纵向拼接
if (img1.cols != img2.cols)
{
// 调整img2的宽度以匹配img1
double scale = static_cast<double>(img1.cols) / img2.cols;
resize(img2, img2_resized, Size(), scale, scale);
img2 = img2_resized;
}
else
{
img2_resized = img2;
}
// 纵向拼接
Mat img_v_concat;
vconcat(img1, img2_resized, img_v_concat);
imshow("Horizontal Concat", img_h_concat);
imshow("Vertical Concat", img_v_concat);
waitKey(0);
destroyAllWindows();
return 0;
}
执行上面的示例代码,运行效果可参考下图。
灵活拼接
上面介绍的cv::hconcat或cv::vconcat函数只能用于横向拼接或纵向拼接,且需要图片的高度或宽度相同。如果需要更灵活地控制拼接过程,可以使用OpenCV中的copyTo函数。copyTo函数允许我们将源图像(或源图像的一部分)复制到目标图像的特定位置,其接口原型如下。
cpp
void Mat::copyTo(OutputArray dst, InputArray mask=noArray()) const;
各个参数的含义如下。
dst:目标图像或其一部分。它可以是一个预先分配好的cv::Mat对象,也可以是一个cv::OutputArray对象。
mask:可选参数,一个掩膜图像。类型通常是cv::Mat,用于指定哪些像素需要复制。默认为noArray(),表示不需要掩膜。
下面的实战代码首先尝试加载两张图片,如果图片未能正确加载,则输出错误信息并退出。接下来,创建一个具有特定尺寸的矩形画布,其尺寸是两幅图像宽度与高度之和,背景颜色设置为纯绿色。然后,第一幅图像被复制到这个画布的左上角位置,第二幅图像则被放置在画布的右下角位置。最后,我们使用窗口显示了包含两幅图像的画布。
cpp
#include <opencv2/opencv.hpp>
using namespace cv;
#include <iostream>
using namespace std;
int main()
{
Mat img1 = imread("Hello.png");
Mat img2 = imread("C++.png");
if (img1.empty() || img2.empty())
{
cout << "Can not open or find the image" << endl;
return -1;
}
// 创建绿色背景的画布
Size canvas_size(img1.cols + img2.cols, img1.rows + img2.rows);
Mat canvas(canvas_size, CV_8UC3, Scalar(0, 255, 0));
// 将左上角的图像复制到画布
img1.copyTo(canvas(Rect(0, 0, img1.cols, img1.rows)));
// 将右下角的图像复制到画布
Point pos(canvas.cols - img2.cols, canvas.rows - img2.rows);
img2.copyTo(canvas(Rect(pos.x, pos.y, img2.cols, img2.rows)));
imshow("Concat Canvas", canvas);
waitKey(0);
destroyAllWindows();
return 0;
}
执行上面的示例代码,运行效果可参考下图。