如何在OpenCV中获取实际位置处的图像像素?

23

我想获取图像中一个像素的RGB值。但该位置不是整数位置,而是实数值(X,Y)。我需要一个双线性插值的值。如何在OpenCV中实现?

非常感谢。


我建议您添加更多信息。您可能没有时间问一个简短的问题。 - Tae-Sung Shin
5个回答

50

对于亚像素访问,没有简单的函数可用,但我可以为您提供几个选项:

  1. 使用getRectSubPix函数,然后提取1像素区域:

cv::Vec3b getColorSubpix(const cv::Mat& img, cv::Point2f pt)
{
    cv::Mat patch;
    cv::getRectSubPix(img, cv::Size(1,1), pt, patch);
    return patch.at<cv::Vec3b>(0,0);
}
  • 使用像素为一的映射,采用更加灵活但不太精确的remap函数:

  • cv::Vec3b getColorSubpix(const cv::Mat& img, cv::Point2f pt)
    {
        cv::Mat patch;
        cv::remap(img, patch, cv::Mat(1, 1, CV_32FC2, &pt), cv::noArray(),
            cv::INTER_LINEAR, cv::BORDER_REFLECT_101);
        return patch.at<cv::Vec3b>(0,0);
    }
    
  • 自己实现双线性插值,因为它并不是什么高深的技术:

    cv::Vec3b getColorSubpix(const cv::Mat& img, cv::Point2f pt)
    {
        assert(!img.empty());
        assert(img.channels() == 3);
    
        int x = (int)pt.x;
        int y = (int)pt.y;
    
        int x0 = cv::borderInterpolate(x,   img.cols, cv::BORDER_REFLECT_101);
        int x1 = cv::borderInterpolate(x+1, img.cols, cv::BORDER_REFLECT_101);
        int y0 = cv::borderInterpolate(y,   img.rows, cv::BORDER_REFLECT_101);
        int y1 = cv::borderInterpolate(y+1, img.rows, cv::BORDER_REFLECT_101);
    
        float a = pt.x - (float)x;
        float c = pt.y - (float)y;
    
        uchar b = (uchar)cvRound((img.at<cv::Vec3b>(y0, x0)[0] * (1.f - a) + img.at<cv::Vec3b>(y0, x1)[0] * a) * (1.f - c)
                               + (img.at<cv::Vec3b>(y1, x0)[0] * (1.f - a) + img.at<cv::Vec3b>(y1, x1)[0] * a) * c);
        uchar g = (uchar)cvRound((img.at<cv::Vec3b>(y0, x0)[1] * (1.f - a) + img.at<cv::Vec3b>(y0, x1)[1] * a) * (1.f - c)
                               + (img.at<cv::Vec3b>(y1, x0)[1] * (1.f - a) + img.at<cv::Vec3b>(y1, x1)[1] * a) * c);
        uchar r = (uchar)cvRound((img.at<cv::Vec3b>(y0, x0)[2] * (1.f - a) + img.at<cv::Vec3b>(y0, x1)[2] * a) * (1.f - c)
                               + (img.at<cv::Vec3b>(y1, x0)[2] * (1.f - a) + img.at<cv::Vec3b>(y1, x1)[2] * a) * c);
    
        return cv::Vec3b(b, g, r);
    }
    

  • +1,展示了几个版本,我从来没有想到前两个。你的第三个实现中是否缺少涉及c的术语?例如(y0,x0)[0](1.f-a)(1.f-c)。 - Hammer
    不,我没有失踪。(y0,x0)[0]*(1.f-a)*(1.f-c) 是在以 uchar b = 开头的那一行计算的。 - Andrey Kamaev
    你的意思是在 "uchar b = (uchar)cvRound((img.atcv::Vec3b(y0, x0)[0] * (1.f - a) + img.atcv::Vec3b(y0, x1)[0] * a) * (1.f - c)" 这句话中吗?我没有看出来... - Hammer
    3
    您需要打开括号才能看到: "uchar b = (uchar)cvRound((img.atcv::Vec3b(y0, x0)[0] * (1.f - a) + img.atcv::Vec3b(y0, x1)[0] * a ) * (1.f - c)"。实际上,在发布之前,我已经测试了所有3个版本,并且它们产生相同的结果。 - Andrey Kamaev
    1
    啊,我现在明白了,我应该更仔细地阅读。谢谢你的解释。 - Hammer
    1
    @AndreyKamaev 为什么 remapgetRectSubPix 不够精确? - Alessandro Jacopson

    5
    很不幸,我没有足够的积分在接受答案上发布评论... 我调整了代码以适应我的问题,需要对浮点数的单通道矩阵插值。

    我想要一些直觉,哪种方法最快。

    我实现了Andrey Kamaev答案中的三种方法以及一个简单的最近邻(基本上只是将坐标四舍五入)。

    我使用了一个100x100的矩阵A,里面装满了垃圾。然后我又制作了一个400x400的矩阵B,其中填充了从A中插值而来的值,即:B(i,j) = A(i/4, j/4)。

    每次运行都进行了1000次,以下是平均时间:

    • 最近邻:2.173毫秒
    • getRectSubPix:26.506毫秒
    • remap:114.265毫秒
    • 手动:5.086毫秒
    • 不使用borderInterpolate的手动插值:3.842毫秒

    因此,如果你不太关心实际插值,只需要一个值,特别是当你的数据变化非常平稳时,最近邻算法可以获得超快的速度。对于其他情况,我会选择手动双线性插值,因为它似乎比其他方法快得更稳定。(OpenCV 2.4.9 - Ubuntu 15.10 Repo - Feb 2016)。

    如果你知道所有四个贡献像素都在矩阵的边界内,那么你可以使它基本上等效于最近邻算法 - 不过两者之间的差异相当微不足道。


    4
    双线性插值就是根据你所检查的像素点周围4个最近的像素点来加权计算其值。权重可以按以下方式计算。
    cv::Point2f current_pos; //assuming current_pos is where you are in the image
    
    //bilinear interpolation
    float dx = current_pos.x-(int)current_pos.x;
    float dy = current_pos.y-(int)current_pos.y;
    
    float weight_tl = (1.0 - dx) * (1.0 - dy);
    float weight_tr = (dx)       * (1.0 - dy);
    float weight_bl = (1.0 - dx) * (dy);
    float weight_br = (dx)       * (dy);
    

    您的最终值是通过每个像素与其相应权重的乘积之和来计算的。


    2
    使用映射技术可以更加高效地重复或者持续地完成任务。另一个优点是可以选择插值方法以及如何处理边界条件。最后,一些插值函数也可以在GPU上实现。 remap

    1
    只需注意别名和极端缩放问题。双线性插值并非解决所有问题的神奇技巧。它仅使用4个相邻像素。有时需要创建图像金字塔以确保正确采样数据。 - Vlad

    0
    这是一个使用 Python 获取浮点精度子像素值的示例。根据文档,它在背后使用双线性插值:
    import cv2 as cv
    
    def get_subpixel(img: cv.Mat, x: float, y: float):
        """Get interpolated pixel value at (@x, @y) with float precision"""
        patch = cv.getRectSubPix(img, (1,1), (x, y), np.zeros((1,1)), cv.CV_32F)
        if patch is not None:
            return patch[0][0]
        return None
    

    请注意,它使用了 @andrey-kamaev 建议的 getRectSubPix,但强制opencv在返回的补丁中使用浮点精度。

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接