图像处理 阴影去除代码

2017-10-24 20:07:49 piaoxuezhong 阅读数 7904

暗角的概念

暗角一词属于摄影术语,是指一幅图像的四周的亮度或饱和度相比于中间部分的降低,画面四角有变暗的现象。暗角对于任何相机设置或镜头都不可避免。当然有时会故意引入暗角这种效果。

产生暗角的原因

(1)边角的成像光线与镜头光轴有较大的夹角是主要原因。沿着视场边缘的光线的前进方向看光圈,由于光线与光圈所在的平面有夹角,看到的光圈是椭圆的,所以通光面积减小。镜头光心到胶片的边缘距离较大,同样的光圈直径到达底片的光线夹角较小,亮度必然减小。同理,同样的光线偏角,对于边角光线位移较大,等价于照在较大的面积上。而面积是与位移的平方成正比的,所以综合上述原因,边缘亮度与光线和光轴夹角的cos值的4次方成正比。换句话说,广角镜头的边缘亮度随着视角变大急剧下降。


(2)长焦镜头,尤其是变焦长焦镜头,镜片很多,偏离光圈比较远的镜片为了能让边角光线通过,这些镜片必须很大。为了降低成本,缩小了这些镜片直径,造成边角成像光线不能完全通过,降低了边角的亮度。
(3)边角的像差较大。为了提高像质,某些镜片的边缘或专门设置的光阑有意挡住部分影响成像质量的边缘光线,造成边角失光。

暗角的分类

(1)自然暗角:表现为照片四角平缓暗化的渐变效果,这主要是由不同位置的光照进相机传感器的角度不同造成的。这种类型的照片暗角在用广角镜头拍摄的时候最为明显。:
(2)光学暗角:渐变同样也很平缓,但是它形成的主要原因是由镜头的固有特性,或者由镜筒自身的阴影造成。光学暗角也最终决定了一个镜头的成像圈的尺寸。这种暗角多在使用大光圈的情况下出现,并且受特定的镜头设计的影响很大。
(3)机械暗角:通常非常突兀,且只存在于照片的四角,通常是由遮光斗、滤镜环或其他安置在镜头前的遮光器材造成的。这种暗角如果由大光圈镜头或者变焦镜头来拍的话渐变效果就会略微平缓一些,没有那么突兀,也可以使用长焦从而避免这种效果。

po几张带暗角效果的原始图像:



去暗角算法

去阴影的算法有多种,例如参考【7,8】里的方法,本文主要讨论基于熵的去暗角算法,在论文《Retrospective shading correction based on entropy minimization》【1】中介绍了基于熵最小化的阴影校正(暗角自然也是一种阴影形式)方法,另外论文【2】《Single-Image Vignetting Correction by Constrained Minimization of log-Intensity Entropy》进一步讨论了论文【1】中方法的局限,主要是指局部最优的问题,并提出了一种基于对数熵的方法,这里大概讲述一下论文【2】中方法的思路,另见参考[4]。其主要内容有三部分:一是关于对数熵的评价准则;二是阴影去除方法的建模;三是模型参数的优化,使得熵值最小。

(1)对数熵

关于熵的概念,有点抽象,在维基里有很详细的解释,请参见:https://en.wikipedia.org/wiki/Entropy,这里只po一下连续随机变量的信息熵公式:

,

其中,X为图像灰度的分布,f(x)为概率密度函数,当X乘以一个参数c时,熵变为:


当c>1时,ln|c|>0.

(2)暗角建模

首先将灰度进行对数映射:

即将[0,255]的像素值基于对数关系映射到[0, N-1]内,通常N取256,这样映射后的像素范围还是[0,255]。映射后的直方图为:

那两个数学符号了,是向上取整和向下取整的意思,作和是一种线性权重取值方式。此时的对数熵直方图由于巨大的色阶调整,会出现直方图信息缺失,需要进行高斯平滑得到新的直方图。经过高斯平滑后的离散熵为:

论文给出的阴影函数反函数,也称增益函数g 为:

, g的范围为:1<g<1

其中,(x,y)是图像点坐标,x,y均值表示图像的中心位置。可以看到,当r=0时,校正系数=1,即无需校正。当r=1时,校正系数为1+a+b+c。经过暗角校正后的图像就为:


图像从暗角的中心点到四周是逐渐变暗的,函数g是随着r单调递增的,因此函数g的一阶导数大于0,即:

