2018-06-21 20:28:04 zx520113 阅读数 479

        5、图像滤波(平滑)

        图像滤波(平滑),即在尽量保留图像细节特征的条件下对目标图像的噪声进行抑制,是图像预处理中不可缺少的操作,其处理效果的好坏将直接影响到后续图像处理和分析的有效性和可靠性。

        常用的图像滤波算法高斯滤波、均值滤波、中值滤波、双边滤波等。

        卷积的定义:假设被卷积的图像为I,卷积核为K。

        

        I与K的二维离散卷积计算步骤如下:

        首先将K翻转成,然后用K沿着I的每一个位置相乘求和,得到full卷积,,从filter和image刚相交开始做卷积。


        valid卷积:

        当filter全部在image里面的时候,进行卷积运算。

        same卷: 

        当filter的中心(K)与image的边角重合时,开始做卷积运算。

        在OpenCV中通过调用signal.convolve2d()实现卷积。

 

        高斯滤波:高斯滤波(高斯平滑)是一种线性平滑滤波,适用于消除高斯噪声,广泛应用于图像处理的减噪过程。通俗的讲,高斯滤波就是对整幅图像进行加权平均的过程,每一个像素点的值,都由其本身和邻域内的其他像素值经过加权平均后得到。高斯平滑滤波器对于抑制服从正态分布的噪声非常有效。

        高斯卷积算子的构建:

        首先计算高斯矩阵:

        

        再计算高斯矩阵的和:

        最后用高斯矩阵除以本身的和,即归一化,得到高斯卷积算子:

         

        在OpenCV中,通过调用cv2.getGaussianKernel(ksize,sigma,ktype)实现一维水平方向上的高斯平滑,再进行一维垂直方向上的高斯平滑。

 

        均值平滑:均值滤波,是最简单的一种线性滤波操作,输出图像的每一个像素是核窗口内输入图像对应图像像素的平均值。其算法比较简单,计算速度快,但是均值滤波本身存在着固有的缺陷,即它不能很好地保护图像细节,在去除噪声的同时,也破坏了图像的细节部分,从而使图像变得模糊,不能很好地去除噪声点。但均值滤波对周期性的干扰噪声有很好的抑制作用。

        均值平滑:

        利用矩阵积分,计算出矩阵中任意矩形区域内的和,快速均值平滑:

        

        在OpenCV中,通过调用cv2.blur()实现均值滤波。

 

        中值滤波:中值滤波法是一种非线性平滑技术,将图像的每个像素用邻域 (以当前像素为中心的正方形区域)像素的中值代替 ,常用于消除图像中的椒盐噪声。中值滤波对脉冲噪声有良好的滤除作用,特别是在滤除噪声的同时,能够保护信号的边缘,使之不被模糊,但它会洗去均匀介质区域中的纹理。

        再OpenCV中,通过调用cv2.medianBlur()实现中值滤波。

 

        双边滤波:双边滤波(Bilateral filter)是一种非线性的滤波方法,是结合图像的空间邻近度和像素值相似度的一种折衷处理,同时考虑空域信息和灰度相似性,达到保边去噪的目的,具有简单、非迭代、局部的特点,能够对低频信息进行较好的额滤波。双边滤波器的好处是可以做边缘保存,这个特点对于一些图像模糊来说很有用。

        双边滤波根据每个位置的领域,对应该位置构建不同的权重模板,首先,构建winH*winW的空间距离权重模板,与构建高斯卷积核的过程类似,winH*winW均为奇数,0<=h<winH,0<=w<winW。

        

        然后,构建winH*winW的相似权重模板,是通过(r,c)处的值与其领域值得差值的指数衡量。

        

        最后将closenessWeight和similarityWeight的对应位置相乘,然后进行归一化,便可得到改位置的权重模板,将所得到的权重模板和该位置领域的对应位置相乘求和,最后就得到该位置的输出值。

        在OpenCV中,通过调用cv2.bilateralFilter()实现双边滤波。

        联合双边滤波与双边滤波不同之处在于,双边滤波是根据原图对每一个位置,通过该位置和其领域的灰度值的差的指数来估计相似性;而联合双边滤波是首先对原图进行高斯平滑,根据平滑的结果,用当前的位置及其领域的值得差来估计相似性权重模板。

2018-07-25 14:19:37 SweetWind1996 阅读数 923

漫水填充

漫水填充是一个非常有用的功能。它经常被用来标记或者分离图像的一部分以便对其进行进一步处理或分析。漫水填充也可以用来从输入图像获取掩码区域,掩码会加速处理过程,或只处理掩码指定的像素点。

floodFill() [2/2]

int cv::floodFill  ( InputOutputArray  image
    InputOutputArray  mask
    Point  seedPoint
    Scalar  newVal
    Rect *  rect = 0
    Scalar  loDiff = Scalar()
    Scalar  upDiff = Scalar()
    int  flags = 4 
  )    

 

Parameters

image Input/output 1- or 3-channel, 8-bit, or floating-point image. It is modified by the function unless the FLOODFILL_MASK_ONLY flag is set in the second variant of the function. See the details below. 
mask Operation mask that should be a single-channel 8-bit image, 2 pixels wider and 2 pixels taller than image. Since this is both an input and output parameter, you must take responsibility of initializing it. Flood-filling cannot go across non-zero pixels in the input mask. For example, an edge detector output can be used as a mask to stop filling at edges. On output, pixels in the mask corresponding to filled pixels in the image are set to 1 or to the a value specified in flags as described below. Additionally, the function fills the border of the mask with ones to simplify internal processing. It is therefore possible to use the same mask in multiple calls to the function to make sure the filled areas do not overlap. 
seedPoint Starting point. 
newVal New value of the repainted domain pixels. 
loDiff Maximal lower brightness/color difference between the currently observed pixel and one of its neighbors belonging to the component, or a seed pixel being added to the component. 
upDiff Maximal upper brightness/color difference between the currently observed pixel and one of its neighbors belonging to the component, or a seed pixel being added to the component. 
rect Optional output parameter set by the function to the minimum bounding rectangle of the repainted domain. 
flags Operation flags. The first 8 bits contain a connectivity value. The default value of 4 means that only the four nearest neighbor pixels (those that share an edge) are considered. A connectivity value of 8 means that the eight nearest neighbor pixels (those that share a corner) will be considered. The next 8 bits (8-16) contain a value between 1 and 255 with which to fill the mask (the default value is 1). For example, 4 | ( 255 << 8 ) will consider 4 nearest neighbours and fill the mask with a value of 255. The following additional options occupy higher bits and therefore may be further combined with the connectivity and mask fill values using bit-wise or (|), see cv::FloodFillFlags.

