寻找两幅噪声和光照变化图像之间的差异(直方图对齐等)

3
我正在尝试从照相机拍摄的图片中提取单个对象。存在一张没有物体的相机视图图片。相机的焦距不会改变,但是当有物体出现在视野中时,光照会发生变化。该物体是固体的,但灰度值不是恒定的。这里是右边的参考图像和左边带有物体的另一张图像的图片。这些图片都是单色的。
所有处理均在图像采集后进行,计算时间不是一个大问题。算法的精度更加重要。
我找到的大多数问题都涉及两张图片是否相似,但我想找到对象在图像中占据的区域,以进行后续测量。
到目前为止,我已经尝试了不同组合的模糊图像减法和二值化,但这不具备光照不变性。事先通过相对照明差异乘以参考图像也无法产生令人满意的结果。也许如果使用更好的模拟不同曝光的方式,它可以工作。
我还尝试了减去LoG过滤图像和一些粗糙的邻域像素比较,但都没有成功。
感觉这是一个非常直观的任务,某种描述符比较应该能够处理,但我正在努力寻找一个好的解决方案。有人知道我可能错过的好方法吗?
提前感谢您。
多亏了Franco Callari的回答,我偶然发现了直方图匹配,这让我惊讶的是OpenCV没有涵盖它的预制函数。由于这似乎是一个常见问题,我也可以在这里发布我的低效hack-up,以防有人想使用它。
// Aligns the histogram of the source image to match that of the target image. 
// Both are evaluated in a ROI, not across the whole picture.
// The function assumes 8-bit grayscale images.
static void alignHistogram(Mat alignsource, const Mat& aligntarget, 
    const int rowstart = 500, const int rowend = 700, 
    const int colstart = 0, const int colend = 1000)    
{   
    // 1) Calculate target and source histogram in region of interest
    // 2) Compute the integral of each histogram (cumulative distribution function)
    // 3) Set brightness of each pixel in the source image to the brightness of the target where the CDF values are equal

    Mat ROIsource = alignsource(Range(rowstart, rowend), Range(colstart, colend));
    Mat ROItarget = aligntarget(Range(rowstart, rowend), Range(colstart, colend));
    MatND hist_source, hist_target;

    int bins = 256, int histSize[] = {bins};
    float sranges[] = { 0, 256 };        const float* ranges[] = { sranges };
    int channels[] = {0};
    calcHist( &ROItarget, 1, channels, Mat(), hist_target, 1, histSize, ranges, true, false );
    calcHist( &ROItarget, 1, channels, Mat(), hist_source, 1, histSize, ranges, true, false );

    Mat CDF_target_2d, CDF_source_2d; 
    integral(hist_target, CDF_target_2d);
    integral(hist_source, CDF_source_2d);
    Mat CDF_target = CDF_target_2d.col(1),  CDF_source = CDF_source_2d.col(1);

    // Cycle through source image inefficiently and adjust brightness
    for (int i = 0; i < alignsource.rows; i++)
    {
        for (int j = 0; j < alignsource.cols; j++)
        {
            int source_brightness = alignsource.at<unsigned char>(i, j);
            double cdf_source_value = CDF_source.at<double>(source_brightness);

            int target_brightness = 0;
            while (target_brightness < 256) {
                if (CDF_target.at<double>(target_brightness) > cdf_source_value)
                    break;
                target_brightness++;
            }
            alignsource.at<unsigned char>(i, j) = target_brightness;
        }
    }
}

调整光线有助于获得更好的物体初步猜测,但这还不足以得到精确的轮廓,特别是当背景与物体相似或含有丰富的特征时。目前为止,我的工作就做到这里了。

5个回答

3
如果相机没有移动,背景也没有改变(从样本照片中看似乎是这样),整体照明差异很可能由以下两个因素之一引起 - 自动曝光的相机在场景中出现主体时选择了不同的f/stop或曝光时间,或者曝光时间窗口内存在时间变化的光源(例如灯具中的60Hz线性杂波)。如果照明器是闪光灯(并且给予闪光灯充电的足够时间),则后者应被排除。
我上面说“大部分”是因为,由于主题占据了画面的大部分,反射光也会影响整体照明,但在你的情况下,这很可能是一个次要影响。
您最好的方法可能是更好地控制捕获 - 至少,在相机中禁用自动曝光,使用平衡的灯光(如果不是闪光灯)。
如果您无法(或者另外),您应该从全局直方图对齐开始,而不是均衡化。其他帖子建议的全局直方图均衡化可能会有害,因为主体的像素值将成为直方图的一部分,而不仅仅是背景。但是,如果相机没有移动,您可以在图像帧中预先识别已知为背景的区域,并仅从它们中采样直方图,无论是“仅背景”还是“带主题”的图像。然后,您可以找到动态范围的顶部和底部5%处的值,并只应用全局缩放以使它们匹配。