,由r>0,可以得到:

,令q2=r,那么转换为:

,方程解为:

,根据g在所属范围内的单调递增特性,推出C1~C5,并保证了r=0和1时,g>0,并且在范围0~1内,g不等于0;


(3)参数最优化

在第二部分,建立暗角模型后,问题便转化为一个最优化问题,最优条件是调参使得对数熵最小。文中介绍的求最优方法是爬山法:即从一个随机的初始解开始,逐步找到一个最优解。 因为模型中有多个参数,通过爬山法逐步获得最优解的过程中可以依次分别将某个参数的值增加或者减少一个单位。

算法实现:(待完成)

参考:

  1. https://en.wikipedia.org/wiki/Vignetting
  2. http://blog.csdn.net/omade/article/details/17449471
  3. http://www.xinpianchang.com/e881
  4. http://www.cnblogs.com/Imageshop/p/6166394.html
  5. http://blog.csdn.net/grafx/article/details/68958815
  6. 《Single-Image Vignetting Correction by Constrained Minimization of log-Intensity Entropy》[J].IEEE
  7. 《Single-Image Vignetting Correction》[J].IEEE
  8. 《Single-Image Vignetting Correction Using Radial Gradient Symmetry》[J].IEEE
2019-05-31 17:45:29 WHU_StudentZhong 阅读数 1876

     最近在做一个遥感图像处理的任务,觉得比较有意思,就拿出来跟大家分享一下。

     这次的任务是遥感图像的阴影提取,看上去好像有一点高大上的样子,让人有些摸不到头脑。我先到网上查找了一下,主要的方法都是用二值化,配合Canny算子或者Sobel算子之类的来提取阴影的面积,但是我觉得这样做比较复杂,而且效果也不一定很好。于是我就变了一个思路,来用阴影本身的特点来进行提取。

     首先要说明的是,我要处理的图像都是光照条件比较好的,所以阴影部分的亮度就会比其他区域的亮度明显低不少;另外,由于亮度的降低,导致其色调比较浅,所以R、G、B三个通道数值的方差肯定比较小。为了编写代码,我先做了一些尝试,最后确定了效果比较好的阈值,最后计算了阴影面积的比重,并显示在窗口的标题上。

         下面我们来看一下效果

                                               

                                                                                        原图

                                             

                                                                                    阴影图

           

                                              最终效果图                                                                    强力提取效果图

            从图中可以看到,最终的效果还是非常好的,目测估计准确率在90%以上(哈哈哈)

            下面是其他图片的处理效果

      无论是遥感影像还是生活照的效果都不错哦。

      下面就是代码部分了。(提醒:环境是VS2017+OPENCV4.1)

       大家有什么好的办法,记得和我讨论分享一下哦。(^_^)

#include "pch.h"
#include <iostream>
#include <opencv2/opencv.hpp>
#include <math.h>
using namespace std;
using namespace cv;

double varOf3(double x1, double x2, double x3) {
	double mean = (x1 + x2 + x3) / 3;
	return (pow((x1 - mean), 2) + pow((x2 - mean), 2) + pow((x3 - mean), 2));
}
Mat ShadowExtraction(Mat src,int type=1);

int main()
{
	//读取图片
	char filename[] = "Color4.bmp";
	Mat src = imread(filename);
	resize(src, src, Size(720, (720 * src.rows / src.cols)));//将图像的尺寸缩放到适合屏幕观看
	imshow("Previous", src);
	ShadowExtraction(src);
	return 0;
}