image是输入/输出图像,该图像可以是8位或浮点类型的单通道或三通道图像。
mask参数所代表的掩码既可以作为FloodFill()的输入值(此时它控制可以被填充的区域),也可以作为FloodFill()的输出值 (此时它指已经被填充的区域)。如果mask非空,那么它必须是一个单通道、8位、像素宽度和高度均匀比源图像大两个像素的图像(这是为了使内部运算更简单快速)。mask图像的像素(x+1,y+1)与源图像的像素(x,y)相对应。注意:FlooFill()不会覆盖mask的非零像素点,因此如果不希望mask阻碍填充操作时,将其中元素设为0.源图像img和掩码图像mask均可以用漫水填充来染色。

注意:如果漫水填充的掩码不为空,那么要用flags参数中间的比特值(第8~15位)来填充掩码图像(参考下文)。如果没有设置flags中间比特值,则取默认值1。如果填充了掩码后显示出来是黑色,不要感到奇怪,因为所设置的值(如果flags的中间值没有被设置)为1,所以如果要显示它,必须需要放大这个掩码图像的数值。

seedPoint是漫水填充的起点。

newVal是像素点被重新染色的值。

loDiff和upDiff表示负/正差最大值。举个通俗的例子,在很多农业肥料(尿素、化肥)的袋子上都标有类似±100g的字样,这表示一个范围,实际量可能比标准量少但最多少100g,实际量可能比标准量多,但最多多100g。在这里则表示,如果一个像素的值不低于被染色的相邻点减去loDiff且不高于其加上upDiff,那么这个像素就会被染色。注意:这里是和被染色的相邻点比较。如果参数包含CV_FLOODFILL_FIXED_RANGE,这时每个像素点都将与种子点而不是相邻点比较。

rect表示一个可选的参数,为RECT*型,有默认值0。用于设置floodfill函数将要重绘的最小边界矩形区域。

flags参数,此参数包含三部分。

第一部分:低8位部分(第0~7位)可以设为4或8,这个参数控制填充算法的连通性。如果设为4,填充算法只考虑当前像素的水平方向和垂直方向的相邻点;如果设为8,出上述相邻点外,还会包含对角线方向的相邻点。

第二部分:高8位部分(第16~23位)可以设为CV_FLOODFILL_FIXED_RANGE(如果设置为这个值,则只有当某个相邻点与种子像素之间的差值在指定范围内才能填充,否则考虑当前点与其相邻点是否落在指定范围)或者CV_FLOODFILL_MASK_ONLY(如果设置,函数不填充原始图像,而去填充掩码图像),所以必须输入符合要求的掩码。

第三部分:flags的中间比特(第8~15位)的值指定填充掩码图像的值。但如果中间比特值为0,则掩码将用1填充。所有flags可以通过OR操作连接起来。例如,如果想用8邻域填充,并填充固定像素值范围,是填充掩码而不是填充源图像,以及设填充值为47,那么输入的参数应该是:

flags = 8                           //8邻域填充
    |CV_FLOODFILL_FIXED_RANGE      //固定范围填充
    |CV_FLOODFILL_MASK_ONLY       //使用掩码图像
    |(47<<8)                     //填充值为47 并且将47左移8位

需要注意的是,在以上参数中newVal、loDiff、upDiff都是Scalar类型,所以它们可以同时处理三个通道。例如,loDiff = CV_RGB(20,30,40),则三种颜色的loDiff分别设为红色值20,绿色值30,蓝色值40。

 

示例:

#include <opencv2\opencv.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>
using namespace std;
using namespace cv;

Mat g_srcImage, g_dstImage, g_grayImage, g_maskImage;//掩码图像
int g_nFillMode = 1;
int g_nLowDifference = 20;//初始负差值20
int g_nUpDifference = 20;//初始正差值20
int g_nConnectivity = 4;//初始连通方式为水平方向和垂直方向
int g_nIsColor = true;//是否为彩色图像
int g_nUseMask = false;//是否使用掩膜图像
int g_nNewMaskVal = 255;//初始新的像素值为255

