ZH ·
🌏 English

利用 OpenCV 中的 & 运算符优雅地处理 ROI 边界裁剪

给定 ROI 的图像裁剪

假设需要按照既定的 ROI 对图像进行裁剪,通常使用 cv::Rect 定义区域,裁剪方式如下:

cv::Mat image = cv::imread("/path/to/image.jpg");
cv::Rect roi = cv::Rect(x, y, width, height);
cv::Mat crop = image(roi);

限制边界

如果 roi 的坐标超出了图像的合法区域,会引发运行时错误,导致程序崩溃。通常的做法是进行边界检查和规范化:

if(roi.x < 0) roi.x = 0;
if(roi.y < 0) roi.y = 0;
if(roi.x + roi.width >= image.cols) roi.width = image.cols - roi.x;
if(roi.y + roi.height >= image.rows) roi.height = image.rows - roi.y;

这种写法不仅冗长,而且可读性较差。另一种常见的写法是:

int w = image.cols;
int h = image.rows;

int x0 = std::max<int>(0, roi.tl().x);
int y0 = std::max<int>(0, roi.tl().y);
int x1 = std::min<int>(w, roi.br().x);
int y1 = std::min<int>(h, roi.br().y);

roi = cv::Rect(cv::Point(x0, y0), cv::Point(x1, y1));

这种方法虽然比前一种稍微清晰一些,但依然显得繁琐。冗长的代码不仅难以维护,而且由于重复操作同一变量的多个成员,非常容易引入低级错误。

使用 & 运算符获取 cv::Rect 交集

OpenCV 对 cv::Rect 重载了 & 运算符,其功能是计算两个矩形的相交区域。因此,要对 ROI 进行边界限制,只需将其与图像的边界框(Bounding Box)求交集即可:

cv::Rect bbox(0, 0, mat.cols, mat.rows);
cv::Rect roi = roi & bbox; // 一行代码搞定

这样,我们仅用一行代码就完成了复杂的边界限制。

扩展:检查矩形是否在图像区域内

如果需要判断一个 rect 是否完全位于图像区域内,如果不使用运算符重载,通常需要编写如下逻辑:

bool rectIsInside(const cv::Rect& rect, const cv::Mat& image)
{
    return (
        rect.x >= 0 && 
        rect.y >= 0 && 
        rect.x + rect.width <= image.cols && 
        rect.y + rect.height <= image.rows);
}

利用 & 运算符,实现会变得更加简洁高效:

bool rectIsInside(const cv::Rect& rect, const cv::Mat& image)
{
    cv::Rect bbox(0, 0, image.cols, image.rows);
    return (rect & bbox) == rect; // 优雅且高效
}

这种方式不仅代码量更少,而且逻辑直观,极大地提高了代码的可读性。