Mat ShadowExtraction(Mat src, int type ) {
	int rows = src.rows, cols = src.cols;
	Mat M(rows, cols, CV_8UC1);
	double* var = new double[rows*cols];
	int i = 0;
	for (int x = 0; x < cols; x++) {
		for (int y = 0; y < rows; y++) {
			if (((double)src.at<Vec3b>(y, x)[0] + (double)src.at<Vec3b>(y, x)[1] + (double)src.at<Vec3b>(y, x)[2]) < 250) {//限制亮度
				var[i] = sqrt(varOf3((double)src.at<Vec3b>(y, x)[0], (double)src.at<Vec3b>(y, x)[1], (double)src.at<Vec3b>(y, x)[2]));
			}
			else {
				var[i] = 255;
			}
			i++;
		}
	}
	int j = 0;
	for (int x = 0; x < cols; x++) {
		for (int y = 0; y < rows; y++) {
			M.at<uchar>(y, x) = (uchar)(var[j]);//把方差作为亮度进行赋值
			j++;
		}
	}
	//imshow("Before Binarization", M);
	switch (type) {
	case 1://轻度阴影提取
		equalizeHist(M, M);//可以过滤掉颜色比较浅的部分
		break;
	case 2://强力阴影提取
		break;
	}
	/*int block_size = 25;
	int const_value = 10;
	adaptiveThreshold(M, M, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY_INV, block_size, const_value);*/
	threshold(M, M, 70, 255, THRESH_BINARY_INV);//把图像二值化
	medianBlur(M, M, 7);//中值滤波,去除小斑点
	imshow("Shadow", M);
	Mat M3 = src.clone();//M3是最后阴影提取后的结果
	double count = 0.0;
	for (int x = 0; x < cols; x++) {//给识别出来的阴影上色
		for (int y = 0; y < rows; y++) {
			if (M.at<uchar>(y, x) == 255) {
				M3.at<Vec3b>(y, x)[0] = 255;
				M3.at<Vec3b>(y, x)[1] = 0;
				M3.at<Vec3b>(y, x)[2] = 0;
				count++;
			}
		}
	}
	char str1[64] = "Shadow in Previous";
	char str2[64];
	sprintf_s(str2, "  ||  Weight of Shadow:%.2lf%%", 100.0*count / rows / cols);//阴影量
	strcat_s(str1, str2);
	//erode(M3, M3, Mat());//腐蚀
	//dilate(M3, M3, Mat());//膨胀
	imshow(str1, M3);//显示最后处理结果的


	waitKey(0);

	delete[] var;
	return M3;
}

   

 

2016-06-23 16:47:12 tiemaxiaosu 阅读数 6311
一、阴影的特点及处理方法

    在室内监控场景内,考虑运动人体之间的遮挡和光照不均匀等因素,可能会产生阴影。阴影和运动目标一样,与背景有明显的差别,并作相同的运动。因而在进行运动目标检测时,阴影会被误认为是目标,对目标提取影响比较显著,并会对后续的跟踪、行为识别,产生负面影响。因此在运动目标检测阶段,应将阴影部分去掉。

   阴影有许多不同类型。一种是目标自身的部分位置因没有光照而产生的阴影,称为自阴影(self-shadow);另外一种是由于光照在对象的周围产生的阴影,称为投影阴影(cast-Shadow)。本文只考虑后者。通常,去除阴影的方法可分为基于模型的方法和无模型的方法两种。基于模型的方法是对场景中的目标根据先验知识,建立特征模型以区分阴影和目标,该方法效果较好,但建模的过程复杂耗时,通常应用在特定的场合无模型的方法则是从人们的视觉印象出发,利用目标和阴影的光照特性、颜色、几何特征来区别,该方法的计算量少。

   按照人们的视觉系统对阴影的认识,可以把它看作半透明区域,具有三个特点:

    (1)相较原背景,被阴影遮挡的背景像素点的颜色有所改变,但变化不大。
    (2)这些像素点的饱和度变小了。
    (3)这些像素点的亮度明显降低了,即阴影使背景变得更黑。

    这三个特点可以从图3.4中看到。本文根据这一特征,采用无模型的方法实现阴影的识别与去除。由于RGB颜色空间表示的图像是将色度和亮度信息混合进了R、G、B三个分量中,而对阴影的处理需要利用独立的色度和亮度信息。因而本文将图像转换到HSV颜色空间来进行阴影的检测与消除。


                      