static void onMouse(int event, int x, int y, int, void*)//鼠标事件函数
{
	if (event != EVENT_LBUTTONDOWN)//如果鼠标左键没有按下直接返回
		return;
	Point seed = Point(x, y);//设置种子点
	int LowDifference = g_nFillMode == 0 ? 0 : g_nLowDifference;//为0则是空范围填充,否则设置为全局的g_nLowDifference
	int UpDifference = g_nFillMode == 0 ? 0 : g_nUpDifference;//为0则是空范围填充,否则设置为全局的g_nUpDifference
	//flags为4邻域填充填充值为g_nNewMaskVal并且左移8位,采用固定范围填充或者0
	int flags = g_nConnectivity + (g_nNewMaskVal << 8) + (g_nFillMode == 1 ? FLOODFILL_FIXED_RANGE : 0);
	//随机生成bgr的值
	int b = (unsigned)theRNG() & 255;
	int g = (unsigned)theRNG() & 255;
	int r = (unsigned)theRNG() & 255;

	Rect ccomp;//定义最小边界矩形范围
	//如果是彩色图像,新的染色值newVal为三通道彩色像素,否则为灰度像素
	Scalar newVal = g_nIsColor ? Scalar(b, g, r) : Scalar(r*0.299 + g*0.587 + b*0.114);

	Mat dst = g_nIsColor ? g_dstImage : g_grayImage;//输出图像(彩色图像或灰度图像)
	int area;//用于接收重绘像素个数
	if (g_nUseMask)//如果使用掩膜图像,填充掩膜
	{
		threshold(g_maskImage, g_maskImage, 1, 128, THRESH_BINARY);
		area = floodFill(dst, g_maskImage, seed, newVal, &ccomp,
			Scalar(LowDifference, LowDifference, LowDifference),
			Scalar(UpDifference, UpDifference, UpDifference), flags);
		imshow("mask", g_maskImage);

	}
	else
	{
		area = floodFill(dst, seed, newVal, &ccomp,
			Scalar(LowDifference, LowDifference, LowDifference),
			Scalar(UpDifference, UpDifference, UpDifference), flags);
		imshow("效果图", dst);
		cout << area << "个像素被重绘\n";
	}

	
}
int main()
{
	g_srcImage = imread("2.jpg");
	if (!g_srcImage.data)
	{
		printf("error");
		return false;
	}
	g_srcImage.copyTo(g_dstImage);
	cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);//转换为灰度图像
	g_maskImage.create(g_srcImage.rows + 2, g_srcImage.cols + 2, CV_8UC1);//掩码大小比源图像宽高都大两个像素,可以加快处理
	namedWindow("效果图", WINDOW_AUTOSIZE);
	createTrackbar("负差最大值", "效果图", &g_nLowDifference, 255, 0);//创建正/负差值轨迹条
	createTrackbar("正差最大值", "效果图", &g_nLowDifference, 255, 0);
	setMouseCallback("效果图", onMouse, 0);//鼠标回调函数
	while (1)
	{
		imshow("效果图", g_nIsColor ? g_dstImage : g_grayImage);

		int c = waitKey(0);
		if ((c & 255) == 27)
		{
			cout << "程序退出.........\n";
			break;
		}

		switch ((char)c)
		{
		case '1':
			if (g_nIsColor)//如果是彩色图像
			{
				cout << "键盘“1”被按下,切换彩色/灰度模式,当前操作为【彩色模式】切换为【灰度模式】";
				cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);
				g_maskImage = Scalar::all(0);//掩码为0
				g_nIsColor = false;//改变颜色参数
			}
			else//如果是灰度图像
			{
				cout<< "键盘“1”被按下,切换彩色/灰度模式,当前操作为【灰度模式】切换为【彩色模式】";
				g_srcImage.copyTo(g_dstImage);
				g_maskImage = Scalar::all(0);
				g_nIsColor = true;
			}
			break;
		case '2':
			if (g_nUseMask)
			{
				destroyWindow("mask");//销毁掩膜窗口
				g_nUseMask = false;
			}
			else
			{
				namedWindow("mask", 0);
				g_maskImage = Scalar::all(0);//设置掩码为0
				imshow("mask", g_maskImage);
				g_nUseMask = true;
			}
			break;
		case '3':
			cout << "按键“3”被按下,恢复原始图像\n";
			g_srcImage.copyTo(g_dstImage);
			cvtColor(g_dstImage, g_grayImage, COLOR_BGR2GRAY);
			g_maskImage = Scalar::all(0);
			break;
		case '4':
			cout << "按键“4”被按下,使用空范围漫水填充\n";
			g_nFillMode = 0;
			break;
		case '5':
			cout << "按键“5”被按下,使用渐变、固定范围的漫水填充\n";
			g_nFillMode = 1;
			break;
		case '6':
			cout << "按键“6”被按下,使用渐变、浮动范围漫水填充\n";
			g_nFillMode = 2;
			break;
		case '7':
			cout << "按键“7”被按下,操作标识符的第八位使用4位连接模式\n";
			g_nConnectivity = 4;
			break;
		case '8':
			cout << "按键“8”被按下,使用渐变、浮动范围漫水填充\n";
			g_nConnectivity = 8;
			break;

		}
	}
	destroyAllWindows();
	return 0;
}

结果展示:

 

图像金字塔和图片尺寸缩放

缩放图像最直接的方式--------resize()函数

◆ resize()
void cv::resize    (    InputArray     src,
OutputArray     dst,
Size     dsize,
double     fx = 0,
double     fy = 0,
int     interpolation = INTER_LINEAR 
)        
Python:
dst    =    cv.resize(    src, dsize[, dst[, fx[, fy[, interpolation]]]]    )

该函数可以将源图像精确转换为目标图像的尺寸。如果源图像中设置了ROI,那么resize()将会对ROI区域调整尺寸,以匹配目标图像,同样,如果目标图像中已设置ROI,那么resize()将会将源图像进行尺寸调整并填充到目标图像的ROI中。

参数:

Parameters
src    input image.
dst    output image; it has the size dsize (when it is non-zero) or the size computed from src.size(), fx, and fy; the type of dst is the same as of src.
dsize    output image size; if it equals zero, it is computed as:
dsize = Size(round(fx*src.cols), round(fy*src.rows))
Either dsize or both fx and fy must be non-zero.
fx    scale factor along the horizontal axis; when it equals 0, it is computed as
(double)dsize.width/src.cols
fy    scale factor along the vertical axis; when it equals 0, it is computed as
(double)dsize.height/src.rows
interpolation    interpolation method, see cv::InterpolationFlags
  该参数指定插值方法,默认为线性插值法。

常用的插值方法如下:

INTER_NEAREST 
Python: cv.INTER_NEAREST
nearest neighbor interpolation
INTER_LINEAR 
Python: cv.INTER_LINEAR
bilinear interpolation
INTER_CUBIC 
Python: cv.INTER_CUBIC
bicubic interpolation
INTER_AREA 
Python: cv.INTER_AREA
resampling using pixel area relation. It may be a preferred method for image decimation, as it gives moire'-free results. But when the image is zoomed, it is similar to the INTER_NEAREST method.

一般情况下,我们期望源图像和重采样后的图像之间的映射尽可能地平滑。参数interpolation控制如何进行映射。其中最简单的方法是将目标图像各点的像素值设为源图像中与其距离最近的像素值,这就是当interpolation设为INTER_NEAREST 时用的算法。或者采用线性插值算法(INTER_LINEAR ),将根据源图像附近的4个(2*2范围)邻近像素的线性加权计算得出,权重由着4个像素到精确目标点的距离决定。也可以用新的像素点覆盖原来的像素点,然后求取覆盖区域的平均值来代替目标像素点,即通过INTER_AREA 。

示例:

#include<opencv2\opencv.hpp>
#include<opencv2\imgproc\imgproc.hpp>
using namespace cv;