我完全同意。在这种情况下,控制曝光可能行不通,因为物体会显著影响设置的照明 - 调整它似乎是最好的方法。可悲的是,我没有找到任何有关在OpenCV中对齐两个图像直方图的教程或函数。当我想出一些代码时,我稍后会更新我的帖子。如果您有任何关于直方图对齐的资源,我很乐意得到一些指针! - FvD
我会从简单的线性缩放开始进行“对齐”。在两个图像中计算出直方图的5%和95%处的像素值:它们定义了两个图像的“有效动态范围”(当然,您可以稍微调整边界,5和95只是一个好的起点),然后计算移位和比例,使得应用于图像2时,其有效动态范围等于图像1。您可以在Gimp或Photoshop中尝试一些示例图像(“调整级别...”),并查看它的效果如何,而无需编写任何代码。 - Francesco Callari
我想我明白你的意思,但我开始实现的是一种类似于查找表的方法,遵循这个过程,并通过参考区域中亮度平均差异的简单移位来进行。你可以在这些图片中看到结果。奇怪的是,其中最简单的方法似乎效果最好。我将您的帖子标记为被接受的答案,因为这显然是正确的第一步,尽管参考图片中的模糊细节仍然使得区分不同区域变得困难。 - FvD

1

1) 使用直方图均衡化(OpenCV中的cvEqualizeHist()函数)。它将帮助您消除光照差异。

2) 您将拥有两个几乎相同的图像(没有对象和有对象的图像,其他部分必须相同,因为相机是静态的)。通过函数取它们的差异。

void cvSub(
    const CvArr* src1,
    const CvArr* src2,
    CvArr*       dst,
    const CvArr* mask  = NULL)

3) 结果必须如下:物体所在的位置将几乎是白色的,图像的其余部分不会那么明亮。使用

void cvInRangeS(
    const CvArr* src,
    CvScalar     lower,
    CvScalar     upper,
    CvArr*       dst
);

这个函数可用于检查图像中的像素是否落在特定指定范围内。因此,选择接近白色的范围(例如(200、255))。

4)使用该函数找到图像中剩余物体的边缘。

int cvFindContours(
  IplImage*              img,
  CvMemStorage*          storage,
  CvSeq**                firstContour,
  int                    headerSize  = sizeof(CvContour),
  CvContourRetrievalMode mode        = CV_RETR_LIST,
  CvChainApproxMethod    method      = CV_CHAIN_APPROX_SIMPLE
);

正如Franco Callari所指出的那样,使用equalizeHist包括了视图中的对象并且效果不佳。您可以在这里查看均衡化后的图像和减法的结果:http://imgur.com/a/ubMt2。 - FvD

0
如果光照是时间变化的,您可以在HSV或HSI空间中利用色调信息,色调可能对光照具有强烈的不变性。如果您的图像是灰度的,则可以尝试一些直方图均衡化。

很遗憾,这张图片是黑白的。我应该注明的。 - FvD

0
我会从以下几个方面开始:
(i) 直方图均衡化 (ii) 两幅图像的差值(如果参考物体确实没有改变位置) (iii) 开运算(腐蚀、膨胀),尝试不同的掩模大小(3x3,5x5)
然后查看生成的图像。有多少“垃圾”留下了?如果在您感兴趣的区域之外有太多连接的像素,则可能需要增加开运算的掩模(或仅增加腐蚀的掩模)。然后,您可以将二进制图像作为掩模对原始图像进行过滤,以过滤出感兴趣的区域。

0
另一个选择是使用背景减法算法。虽然主要用于视频序列,但应该可以使用。我建议你使用opencv的Code Book Method
1. 给代码本方法你的输入图像。 2. 你有变量可以调整,告诉代码本方法它必须以多少帧作为背景,对于你的情况,这将是一个。 3. 结果是前景图像和背景图像。 4. 然后可以使用平滑来清除一些噪声。 5. 使用应该包含对象的前景图像(假设手指是您要查找的内容)。 6. 使用OpenCV方法查找轮廓。
希望这可以帮助到您。

该链接显示404错误,而我不幸地借出了我的《学习OpenCV》书籍。但据我所知,基于单个背景图像的前景提取基本上是对容忍度进行像素比较,即简单的减法和阈值操作。我错了吗?因为如果是这种情况,它似乎在这些图像上表现不佳,因为没有移动的背景,但有很多光照变化。虽然如此,如果我想要使用一组具有不同光照条件的背景图像,这是一个有趣的方法。 - FvD
你可能是对的,但它也在对输入进行平均处理,累积差异,然后排除前景和背景。我已经修复了链接。 - user349026

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