二、HSV空间的阴影检测与消除

    下面在HSV颜色空间对阴影进行处理,将该区域内的像素值与背景模型中对应的像素点值相比较,若其包含的相应色度和饱和度都小于一定的阈值,那么就认为该点为阴影。

    具体步骤如下:
    第一步:颜色空间转换与通道分离。

    将当前帧和背景图像从RGB颜色空间转换到HSV颜色空间表示,经过通道分离,获得色度H、饱和度S和亮度V三个通道的图像。

    第二步:阴影检测。

    已获得了当前帧的前景目标区域,这些像素点包含了真正的目标像素,还有阴影像素。这里将当前图像H,S通道的图像中属于前景目标区域的各个像素点与背景图像的对应像素点相减,其差与阈值比较。若像素点的色度H和饱和度S均比阈值小,则该像素点为阴影。设矩阵舶口如Ⅵ表示各点是否为阴影,公式如(3.19)所示。

          

    其中,Imagek表示第 k 帧图像,backgroundk表示背景图像,a表示光照的强弱,光线越强,a值越小,由于阴影部分的像素点的亮度V必然是小于非阴影部分像素点的亮度,故B < l。

    第三步:阴影消除。

    在前景图中将阴影矩阵 shadowk 为 1 的对应像素点值置为 O(黑色),消除阴影。

   与RGB颜色空间相比,使用HSV颜色空间可以对色度,饱和度,亮度信息分别处理,充分利用了图像的颜色信息。阴影处理的效果图如图3.5所示,(a)为第262帧为未经阴影处理的前景图像,(b)是按本文方法作了阴影处理后的前景图像,可以很明显地观察到左边目标下面的阴影被去除了。

              


   




2016-07-02 22:44:29 qq_26499769 阅读数 6406

相应的文章:

The Shadow Meets the Mask:

Pyramid-Based Shadow Removal


文章的主体思想:

1.进行区域分割选取出需去除的阴影部分、光亮的部分

2.阴影部分的均值、方差、还有光亮部分的均值方差。通过均值比和标准差比可以利用下边两式求解,

这样便可预估参数



方程来源推导是该部分:






同时根据阴影部分的数据,这样可以得到光亮时的数据了

3.在区域还原时,会有阴影边缘会有一部分像素无法计算,这里的值使用去水印的方式,使用图像恢复还原。


2012-09-01 23:13:10 lvwx369 阅读数 14245
在运动目标检测中,常常会出现由于光线被遮挡,或场景其他物体的遮挡,在目标附近或场景里出现阴影,阴影的出现对后期目标的正确分割与处理带了很大的不便。如今,国内外已有不少文献来研究这个问题,并且提出了各种各样的阴影去除算法。本文就其中的一种算法提出了一个通用的实现算法,该算法考虑了背景与前景颜色相近的情况,希望能给大家一些帮助:(介绍下算法的思路:算法首先对RGB颜色空间的值进行归一化处理,即:r=R/(R+G+B),g=G/(R+G+B), I=(R+G+B)/3。然后利用背景和当前帧r,g的插值和I的比例来确定阴影区域:
                                                              基于Opencv的目标检测与跟踪阴影去除算法实现  
详细细节请参考文献:Detecting moving objects,ghosts and shadows in video streams
// shadeImg is a binary image,the value th2 and th3 are chosen empirically here,set th2=0.6 th3=1.5, th4=7
void ShadeDetect(IplImage *currImg, IplImage *bkImg, IplImage *shdadeImg,double th1,double th2,double th3,double th4)
{
       cvZero(shdadeImg);
       unsigned char* currData;
       unsigned char* bkData;
       unsigned char* shadeData;
       int i=0,j=0;
       int height=currImg->height;
       int width=currImg->width;
       double rb=0,gb=0,Ib=0,Rb=0,Gb=0,Bb=0;
       double rt=0,gt=0,It=0,Rt=0,Gt=0,Bt=0;
      //CvScalar cs=cvScalarAll(255);
       for (i=0;i<height;i++)
       {
             currData=(unsigned char*)currImg->imageData+i*currImg->widthStep;
             bkData=(unsigned char*)bkImg->imageData+i*bkImg->widthStep;
             shadeData=(unsigned char*)shdadeImg->imageData+i*shdadeImg->widthStep;
            for (j=0;j<width;j++)
           {
                // current normalized
                  Rt=currData[j*3];
                  Gt=currData[j*3+1];
                  Bt=currData[j*3+2];
                  rt=Rt/(Rt+Gt+Bt);
                  gt=Gt/(Rt+Gt+Bt);
                  It=(Rt+Gt+Bt)/3;
                 // Bk normalized
                 Rb=bkData[j*3];
                 Gb=bkData[j*3+1];
                 Bb=bkData[j*3+2];
                rb=Rb/(Rb+Gb+Bb);
                gb=Gb/(Rb+Gb+Bb);
                Ib=(Rb+Gb+Bb)/3;
              // judge whether is shadeimg
              if (fabs(rt-rb)<=th1 && fabs(gt-gb)<th1 && It/Ib>=th2 && It/Ib<=th3 && fabs(It-Bt)<th4)
             {
                    shadeData[j]=255;
             }  
       }
   }
}