int main()
{
	Mat srcImage = imread("2.jpg");
	Mat tmpImage, dstImage1, dstImage2;
	tmpImage = srcImage;

	imshow("原图", srcImage);

	//进行尺寸调整
    //Size()指定目标图像尺寸
	resize(tmpImage, dstImage1, Size(tmpImage.cols / 2, tmpImage.rows / 2), (0, 0), (0, 0), INTER_AREA);//列数等于行长
	resize(tmpImage, dstImage2, Size(tmpImage.cols * 2, tmpImage.rows * 2), (0, 0), (0, 0), INTER_LINEAR);

	imshow("效果图1", dstImage1);
	imshow("效果图2", dstImage2);

	waitKey();
	destroyAllWindows();
}

实现效果:

 

 

 

注:效果图2(放大两倍的图像)经过网页自动调整可能不太明显。

 

图像金字塔

图像金字塔是一个图像的集合,集合中所有的图像都源于同一个原始图像,而且是通过对原始图像连续降采样获得,知道达到某个中值条件才停止采样。

高斯金字塔与拉普拉斯金字塔

高斯金字塔用来向下采样图像,尔拉普拉斯金字塔则用来从尽在他低层图像中向上采样重建一个图像。

◆ pyrUp()
void cv::cuda::pyrUp	(	InputArray 	src,
OutputArray 	dst,
Stream & 	stream = Stream::Null() 
)		
//Upsamples an image and then smoothes it.

向上采样函数,用于放大一个图像并通过滤波进行平滑。

该函数用于将现有的图像在每个维度上扩大为原来的两倍。

◆ pyrDown()
void cv::pyrDown	(	InputArray 	src,
OutputArray 	dst,
const Size & 	dstsize = Size(),
int 	borderType = BORDER_DEFAULT 
)		
Python:
dst	=	cv.pyrDown(	src[, dst[, dstsize[, borderType]]]	)
//Blurs an image and downsamples it.

向下采样函数,用于模糊一个图像并将其缩小。

需要注意的是pyrUp与pyrDown不是互为逆操作的。pyrDown是一个会丢失信息的函数,每当把图像缩小两倍,将会丢失很多像素信息,再通过pyrUp扩大为原尺寸,图像并不会和降采样之前一样而是会变得模糊。而由降采样操作丢失的信息,形成了拉普拉斯金字塔。

以下是一张高斯金字塔及其逆形式---拉普拉斯金字塔的运算过程(图片来源于网络)

由上图可以看出,原图通过高斯模糊一直以两倍的大小进行降采样,在采样的过程中一直丢失图像的信息,而这些信息保存在每层的拉普拉斯金字塔中。从最下层可以看出,从左至右,经过两次降采样的图像想进行逆采样重建图像。在每次向上操作的过程中都会加上每一层的拉普拉斯金字塔(也就是降采样时丢失的内容),最后便可以重建这张图像。

在进行图像分割时通常会现在金字塔高层的低分辨率的图像上完成,然后字逐层对分割加以优化。

需要特别注意的一点:由于图像金字塔各层的场合宽都必须是整数,所以必须要求其实图像的长和宽都能够被2整除(对每一层都是这样),并且能够被2整除的次数不少于金字塔的总层数。也就是说图像的长和宽都必须是2*2*2*---*2*X的大小。

resize和pyrUp、pyrDown的区别在于,resize可以对图片的尺寸进行方便的更改,而对于pyrUphepyrDown而言只能以2倍的大小进行放大和缩小。

示例:

#include <opencv2\opencv.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>

using namespace std;
using namespace cv;

#define WINDOW_NAME "【程序窗口】"

Mat g_srcImage, g_dstImage, g_tmpImage;

int main() 
{
	g_srcImage = imread("1.jpg");
	if (!g_srcImage.data)
	{
		printf("error!");
		return false;
	}

	namedWindow(WINDOW_NAME, WINDOW_AUTOSIZE);
	imshow(WINDOW_NAME, g_srcImage);

	g_tmpImage = g_srcImage;
	g_dstImage = g_tmpImage;

	int key = 0;

	while (1)
	{
		key = waitKey();
		switch (key)
		{
		case 27:
			destroyAllWindows();
			return 0;
		
			break;
		case 'q':
			destroyAllWindows();
			return 0;
		
			break;
		case 'a':
			pyrUp(g_tmpImage, g_dstImage, Size(g_tmpImage.cols * 2, g_tmpImage.rows * 2));
			break;
		case 'w':
			resize(g_tmpImage, g_dstImage, Size(g_tmpImage.cols * 2, g_tmpImage.rows * 2));
			break;
		case 'd':
			pyrDown(g_tmpImage, g_dstImage, Size(g_tmpImage.cols / 2, g_tmpImage.rows / 2));
			break;
		case 's':
			resize(g_tmpImage, g_dstImage, Size(g_tmpImage.cols / 2, g_tmpImage.rows / 2));
			break;
		

		}
		imshow(WINDOW_NAME, g_dstImage);
		g_tmpImage = g_dstImage;
	}
	destroyAllWindows();
	return 0;


}

结果:

阈值化

固定阈值操作:Treshold()函数

函数Treshold通常是对灰度图像进行阈值操作得到二值图像。

◆ threshold()
double cv::cuda::threshold	(	InputArray 	src,
OutputArray 	dst,
double 	thresh,
double 	maxval,
int 	type,
Stream & 	stream = Stream::Null() 
)		
//Applies a fixed-level threshold to each array element.

参数

Parameters
src    Source array (single-channel).单通道数组
dst    Destination array with the same size and type as src .和输入图像同大小、同类型的目标图像
thresh    Threshold value.阈值
maxval    Maximum value to use with THRESH_BINARY and THRESH_BINARY_INV threshold types.

使用THRESH_BINARY 和 THRESH_BINARY_INV 类型时的最大阈值。
type    Threshold type. For details, see threshold . The THRESH_OTSU and THRESH_TRIANGLE threshold types are not supported.阈值类型,其中THRESH_OTSU 和 THRESH_TRIANGLE无法实现。
stream    Stream for the asynchronous version.

程序示例:

#include <opencv2\opencv.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>

using namespace std;
using namespace cv;

#define WINDOW_NAME "【程序窗口】"

int g_nThresholdValue = 100;
int g_nThresholdType = 3;

Mat g_srcImage, g_grayImage, g_dstimage;


void on_Threshold(int, void*);

