如何在环面环绕的图像中提取子图像?(OpenCV实现)
环面(Toroid)形式提取子图像的实现方案
嘿,先帮你捋清楚当前代码的问题,再给你搞定环面子图像提取的需求:
首先看你写的代码,有两个明显的问题:
- OpenCV里
Rect的构造参数是**x, y, width, height**,你写的Rect(rows - 1, cols - 1, 51, 51)把行(对应y轴)和列(对应x轴)搞反了,应该是Rect(cols - 1, rows - 1, 51, 51)才对。 - 即使修正了坐标顺序,当你的ROI超出原图像边界时,直接用
image(roi)截取会报错或者得到错误结果——因为普通的ROI截取不支持循环边界,而这正是你要的环面效果的核心:超出图像的部分从对侧补全。
OpenCV没有直接内置环面提取子图像的函数,但有两种简单的实现方式,推荐第一种更高效的:
方法一:用copyMakeBorder实现(推荐,简洁高效)
利用OpenCV的copyMakeBorder函数,它支持BORDER_WRAP这种边框填充模式,正好对应环面的循环效果——填充的部分直接取图像对侧的像素。步骤很简单:先给原图像做循环边界填充,再在填充后的图像上截取ROI。
代码如下:
#include <opencv2/core/core.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <opencv2/highgui/highgui.hpp> #include <iostream> using namespace cv; using namespace std; int main(int argc, char **argv) { Mat image = imread("image.jpg", IMREAD_GRAYSCALE); if (image.empty()) { cerr << "没法读取图像哦,检查下文件路径!" << endl; return -1; } // 定义你要提取的子图像尺寸 const int roiWidth = 51; const int roiHeight = 51; // 修正后的ROI起始坐标(x是列方向,y是行方向) const int roiX = image.cols - 1; const int roiY = image.rows - 1; // 计算需要填充的边界大小:如果ROI超出原图像,就补对应的边界 int top = max(0, -roiY); int bottom = max(0, roiY + roiHeight - image.rows); int left = max(0, -roiX); int right = max(0, roiX + roiWidth - image.cols); // 创建循环填充后的图像,BORDER_WRAP就是环面模式 Mat wrappedImage; copyMakeBorder(image, wrappedImage, top, bottom, left, right, BORDER_WRAP); // 在填充后的图像上截取正确的ROI Rect roi(roiX + left, roiY + top, roiWidth, roiHeight); Mat subImage = wrappedImage(roi); namedWindow("Toroid Subimage", WINDOW_AUTOSIZE); imshow("Toroid Subimage", subImage); waitKey(0); return 0; }
方法二:逐像素循环映射(适合理解原理)
如果想搞清楚底层逻辑,可以手动遍历子图像的每个像素,把坐标做循环映射(处理负数的情况,因为C++里负数取模结果是负数,要调整成正数):
#include <opencv2/core/core.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <opencv2/highgui/highgui.hpp> #include <iostream> using namespace cv; using namespace std; // 自定义环面子图像提取函数 Mat getToroidSubimage(const Mat& src, int roiX, int roiY, int roiWidth, int roiHeight) { Mat subImage(roiHeight, roiWidth, src.type()); const int srcCols = src.cols; const int srcRows = src.rows; for (int y = 0; y < roiHeight; ++y) { // 计算原图像的y坐标:循环映射,处理负数 int srcY = (roiY + y) % srcRows; if (srcY < 0) srcY += srcRows; uchar* subRow = subImage.ptr<uchar>(y); const uchar* srcRow = src.ptr<uchar>(srcY); for (int x = 0; x < roiWidth; ++x) { // 计算原图像的x坐标:循环映射,处理负数 int srcX = (roiX + x) % srcCols; if (srcX < 0) srcX += srcCols; subRow[x] = srcRow[srcX]; } } return subImage; } int main(int argc, char **argv) { Mat image = imread("image.jpg", IMREAD_GRAYSCALE); if (image.empty()) { cerr << "没法读取图像哦,检查下文件路径!" << endl; return -1; } const int roiWidth = 51; const int roiHeight = 51; const int roiX = image.cols - 1; const int roiY = image.rows - 1; Mat subImage = getToroidSubimage(image, roiX, roiY, roiWidth, roiHeight); namedWindow("Toroid Subimage", WINDOW_AUTOSIZE); imshow("Toroid Subimage", subImage); waitKey(0); return 0; }
额外注意点
- 废弃的
CV_LOAD_IMAGE_GRAYSCALE已经换成了推荐的IMREAD_GRAYSCALE,避免兼容性问题。 - 一定要加图像读取的判断,防止空图像导致崩溃。
- 记住OpenCV的坐标规则:
cols是x方向的宽度,rows是y方向的高度,Rect的参数顺序是x, y, width, height,别再搞反啦~
内容的提问来源于stack exchange,提问作者user8469759
相关产品推荐
相关产品推荐