int main()
{
	g_srcImage = imread("1.jpg");
	cvtColor(g_srcImage, g_grayImage, COLOR_RGB2GRAY);
	namedWindow(WINDOW_NAME, WINDOW_AUTOSIZE);
	createTrackbar("模式", WINDOW_NAME, &g_nThresholdType, 4, on_Threshold);
	createTrackbar("参数值", WINDOW_NAME, &g_nThresholdValue, 255, on_Threshold);
	on_Threshold(0, 0);
	while (1)
	{
		int key;
		key = waitKey();
		if ((char)key == 27)
		{
			break;
		}
	}
	destroyAllWindows();
	return 0;
}
void on_Threshold(int, void*)
{
	threshold(g_grayImage, g_dstimage, g_nThresholdValue, 255, g_nThresholdType);
	imshow(WINDOW_NAME, g_dstimage);
}

 

2019-05-24 16:58:50 weiwei9363 阅读数 1211

内窥镜去反光的论文整理

源码在这里:

  • https://github.com/jiemojiemo/some_specular_detection_and_inpainting_methods_for_endoscope_image
  • https://github.com/jiemojiemo/FCN_Specular_Detection

这三篇内窥镜去反光的论文分别是:

  • Saint-Pierre C A, Boisvert J, Grimard G, et al. Detection and correction of specular reflections for automatic surgical tool segmentation in thoracoscopic images[J]. Machine Vision and Applications, 2011, 22(1): 171-180.
  • Meslouhi O, Kardouchi M, Allali H, et al. Automatic detection and inpainting of specular reflections for colposcopic images[J]. Open Computer Science, 2011, 1(3): 341-354.
  • Arnold M, Ghosh A, Ameling S, et al. Automatic segmentation and inpainting of specular highlights for endoscopic imaging[J]. Journal on Image and Video Processing, 2010, 2010: 9.

Detection and correction of specular reflections for automatic surgical tool segmentation in thoracoscopic images

反光检测

这篇文章的反光检测主要思想是:自动取阈值。大于阈值的区域被认为是反光

主要步骤:

  1. 图像增强。这一步的作用是让反光区域更加明显,让非反光区域更不明显,减少干扰这一步的作用是让反光区域更加明显,让非反光区域更不明显,减少干扰
    enhaced_img = reflection_enhance(I)
    
  2. 直方图去噪。首先统计图像的直方图分布,然后对分布曲线做去噪。这一步中,论文中没有说清楚是用RGB彩色图像统计分布还是用灰度图,在我的实现中,我用的是灰度图。其中去噪的算法用到了一个小波去噪的算法,总之,有点复杂。好在MATLAB 2018中已经封装好了这个去噪算法wdenoise(其他版本的我不知道有没有)
    denoised_hist = histogram_denoise(enhanced_gray);
    
  3. 根据直方图找到阈值。这一步对denoised_hist做了两次类似一阶导的操作,取最后一个不为零的位置作为阈值
    threshold = find_specular_bump_threshold(denoised_hist);
    
  4. 反光区域扩展。经过第三步,将大于threshold的区域试做反光点,现在我们要从点扩散到区域。但是很不幸,论文没有说清楚这个扩展如何做,只是比较含糊的说“反光点周围区域也是反光;这些反光区域的值比较中心点亮度有所下降,但是它们还是反光,因此啊,我们遍历反光点的周围的像素,判断这些像素是不是反光”。论文中即没说清楚这个“周围”是多大,也没有说清楚亮度衰退的模型时什么。因此,在个人实现中,之间跳过这一步,只用imdilate来简单扩展反光区域
    specular_spike_mask = (enhanced_gray >= (threshold));
    specular_mask = imdilate(specular_spike_mask, strel("diamond", 1));
    

这篇文章的检测算法十分的不靠谱,阈值很难正确的被找到。

反光修复

这篇文章的反光修复用的是图像修复技术:Fast digital image inpainting

图像修复我就不讲了,没有去研究,代码也是找的别人实现的,这篇图像修复的代码非常简单

实验结果

fig8_1

fig9_1

Automatic detection and inpainting of specular reflections for colposcopic images

反光检测

主要思想:反光像素的亮度Y(lunminace)大于 它的彩色亮度y(chromatic luminance)

主要步骤:

  1. 图像增强。和前一篇文章一样
    enhanced = ReflectionEnhance(img);
    
  2. 获取亮度Y。RGB转CIE-XYZ空间就行了
    enhanced_xyz = rgb2xyz(enhanced);
    Y = enhanced_xyz(:,:,2);
    
  3. 获取彩色亮度y。这一步论文的公式(4)和公式(7)好像都是,我用的是公式(4),因为我没有搞懂这个colors normalization是个什么鬼。
    y = enhanced_xyz(:,:,2)./(enhanced_xyz(:,:,1) + enhanced_xyz(:,:,2) + enhanced_xyz(:,:,3));
    
  4. 获取反光区域。这个简单,Y >= y 的像素都是反光
    specular_mask = Y >= y;
    

这篇论文比上一篇取阈值的方法靠谱多了,我没有做很详细的测试,仅是原论文的测试图片,反光检测还是很不错的。算法的速度肯定是很快的,几乎没有耗时的部分,但是总觉得太简单了,可能没有很强的鲁棒性。

反光修复

同样的,还是用的图像修复的方法:Digital Inpainting Survey and Multilayer Image Inpainting Algorithms

没有找到这篇文章的代码,所以我没有实现修复部分

实验结果

只有反光的结果

fig10_a

fig10_b

Automatic segmentation and inpainting of specular highlights for endoscopic imaging

反光检测

反光检测的主要思想:每个像素位置都有一个叫“平滑非反光区域颜色像素”的东西,如果像素的值大于这个东西就被认为是反光

我觉得这个思想很有趣,算法等于是给图片提取了一个特征,通过与特征相比较来得到反光区域

主要步骤:

  1. 模块一,获得高光区域。这个步骤是为了获取到那些很明显的高光区域,并将它们视为反光。代码主要实现的是论文中的公式(2)和公式(3),不太难
    cR = double(img(:,:,1)); 
    cG = double(img(:,:,2));
    cB = double(img(:,:,3));
    cE = 0.2989*cR + 0.5870*cG + 0.1140*cB;
    % module 1
    module1_specular_mask = calc_module1_specular_mask(cE, cG, cB, T1);
    
  2. 模块二,每个像素与“平滑非反光区域颜色像素”比较,得到反光区域。这个模块比较复杂,我们分开将
    2.1 填充反光区域。这一步将对反光区域进行填充,如何填充呢?那就是用反光区域周围一圈的像素平均值。这一步主要是为了后面中值滤波做准备。如果不进行填充,那么反光区域被中值滤波后,仍然是高光,这样就没有区分度了(注意,中值滤波后的图像被视作“平滑非反光区域颜色像素”,因此它不应该是高光)
    specular_mask_T2_abs = calc_module1_specular_mask(cE, cG, cB, T2_abs);
    filled_img = filling_image_using_centroid_color(specular_mask_T2_abs, img);
    
    2.2 对填充图像做中值滤波,得到"平滑非反光区域颜色像素"。这步简单,之间medfilt2就可以了。中值滤波后的图像被认为是"平滑非反光区域颜色"图像
    2.3 反光检测。主要是公式(4)(5)(6)(7),没啥好说的,上代码
    fR = double(medfilt2(filled_img(:,:,1), [30 30], 'symmetric'));
    fG = double(medfilt2(filled_img(:,:,2), [30 30], 'symmetric'));
    fB = double(medfilt2(filled_img(:,:,3), [30 30], 'symmetric'));
    filtered_img = cat(3, fR, fG, fB);
    
    fR(fR < eps) = 1e7;
    fG(fG < eps) = 1e7;
    fB(fB < eps) = 1e7;
    
    % contrast coefficient
    tR = contrast_coeffcient(single(cR));%tR = 1;
    tG = contrast_coeffcient(single(cG));%tG = 1;
    tB = contrast_coeffcient(single(cB));%tB = 1;
    
    max_img = cat(3, tR*cR./fR, tG*cG./fG, tB*cB./fB);
    e_max = max(max_img, [], 3);
    module2_specular_mask = e_max > T2_rel;
    
  3. 后处理。这部分我看的不清不楚的,直接跳过,影响不大,代码中只是简单的写了一个对反光区域大小检测的判断。

反光修复

这篇文章的反光修复终于不是用图像修复了

首先,算法用反光检测中的填充,将反光区域进行填充,然后做一个高斯模糊,再然后结合原图和高斯模糊的图进行修复。效果相当不错!

主要步骤:

  1. 图像填充。与反光检测中一样的
    filled_img = filling_image_using_centroid_color(specular_mask, img);
    
  2. 高斯模糊
    sig = 8;
    gaussian_filtered_img = imgaussfilt(filled_img, sig);
    
  3. 计算两张图像的权重。原论文的方法太麻烦了,我自己用卷积实现了一个类似的。总之,靠近反光中心的权重要大,远一点的要小,就可以了。
    mx = imfilter(double(specular_mask), ones(decay_win_size)/decay_cof);
    mx = mx + specular_mask;
    mx(mx > 1) = 1.0;
    
  4. 结合两张图像,filled_imggaussian_filtered_img。很简单,加权相加就可以了
    inpainted_img = mx.*double(gaussian_filtered_img) + (1-mx).*double(img);
    

实验结果

fig5_a
fig6_a
fig6_b
fig7_a
fig7_b

关于去反光的数据库

本人主要的研究方向是深度学习+图像增强,因此对深度学习比较熟悉,既然是深度,那就少不了数据集了。

在研究内窥镜去反光的时候,发现相关的数据集很少,只有CVC这个实验室提供了较为完整数据集,现在分享给大家。

内窥镜图像数据集:

  • CVC-ColonDB:http://www.cvc.uab.es/CVC-Colon/index.php/databases/
  • CVC-ClinicDB:http://www.cvc.uab.es/CVC-Colon/index.php/databases/

内窥镜反光及其label:

  • CVC-EndoSceneStill(需要注册):http://www.cvc.uab.es/CVC-Colon/index.php/databases/cvc-endoscenestill/
  • CVC-ClinicSpec(密码我已经帮你们搞到了,不用谢:cvcclinicspec):http://www.cvc.uab.es/CVC-Colon/index.php/cvc-clinicspec/

目前用深度学习做去反光的研究较少,只看到仅仅一篇还不错的:

  • Generative adversarial networks for specular highlight removal in endoscopic images

我模仿作者的想法,用CycleGAN实现了去反光的试验,效果不错,但是有几个问题:

  • 数据集难收集,我花了好几天时间来做收集工作,收集大概几千张图片作为训练数据
  • 对于整张图像去反光效果不好,并没有论文上说的对任意大小都没有问题的那么好
  • 会对非反光区域产生影响

基于全连接卷积神经网络的反光检测

有了CVC-EndoSceneStill后,我做了一个基于全连接卷积神经网络的反光检测网络,效果相当不错。

实验结果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

总结

  • 梳理了三篇内窥镜去反光论文的基本方法
  • 介绍了一些有光内窥镜的数据集
  • 实现了一种基于全连接卷积神经网络的反光检测网络
2017-04-19 09:59:35 u011574296 阅读数 14397

颜色空间 

近100多年来,为了满足不同用途的需要,人们开发了许多不同名称的颜色空间,“可以说表示颜色空间的数目是无穷的”,但是,现有的颜色空间还没有一个完全符合人的视觉感知特性、颜色本身的物理特性或发光物体或反光物体的特性。人们还在继续开发各种新的颜色空间。

不同的颜色空间有着不同的特性,使用在不同的领域。因此在实际中需要进行不同颜色空间的转换。不同颜色空间的转换可以是直接转换,也可以通过与设备无关的颜色空间进行转换。这时就会遇到选择颜色空间和转换方法的问题。

 

为了使用颜色空间,首先应该了解各种颜色空间的特性。颜色空间的分类有多种方法。

1.按使用类别分类

彩色色度学模型:CIE-RGB、CIE-XYZ、均匀色差彩色模型(CIE 1976Luv和CIE Lab)

工业彩色模型:RGB彩色显示模型、CMYK彩色印制模型、彩色传输模型YUV(PAL)、YIQ(NTSC)、YCrCb(数字高清晰度电视)

视觉彩色模型:HVC(孟赛尔)、HSB(Photoshop)、HLS(Windows画图和Apple Color Picker)、HSI(图像分割)、HSY(电视)、Ohta(图像分割)等。

2.按颜色感知分类

混合颜色模型:按3种基色的比例混合而成的颜色。RGB、CMYK、XYZ等

非线形亮度/色度颜色模型:用一个分量表示非色彩的感知,用两个分量表示色彩的感知,这两个分量都是色差属性。L*a*b、L*u*v、YUV、YIQ等。

强度/饱和度/色调模型:用强度描述亮度或灰度等光强的感知,用饱和度和色调描述色彩的感知,这两个分量接近人眼对颜色的感觉。如HIS、HSL、HSV、LCH等

 

一.彩色色度学模型

1.CIE-RGB彩色模型

CIE-RGB彩色模型是国际照明委员会(CIE)制定了第一个彩色色度学模型。它是在三原色学说下,以红色、绿色、蓝色作为三种基色建立起来的颜色模型。其不足点是:在某些情况下,颜色值会出现负值。

2.CIE-XYZ彩色模型

CIE-XYZ彩色模型是为消除CIE—RGB色度坐标中负值而设计的。其中三刺激值X、Y、Z,并不代表真实的物理彩色。其颜色空间包含了人类能够发觉的所有颜色,但并不是均匀色差空间。

3.均匀色差空间

在均匀色差空间中,相同距离的彩色色差与人眼主观感觉基本一致。有Luv和Lab两种均匀色差彩色模型。普遍认为,Lab彩色模型更接近人眼的主观感觉。

Buchsbaum等人对色彩迁移进行了研究,利用一些正交线性变换将人类视觉系统所能感受到的红、绿、蓝三色信号变换到三个不相关的色彩分量。在他们的研究基础上,Ruderman等人经过对大量的自然界图像进行颜色分布统计,得到了图像的颜色分布统计结果,并用转化色彩空间的方法形成一个统计意义上具有近似正交基的均匀色彩空间lαβ,并给出了简单的3×3矩阵运算实现RGB到lαβ空间的转化。lαβ色彩空间是基于数据驱动对人类感觉的研究,它假设人类的视觉系统理想地适应自然景色的处理,由亮度分量l,和两个色度分量α和β组成。其中,α表示黄一蓝通道(yellow—blue opponent),β表示红一绿通道(red—green opponent),如图:

 

Ruderman等人在一幅用lαβ色彩空间表示的图像中随机抽取1000个像素点,生成了图4所示的坐标关系图。从中可以看出,lαβ色彩空间不仅基本消除了颜色分量之间的强相关性,而且有效地将图像的灰度信息和颜色信息分离开来。所以可以分别对三个通道图像进行独立的运算,而不需要修改另外两个通道的信息,从而不会影响原图像的自然效果。

 

                                                                                                   lab空间l-a、l-b、a-b关系图

色彩空间的转换

       色彩空间一方面要符合人眼的视觉感知特性,另一方面应方便图像的处理。图像色彩迁移的过程是一个改变图像颜色基调的过程,通常希望在改变图像的一个颜色属性时,不改变图像其它的颜色属性。由前面的介绍可知,RGB空间的三通道具有很强的相关性,而lαβ空间的各通道具有最小的相关性。所以,在lαβ空间对图像的颜色进行出来将会变得十分方便。因此,选择在lαβ空间进行图像间的色彩迁移,这就需要将图像从RGB空间转换到lαβ空间。

1.从RGB空间到lαβ空间的转换

需要进行3次变换,即RGB->CIEXYZ->LMS->lαβ,具体步骤如下:

1)从RGB空间到CIE XYZ空间的转换

 

2)从CIE XYZ空间到LMS空间的转换

 

通过1、2可以得到RGB空间到LMS空间的转换。由于数据在LMS空间比较分散,所以进一步将其转换到以10为底的对数空间,这样不仅使数据分布更加聚敛,而且符号人类对于颜色感觉的心理物理学研究结果。

 

3)从LMS空间到lαβ空间的转换

这一变换是基于对数据的主成分分析(PCA,Principal ComponentAnalysis)得到的,其中l为第一主成分,α为第二主成分,β为第三主成分。

 

经过这三个步骤就完成了从RGB空间到lαβ空间的转换。

色彩空间的逆转换

当图像在lαβ空间进行处理之后,为了显示处理的结果,需要把图像转换到RGB空间,具体步骤为:

1)从lαβ空间到LMS对数空间的转换

 

2)从LMS对数空间到LMS线性空间的转换

 

3)从LMS空间到RGB空间的转换

 

 

二.工业彩色模型

1.RGB彩色显示模型

RGB模型是一种加色系统,色彩源于红、绿、蓝三基色。用于CRT显示器、数字扫描仪、数字摄像机和显示设备上,是当前应用最广泛的一种彩色模型。RGB彩色模型可用一个三维空间的立方体来表示,在此系统中计算的任何颜色都该立方体内,其彩色空间在L*a*b*彩色空间内。但人眼不能直接感觉红、绿、蓝三色的比例,因此对图像进行增强处理结果难以控制。

2.CMYK彩色印制模型

CMYK彩色模型是一种减色模型,色彩来源于青、品红、黄3种基色,其彩色空间小于RGB彩色空间。主要适用于印刷油墨和调色剂等实体物质产生颜色的场合,广泛用于彩色印刷领域。

3.彩色传输模型

彩色传输模型主要用于彩色电视机信号传输标准,他们的共同特点是都能向下兼容黑白显示器,即在黑白显示器上也能显示彩色图像,只不过显示为灰度图像。YUV彩色传输模型适用于PAL、SECAM彩色电视制式。YIQ彩色传输模型适用于美国国家电视标准委员会(NTSC)彩色电视制式,它是经YUV模型旋转色差分量而形成的彩色空间。YCrCb彩色传输适用于计算机用的显示器。

 

三.视觉彩色模型

根据人眼视觉特性提出的,用色调(Hue)、饱和度(Saturation)、亮度(luminance)来描述彩色模型。其彩色空间能更好的与人的视觉特性相匹配。

1.   基于孟塞尔彩色系统的HVC模型

蒙塞尔系统是比较经典和理想的均匀的颜色表示系统。它在视觉上是等色差的,即沿色调、亮度值、或饱和度方向上等量的变化产生相同的视觉差异。遗憾的是,蒙塞尔系统没有具体的描述公式,是依靠大量的实验数据,以查表方式(表的大小为256*256*256)和蒙塞尔色品卡建立起来的,因此和其它的彩色模型的转换十分复杂,必须通过数据查询和线性插值,计算量繁多。

2.   HSB彩色模型

HSB彩色模型是一个单锥体,红、绿、蓝以及各自的补色色调均匀分布于圆周上,是基于孟塞尔彩色系统的一种理想情况。其彩色模型问题在于:

① 除白色外,R、G、B三值中只要其中一个值是255,饱和度就等于最大值100;同一亮度下,各种颜色的饱和度都相等。这和孟塞尔系统颜色模型不一致。

② HSB模型中,在同一亮度平面上(如亮度最大的平面上),可见到所有的颜色。这与人眼的视觉特性相违背。

③ 只要R、G、B中有一个值为255,那么亮度B就达到最大值100。这与Grassman的三色调配公理和格拉斯曼定律内容不符。

3.   HLS彩色模型

HLS彩色模型是一个双锥体,其色调H与HSV模型中的色调分布完全一样。不同的是:HLS模型中的最亮纯色位于L=0.5处,亮度为最大时,只能看到白色。它也是基于孟塞尔彩色系统的一种理想模型,其自身也同样存在一些不足:

① 除白色外,R、G、B三值中只要其中一个值是255,饱和度就等于最大值100;同一亮度下,各种颜色的饱和度都相等;且饱和度随着亮度L均匀的变化。

②HLS模型中的三个属性(H、L、S)并不完全独立。可由公式亮度看出,这给人们单独处理某一属性时带来麻烦。

③ 在L=0.5的平面上能够看到所有的纯彩色;而实际上这些颜色看起来并不一样亮,但看起来一样亮的颜色,其亮度值未必相等,与人眼之间有误差。这个问题在HSB模型中也同样存在。

4.    HSY彩色模型

HSY模型是一种彩色传输模型,传输基本的色差和亮度信号,被用于摄像机传输模式。YCrCb模型、YUV模型以及YIQ模型都是在HSY模型基础上对色差信号进行调制和压缩而形成的彩色电视机信号的传输标准。

①它的亮度变化(在各种纯彩色之间)完全符合人眼的实际视觉感受。它的亮度公式是根据人的视觉特性,由美国国家电视制式委员会的NTSC制式推导得到的

②在 HSY模型饱和度最大的颜色并不在一个圆周上,同一亮度平面上的各颜色的饱和度也不一样大,这很接近人眼的实际视觉感受,和孟塞尔颜色系统理论相符。

③基色色调与其补色近似互补,但是各基色及其补色在圆周上的分布不均匀。

有关研究表明:人眼对亮度、饱和度信息的变化比色调信号所携带的信息的变化要敏感的得多,也就是说人眼对色调的变化相对不敏感。

基于以上综合分析,采用HSY彩色模型作为测量工具,进行实际测试。测试结果和预期分析的结果非常符合,亮度和饱和度相对于HSB和HLS彩色模型,更符合人眼的实际视觉感受,色调分布无大的差别。从总体上说HSY彩色模型的测试效果更好。

5.    HSI彩色模型

 

HSI (hue、saturation、intensity) 和HSV (hue、saturation、value)颜色空间在设计上使得颜色感知和解释的方式与人很接近,在需要手动指定颜色值时经常需要使用它们。HLS (hue、lightness、saturation) 类似于HSI,只是使用了术语lightness,而不是光强度intensity来表示亮度。

HSI和HSV颜色空间的差异在于亮度分量(I or V)的计算方式。HSI颜色空间适合传统的图像处理函数,如卷积、均化、直方图等,可以通过处理亮度值来实现这些操作,因为亮度I对R、G、B值的依赖程度是一样的。HSV颜色空间适合处理色度和饱和度,因为它使得饱和度具有更大的动态取值范围

6.    Ohta颜色空间

Ohta颜色空间是1980年Ohta 等人提出的颜色空间,该空间中三个颜色分量,I1,I2,I3,为一组正交的颜色特征集,它们也可由RGB颜色空间转换得到,三个分量各自互相独立。

 

 

利用该模型也可以对彩色图像进行分割,可以得到很好的效果(黄色的图像背景)

 

数字图像处理和计算机视觉领域,遇到应用问题,应该怎么选择合适的色彩空间,下面是葛老师总结的一个表格,供参考

小结:图象处理使用HIS较多,图形学使用HSV较多。图象分割使用HSV较多,Ohta较新。

 

 OpenCV转换图像的色彩空间的方法,参见博客:

【OpenCV图像处理】颜色空间转换函数 cvtColor()

 

2016-09-18 14:22:11 u012925804 阅读数 4915

亮度

亮度是一个相对的概念。这取决于你的视觉感受。因为亮度是一个相对的概念,所以亮度可以定义发光体(反光体)表面发光(反光)强弱的物理量。在某些情况下我们很容易地说,图像是明亮的,在某些情况下,它不容易察觉。(不要把亮度和光照度混淆)

例如

对比以下两幅图像,并且比较哪个更亮。


我们可以发现,右边的图比左边的图更亮。

但是如果右边放一张比左边更黑的图,那我我们就可以说左边比右边的亮。

如何使图像变量

通过增加或者减少图像矩阵的值可以简单的增加或减少亮度。

考虑5x5的这个黑色图像


我们已经知道图像是一个包含像素值的矩阵。上面这个图像的矩阵如下,

0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
因为矩阵的所有值都为0,所以图像是黑的。

现在我们 给矩阵的每个值加50,得到如下的图像,


现在,我们对比两幅图像,


现在你可以发现image1会比image2更加两,我们再给image1的矩阵值加45,再次对比两幅图像,


现在你会发现,image1明显比image2亮。


对比度

对比度可以简单的解释为图像矩阵中像素的最大值和最小值之差。

例如:

考虑最后一幅图image1


图像矩阵为:

95 95 95 95 95
95 95 95 95 95
95 95 95 95 95
95 95 95 95 95
95 95 95 95 95
图像矩阵的最大值为95.

图像矩阵的最小值为95.

所以对比度=像素最大值 - 像素最小值

      = 95 - 95

  = 0

所以0就为这幅图像的对比度。

译:https://www.tutorialspoint.com/dip/brightness_and_contrast.htm

没有更多推荐了,返回首页