opencv图像处理算法

2020-02-19 21:10:23 weixin_45709330 阅读数 130

传统图像处理算法

以下内容大部分转自:https://mp.weixin.qq.com/s?__biz=MjM5ODU3OTIyOA==&mid=2650680998&idx=2&sn=d0f6731df9a2bb7497d2bd7133b4160f&chksm=bec213d589b59ac3e6dd0f46963eea43f50d12786d0eeda7d43163ec734f9dc464d6e3d8ea37&mpshare=1&scene=23&srcid=&sharer_sharetime=1582028705160&sharer_shareid=ea3070f5b49c55b77146ef196b322028#rd

传统的图像处理算法有以下六种

1.RGB和GRAY互转

个人理解:这个就是彩色图转灰度图的算法,通过调整R,G,B三原色中的颜色值来实现色彩的变化。

RGB是依据人眼识别的颜色定义出的空间,可表示大部分颜色。是图像处理中最基本、最常用、面向硬件的颜色空间,是一种光混合的体系。

RGB颜色空间最常用的用途就是显示器系统,彩色阴极射线管,彩色光栅图形的显示器都使用R、G、B数值来驱动R、G、B 电子枪发射电子,并分别激发荧光屏上的R、G、B三种颜色的荧光粉发出不同亮度的光线,并通过相加混合产生各种颜色。扫描仪也是通过吸收原稿经反射或透射而发送来的光线中的R、G、B成分,并用它来表示原稿的颜色。
(R:Red,G:Green,B:Blue)

首先是RGB2GRAY,也就是彩色图转灰度图的算法。RGB值和灰度的转换,实际上是人眼对于彩色的感觉到亮度感觉的转换,这是一个心理学问题,有一个公式:Grey = 0.299R + 0.587G + 0.114B。直接计算复杂度较高,考虑优化可以将小数转为整数,除法变为移位,乘法也变为移位,但是这种方法也会带来一定的精度损失,我们可以根据实际情况选择需要保留的精度位数。下面给出不同精度(2-20位)的计算公式:

Grey = (R*1 + G*2 + B*1) >> 2

Grey= (R*2 + G*5 + B*1) >> 3

Grey= (R*4 + G*10 + B*2) >> 4

Grey = (R*9 + G*19 + B*4) >> 5

Grey = (R*19 + G*37 + B*8) >> 6

Grey= (R*38 + G*75 + B*15) >> 7

Grey= (R*76 + G*150 + B*30) >> 8

Grey = (R*153 + G*300 + B*59) >> 9

Grey = (R*306 + G*601 + B*117) >> 10

Grey = (R*612 + G*1202 + B*234) >> 11

Grey = (R*1224 + G*2405 + B*467) >> 12

Grey= (R*2449 + G*4809 + B*934) >> 13

Grey= (R*4898 + G*9618 + B*1868) >> 14

Grey = (R*9797 + G*19235 + B*3736) >> 15

Grey = (R*19595 + G*38469 + B*7472) >> 16

Grey = (R*39190 + G*76939 + B*14943) >> 17

Grey = (R*78381 + G*153878 + B*29885) >> 18

Grey =(R*156762 + G*307757 + B*59769) >> 19

Grey= (R*313524 + G*615514 + B*119538) >> 20

再给出保留20位精度的计算代码(使用了Openmp多线程优化):

//RGB2GRAY优化
Mat speed_rgb2gray(Mat src) {
	Mat dst(src.rows, src.cols, CV_8UC1);
#pragma omp parallel for num_threads(4)
	for (int i = 0; i < src.rows; i++) {
		for (int j = 0; j < src.cols; j++) {
			dst.at<uchar>(i, j) = ((src.at<Vec3b>(i, j)[0] << 18) + (src.at<Vec3b>(i, j)[0] << 15) + (src.at<Vec3b>(i, j)[0] << 14) +
				(src.at<Vec3b>(i, j)[0] << 11) + (src.at<Vec3b>(i, j)[0] << 7) + (src.at<Vec3b>(i, j)[0] << 7) + (src.at<Vec3b>(i, j)[0] << 5) +
				(src.at<Vec3b>(i, j)[0] << 4) + (src.at<Vec3b>(i, j)[0] << 2) +
				(src.at<Vec3b>(i, j)[1] << 19) + (src.at<Vec3b>(i, j)[1] << 16) + (src.at<Vec3b>(i, j)[1] << 14) + (src.at<Vec3b>(i, j)[1] << 13) +
				(src.at<Vec3b>(i, j)[1] << 10) + (src.at<Vec3b>(i, j)[1] << 8) + (src.at<Vec3b>(i, j)[1] << 4) + (src.at<Vec3b>(i, j)[1] << 3) + (src.at<Vec3b>(i, j)[1] << 1) +
				(src.at<Vec3b>(i, j)[2] << 16) + (src.at<Vec3b>(i, j)[2] << 15) + (src.at<Vec3b>(i, j)[2] << 14) + (src.at<Vec3b>(i, j)[2] << 12) +
				(src.at<Vec3b>(i, j)[2] << 9) + (src.at<Vec3b>(i, j)[2] << 7) + (src.at<Vec3b>(i, j)[2] << 6) + (src.at<Vec3b>(i, j)[2] << 5) + (src.at<Vec3b>(i, j)[2] << 4) + (src.at<Vec3b>(i, j)[2] << 1) >> 20);
		}
	}
	return dst;
}

2.RGB和YUV互转

YUV是什么?

“Y”表示明亮度(Luminance或Luma),也就是灰阶值;而“U”和“V” 表示的则是色度(Chrominance或Chroma)

个人理解:YUV是一种通过颜色的亮度以及两种颜色与亮度的差所得到的一种颜色的渲染

YUV颜色空间,YUV(亦称YCrCb)是被欧洲电视系统所采用的一种颜色编码方法。在现代彩色电视系统中,通常采用三管彩色摄像机或彩色CCD摄影机进行取像,然后把取得的彩色图像信号经分色、分别放大校正后得到RGB,再经过矩阵变换电路得到亮度信号Y和两个色差信号R-Y(即U)B-Y(即V),最后发送端将亮度和两个色差总共三个信号分别进行编码,用同一信道发送出去。这种色彩的表示方法就是所谓的YUV色彩空间表示。

采用YUV色彩空间的重要性是它的亮度信号Y色度信号U、V分离的。如果只有Y信号分量而没有U、V信号分量(也就是没有色彩信号),那么这样表示的图像就是黑白灰度图像。彩色电视采用YUV空间正是为了用亮度信号Y解决彩色电视机与黑白电视机的兼容问题,使黑白电视机也能接收彩色电视信号。

“U”和“V” 表示的则是色度(Chrominance或Chroma),作用是描述影像色彩及饱和度,用于指定像素的颜色。“亮度”是透过RGB输入信号来建立的,方法是将RGB信号的特定部分叠加到一起。
色度”则定义了颜色的两个方面─色调饱和度,分别用Cr和Cb来表示。其中,Cr反映了RGB输入信号红色部分与RGB信号亮度值之间的差异。而Cb反映的是RGB输入信号蓝色部分与RGB信号亮度值之同的差异

转换公式:

1,RGB转YUV

Y = 0.299R + 0.587G + 0.114B U = -0.147R - 0.289G + 0.436B V 
  = 0.615R - 0.515G - 0.100B

2,YUV转RGB

R = Y + 1.14V G = Y - 0.39U - 0.58V B 
  = Y + 2.03U

算法优化

优化1:去掉浮点运算

基于这一点,我们做如下操作:

Y * 256 = 0.299 * 256R + 0.587 * 256G + 0.114 * 256B

U * 256 = -0.147 * 256R - 0.289 * 256G + 0.436 * 256B
V * 256 = 0.615 * 256R - 0.515 * 256G - 0.100 * 256B

R * 256 = Y * 256 + 1.14 * 256V
G * 256 = Y * 256 - 0.39 * 256U - 0.58 * 256V
B * 256 = Y * 256 + 2.03 * 256U

简化上面的公式如下:

256Y = 76.544R + 150.272G + 29.184B

256U = -37.632R - 73.984G + 111.616B

256V = 157.44R - 131.84G - 25.6B

256R = 256Y + 291.84V

256G = 256Y - 99.84U - 148.48V

256B = 256Y + 519.68U

然后,我们就可以对上述公式进一步优化,彻底干掉小数,注意这里是有精度损失的。

256Y = 77R + 150G + 29B

256U = -38R - 74G + 112B

256V = 158R - 132G - 26B

256R = 256Y + 292V

256G = 256Y - 100U - 149V

256B = 256Y + 520U

实际上就是四舍五入,这是乘以256是为了缩小误差,当然乘数越大,误差越小。和RGB2GRAY一样的套路。

优化二:乘法和除法变为移位运算

先将除法变为移位运算:

Y = (77R + 150G + 29B) >> 8

U = (-38R - 74G + 112B) >> 8

V = (158R - 132G - 26B) >> 8

R = (256Y + 292V) >> 8

G = (256Y - 100U - 149V) >> 8

B = (256Y + 520U) >> 8

公式中还有很多乘法运算,乘法跟移位运算相比,还是效率太低了,因此,我们将把所有乘法都改成移位运算。如何将常数乘法改成移位运算?这里给个例子:

Y=Y*9可以改为:Y=(Y<<3)+Y。

因此,我们可以讲YUV的公式继续改为最简:
RGB转YUV:

Y = ((R << 6) + (R << 3) + (R << 2) + R + (G << 7) + (G << 4) + (G << 2) + (G << 1) + (B << 4) + (B << 3) + (B << 2) + B) >> 8;
U = (-((R << 5) + (R << 2) + (R << 1)) - ((G << 6) + (G << 3) + (G << 1)) + ((B << 6) + (B << 5) + (B << 4))) >> 8;
V = ((R << 7) + (R << 4) + (R << 3) + (R << 2) + (R << 1) - ((G << 7) + (G << 2)) - ((B << 4) + (B << 3) + (B << 1))) >> 8;

YUV转RGB:

R = ((Y << 8) + ((V << 8) + (V << 5) + (V << 2))) >> 8;
G = ((Y << 8) - ((U << 6) + (U << 5) + (U << 2)) - ((V << 7) + (V << 4) + (V << 2) + V)) >> 8;
B = ((Y << 8) + (U << 9) + (U << 3)) >> 8;

使用OpemMP和上诉优化的互转代码如下:注意一下,imread读取的图片通道顺序默认是BGR。

//RGB2YUV优化
Mat speed_rgb2yuv(Mat src) {
	Mat dst(src.rows, src.cols, CV_8UC3);
#pragma omp parallel for num_threads(4)
	for (int i = 0; i < src.rows; i++) {
		for (int j = 0; j < src.cols; j++) {
			dst.at<Vec3b>(i, j)[0] =
				((src.at<Vec3b>(i, j)[2] << 6) + (src.at<Vec3b>(i, j)[2] << 3) + (src.at<Vec3b>(i, j)[2] << 2) + src.at<Vec3b>(i, j)[2] +
				(src.at<Vec3b>(i, j)[1] << 7) + (src.at<Vec3b>(i, j)[1] << 4) + (src.at<Vec3b>(i, j)[1] << 2) + (src.at<Vec3b>(i, j)[1] << 1) +
					(src.at<Vec3b>(i, j)[0] << 4) + (src.at<Vec3b>(i, j)[0] << 3) + (src.at<Vec3b>(i, j)[0] << 2) + src.at<Vec3b>(i, j)[0]) >> 8;
			dst.at<Vec3b>(i, j)[1] = (-((src.at<Vec3b>(i, j)[2] << 5) + (src.at<Vec3b>(i, j)[2] << 2) + (src.at<Vec3b>(i, j)[2] << 1)) -
				((src.at<Vec3b>(i, j)[1] << 6) + (src.at<Vec3b>(i, j)[1] << 3) + (src.at<Vec3b>(i, j)[1] << 1)) +
				((src.at<Vec3b>(i, j)[0] << 6) + (src.at<Vec3b>(i, j)[0] << 5) + (src.at<Vec3b>(i, j)[0] << 4))) >> 8;
			dst.at<Vec3b>(i, j)[2] = ((src.at<Vec3b>(i, j)[2] << 7) + (src.at<Vec3b>(i, j)[2] << 4) + (src.at<Vec3b>(i, j)[2] << 3) + (src.at<Vec3b>(i, j)[2] << 2) + (src.at<Vec3b>(i, j)[2] << 1) -
				((src.at<Vec3b>(i, j)[1] << 7) + (src.at<Vec3b>(i, j)[1] << 2)) - ((src.at<Vec3b>(i, j)[0] << 4) + (src.at<Vec3b>(i, j)[0] << 3) + (src.at<Vec3b>(i, j)[0] << 1))) >> 8;
		}
	}
	return dst;
}
//YUV2RGB优化
Mat speed_yuv2rgb(Mat src) {
	Mat dst(src.rows, src.cols, CV_8UC3);
	#pragma omp parallel for num_threads(4)
	for (int i = 0; i < src.rows; i++) {
		for (int j = 0; j < src.cols; j++) {
			dst.at<Vec3b>(i, j)[0] = ((src.at<Vec3b>(i, j)[0] << 8) + (src.at<Vec3b>(i, j)[1] << 9) + (src.at<Vec3b>(i, j)[1] << 3)) >> 8;
			dst.at<Vec3b>(i, j)[1] = ((src.at<Vec3b>(i, j)[0] << 8) - ((src.at<Vec3b>(i, j)[1] << 6) + (src.at<Vec3b>(i, j)[1] << 5) +
			(src.at<Vec3b>(i, j)[1] << 2)) - ((src.at<Vec3b>(i, j)[2] << 7) + (src.at<Vec3b>(i, j)[2] << 4) +
			(src.at<Vec3b>(i, j)[2] << 2) + src.at<Vec3b>(i, j)[2])) >> 8;
			dst.at<Vec3b>(i, j)[2] = ((src.at<Vec3b>(i, j)[0] << 8) + ((src.at<Vec3b>(i, j)[2] << 8) + (src.at<Vec3b>(i, j)[2] << 5) +
			(src.at<Vec3b>(i, j)[2] << 2))) >> 8;
		}
	}
	return dst;
}

3.RGB和HSV互转

HSV是什么?

HSV即色相(Hue)、饱和度(Saturation)、明度(Value),又称HSB(B即Brightness)。

HSV是一种将RGB色彩空间中的点在倒圆锥体中的表示方法。
色相色彩基本属性,就是平常说的颜色的名称,如红色、黄色等。
饱和度(S)是指色彩的纯度越高色彩越纯,低则逐渐变灰,取0-100%的数值。
明度(V),取0-max(计算机中HSV取值范围和存储的长度有关)。HSV颜色空间可以用一个圆锥空间模型来描述。圆锥的顶点处,V=0,H和S无定义,代表黑色圆锥的顶面中心处V=max,S=0,H无定义,代表白色。RGB颜色空间中,三种颜色分量的取值与所生成的颜色之间的联系并不直观。而HSV颜色空间,更类似于人类感觉颜色的方式。
封装了关于颜色的信息:“这是什么颜色深浅如何明暗如何?”。

在这里插入图片描述
计算公式:
设max等于r、g和b中的最大者,min为最小者。对应的HSV空间中的(h,s,v)值为:
在这里插入图片描述
在这里插入图片描述
代码部分:
1.RGB转HSV

Mat RGB2HSV(Mat src) {
	int row = src.rows;
	int col = src.cols;
	Mat dst(row, col, CV_32FC3);
	for (int i = 0; i < row; i++) {
		for (int j = 0; j < col; j++) {
			float b = src.at<Vec3b>(i, j)[0] / 255.0;
			float g = src.at<Vec3b>(i, j)[1] / 255.0;
			float r = src.at<Vec3b>(i, j)[2] / 255.0;
			float minn = min(r, min(g, b));
			float maxx = max(r, max(g, b));
			dst.at<Vec3f>(i, j)[2] = maxx; //V
			float delta = maxx - minn;
			float h, s;
			if (maxx != 0) {
				s = delta / maxx;
			}
			else {
				s = 0;
			}
			if (r == maxx) {
				h = (g - b) / delta;
			}
			else if (g == maxx) {
				h = 2 + (b - r) / delta;
			}
			else {
				h = 4 + (r - g) / delta;
			}
			h *= 60;
			if (h < 0)
				h += 360;
			dst.at<Vec3f>(i, j)[0] = h;
			dst.at<Vec3f>(i, j)[1] = s;
		}
	}
	return dst;
}

2.HSV转RGB

Mat HSV2RGB(Mat src) {
	int row = src.rows;
	int col = src.cols;
	Mat dst(row, col, CV_8UC3);
	float r, g, b, h, s, v;
	for (int i = 0; i < row; i++) {
		for (int j = 0; j < col; j++) {
			h = src.at<Vec3f>(i, j)[0];
			s = src.at<Vec3f>(i, j)[1];
			v = src.at<Vec3f>(i, j)[2];
			if (s == 0) {
				r = g = b = v;
			}
			else {
				h /= 60;
				int offset = floor(h);
				float f = h - offset;
				float p = v * (1 - s);
				float q = v * (1 - s * f);
				float t = v * (1 - s * (1 - f));
				switch (offset)
				{
				case 0: r = v; g = t; b = p; break;
				case 1: r = q; g = v; b = p; break;
				case 2: r = p; g = v; b = t; break;
				case 3: r = p; g = q; b = v; break;
				case 4: r = t; g = p; b = v; break;
				case 5: r = v; g = p; b = q; break;
				default:
					break;
				}
			}
			dst.at<Vec3b>(i, j)[0] = int(b * 255);
			dst.at<Vec3b>(i, j)[1] = int(g * 255);
			dst.at<Vec3b>(i, j)[2] = int(r * 255);
		}
	}
	return dst;
}

4.RGB和HSI互转

什么是HSI?

H——表示颜色的相位角。红、绿、蓝分别相隔120度;互补色分别相差180度,即颜色的类别
S——表示成所选颜色的纯度该颜色最大的纯度之间的比率,范围:[0, 1],即颜色的深浅程度。(可以理解为颜色的纯度)
I——表示色彩的明亮程度,围:[0, 1],人眼对亮度很敏感!

HSI色彩空间是从人的视觉系统出发,用色调(Hue)、饱和度(Saturation或Chroma)和亮度 (Intensity或Brightness)来描述色彩。
可以将HSI放在下面这个棱锥,用将颜色分布放在空间去理解
HSI色彩空间
在这里插入图片描述
RGB色彩空间
在这里插入图片描述
可以看到HSI色彩空间和RGB色彩空间只是同一物理量的不同表示法,因而它们之间存在着转换关系:HSI颜色模式中的色调使用颜色类别表示,饱和度与颜色的白光光亮亮度刚好成反比,代表灰色与色调的比例,亮度是颜色的相对明暗程度。

转换公式:
1.RGB转HSI
在这里插入图片描述
2.HSI转RGB
给定 HSI空间中的 (h, s, l) 值定义的一个颜色,带有 h 在指示色相角度的值域 [0, 360)中,分别表示饱和度和亮度的s 和 l 在值域 [0, 1] 中,相应在 RGB 空间中的 (r, g, b) 三原色,带有分别对应于红色、绿色和蓝色的 r, g 和 b 也在值域 [0, 1] 中,它们可计算为:首先,如果 s = 0,则结果的颜色是非彩色的、或灰色的。在这个特殊情况,r, g 和 b 都等于 l。注意 h 的值在这种情况下是未定义的。当 s ≠ 0 的时候,可以使用下列过程:
在这里插入图片描述
1.RGB转HSI

Mat RGB2HSI(Mat src) {
	int row = src.rows;
	int col = src.cols;
	Mat dst(row, col, CV_32FC3);
	for (int i = 0; i < row; i++) {
		for (int j = 0; j < col; j++) {
			float b = src.at<Vec3b>(i, j)[0] / 255.0;
			float g = src.at<Vec3b>(i, j)[1] / 255.0;
			float r = src.at<Vec3b>(i, j)[2] / 255.0;
			float minn = min(b, min(g, r));
			float maxx = max(b, max(g, r));
			float H = 0;
			float S = 0;
			float I = (minn + maxx) / 2.0f;
			if (maxx == minn) {
				dst.at<Vec3f>(i, j)[0] = H;
				dst.at<Vec3f>(i, j)[1] = S;
				dst.at<Vec3f>(i, j)[2] = I;
			}
			else {
				float delta = maxx - minn;
				if (I < 0.5) {
					S = delta / (maxx + minn);
				}
				else {
					S = delta / (2.0 - maxx - minn);
				}
				if (r == maxx) {
					if (g > b) {
						H = (g - b) / delta;
					}
					else {
						H = 6.0 + (g - b) / delta;
					}
				}
				else if (g == maxx) {
					H = 2.0 + (b - r) / delta;
				}
				else {
					H = 4.0 + (r - g) / delta;
				}
				H /= 6.0; //除以6,表示在那个部分
				if (H < 0.0)
					H += 1.0;
				if (H > 1)
					H -= 1;
				H = (int)(H * 360); //转成[0, 360]
				dst.at<Vec3f>(i, j)[0] = H;
				dst.at<Vec3f>(i, j)[1] = S;
				dst.at<Vec3f>(i, j)[2] = I;
			}
		}
	}
	return dst;
}

float get_Ans(double p, double q, double Ht) {
	if (Ht < 0.0)
		Ht += 1.0;
	else if (Ht > 1.0)
		Ht -= 1.0;
	if ((6.0 * Ht) < 1.0)
		return (p + (q - p) * Ht * 6.0);
	else if ((2.0 * Ht) < 1.0)
		return q;
	else if ((3.0 * Ht) < 2.0)
		return (p + (q - p) * ((2.0F / 3.0F) - Ht) * 6.0);
	else
		return (p);
}

2.HSI转RGB

Mat HSI2RGB(Mat src) {
	int row = src.rows;
	int col = src.cols;
	Mat dst(row, col, CV_8UC3);
	for (int i = 0; i < row; i++) {
		for (int j = 0; j < col; j++) {
			float r, g, b, M1, M2;
			float H = src.at<Vec3f>(i, j)[0];
			float S = src.at<Vec3f>(i, j)[1];
			float I = src.at<Vec3f>(i, j)[2];
			float hue = H / 360;
			if (S == 0) {//灰色
				r = g = b = I;
			}
			else {
				if (I <= 0.5) {
					M2 = I * (1.0 + S);
				}
				else {
					M2 = I + S - I * S;
				}
				M1 = (2.0 * I - M2);
				r = get_Ans(M1, M2, hue + 1.0 / 3.0);
				g = get_Ans(M1, M2, hue);
				b = get_Ans(M1, M2, hue - 1.0 / 3.0);
			}
			dst.at<Vec3b>(i, j)[0] = (int)(b * 255);
			dst.at<Vec3b>(i, j)[1] = (int)(g * 255);
			dst.at<Vec3b>(i, j)[2] = (int)(r * 255);
		}
	}
	return dst;
}

5.RGB和YCbCr互转

什么是YCbCr?

YCbCr或Y’CbCr有的时候会被写作:YCBCR或是Y’CBCR,是色彩空间的一种,通常会用于影片中的影像连续处理,或是数字摄影系统中。Y’为颜色的亮度(luma)成分、而CB和CR则为蓝色和红色的浓度偏移量成份。Y’和Y是不同的,而Y就是所谓的亮度(luminance),表示光的浓度且为非线性,使用伽马修正(gamma correction)编码处理。——转自YCbCr百度百科
转化公式如下:
在这里插入图片描述
代码:

const float YCbCrYRF = 0.299F;              // RGB转YCbCr的系数(浮点类型)
const float YCbCrYGF = 0.587F;
const float YCbCrYBF = 0.114F;
const float YCbCrCbRF = -0.168736F;
const float YCbCrCbGF = -0.331264F;
const float YCbCrCbBF = 0.500000F;
const float YCbCrCrRF = 0.500000F;
const float YCbCrCrGF = -0.418688F;
const float YCbCrCrBF = -0.081312F;

const float RGBRYF = 1.00000F;            // YCbCr转RGB的系数(浮点类型)
const float RGBRCbF = 0.0000F;
const float RGBRCrF = 1.40200F;
const float RGBGYF = 1.00000F;
const float RGBGCbF = -0.34414F;
const float RGBGCrF = -0.71414F;
const float RGBBYF = 1.00000F;
const float RGBBCbF = 1.77200F;
const float RGBBCrF = 0.00000F;

const int Shift = 20;
const int HalfShiftValue = 1 << (Shift - 1);

const int YCbCrYRI = (int)(YCbCrYRF * (1 << Shift) + 0.5);         // RGB转YCbCr的系数(整数类型)
const int YCbCrYGI = (int)(YCbCrYGF * (1 << Shift) + 0.5);
const int YCbCrYBI = (int)(YCbCrYBF * (1 << Shift) + 0.5);
const int YCbCrCbRI = (int)(YCbCrCbRF * (1 << Shift) + 0.5);
const int YCbCrCbGI = (int)(YCbCrCbGF * (1 << Shift) + 0.5);
const int YCbCrCbBI = (int)(YCbCrCbBF * (1 << Shift) + 0.5);
const int YCbCrCrRI = (int)(YCbCrCrRF * (1 << Shift) + 0.5);
const int YCbCrCrGI = (int)(YCbCrCrGF * (1 << Shift) + 0.5);
const int YCbCrCrBI = (int)(YCbCrCrBF * (1 << Shift) + 0.5);

const int RGBRYI = (int)(RGBRYF * (1 << Shift) + 0.5);              // YCbCr转RGB的系数(整数类型)
const int RGBRCbI = (int)(RGBRCbF * (1 << Shift) + 0.5);
const int RGBRCrI = (int)(RGBRCrF * (1 << Shift) + 0.5);
const int RGBGYI = (int)(RGBGYF * (1 << Shift) + 0.5);
const int RGBGCbI = (int)(RGBGCbF * (1 << Shift) + 0.5);
const int RGBGCrI = (int)(RGBGCrF * (1 << Shift) + 0.5);
const int RGBBYI = (int)(RGBBYF * (1 << Shift) + 0.5);
const int RGBBCbI = (int)(RGBBCbF * (1 << Shift) + 0.5);
const int RGBBCrI = (int)(RGBBCrF * (1 << Shift) + 0.5);

Mat RGB2YCbCr(Mat src) {
	int row = src.rows;
	int col = src.cols;
	Mat dst(row, col, CV_8UC3);
	for (int i = 0; i < row; i++) {
		for (int j = 0; j < col; j++) {
			int Blue = src.at<Vec3b>(i, j)[0];
			int Green = src.at<Vec3b>(i, j)[1];
			int Red = src.at<Vec3b>(i, j)[2];
			dst.at<Vec3b>(i, j)[0] = (int)((YCbCrYRI * Red + YCbCrYGI * Green + YCbCrYBI * Blue + HalfShiftValue) >> Shift);
			dst.at<Vec3b>(i, j)[1] = (int)(128 + ((YCbCrCbRI * Red + YCbCrCbGI * Green + YCbCrCbBI * Blue + HalfShiftValue) >> Shift));
			dst.at<Vec3b>(i, j)[2] = (int)(128 + ((YCbCrCrRI * Red + YCbCrCrGI * Green + YCbCrCrBI * Blue + HalfShiftValue) >> Shift));
		}
	}
	return dst;
}

Mat YCbCr2RGB(Mat src) {
	int row = src.rows;
	int col = src.cols;
	Mat dst(row, col, CV_8UC3);
	for (int i = 0; i < row; i++) {
		for (int j = 0; j < col; j++) {
			int Y = src.at<Vec3b>(i, j)[0];
			int Cb = src.at<Vec3b>(i, j)[1] - 128;
			int Cr = src.at<Vec3b>(i, j)[2] - 128;
			int Red = Y + ((RGBRCrI * Cr + HalfShiftValue) >> Shift);
			int Green = Y + ((RGBGCbI * Cb + RGBGCrI * Cr + HalfShiftValue) >> Shift);
			int Blue = Y + ((RGBBCbI * Cb + HalfShiftValue) >> Shift);
			if (Red > 255) Red = 255; else if (Red < 0) Red = 0;
			if (Green > 255) Green = 255; else if (Green < 0) Green = 0;    // 编译后应该比三目运算符的效率高
			if (Blue > 255) Blue = 255; else if (Blue < 0) Blue = 0;
			dst.at<Vec3b>(i, j)[0] = Blue;
			dst.at<Vec3b>(i, j)[1] = Green;
			dst.at<Vec3b>(i, j)[2] = Red;
		}
	}
	return dst;
}

6.RGB和YDbDr互转

什么是YDbDr?

YDbDr是SECAM制式规格电视所采用的色彩空间。1966年法国提出SECAM彩色电视制式,并制度YDbDr。——转自百度百科
转化公式:
在这里插入图片描述
代码如下:

const float YDbDrYRF = 0.299F;              // RGB转YDbDr的系数(浮点类型)
const float YDbDrYGF = 0.587F;
const float YDbDrYBF = 0.114F;
const float YDbDrDbRF = -0.1688F;
const float YDbDrDbGF = -0.3312F;
const float YDbDrDbBF = 0.5F;
const float YDbDrDrRF = -0.5F;
const float YDbDrDrGF = 0.4186F;
const float YDbDrDrBF = 0.0814F;

const float RGBRYF = 1.00000F;            // YDbDr转RGB的系数(浮点类型)
const float RGBRDbF = 0.0002460817072494899F;
const float RGBRDrF = -1.402083073344533F;
const float RGBGYF = 1.00000F;
const float RGBGDbF = -0.344268308442098F;
const float RGBGDrF = 0.714219609001458F;
const float RGBBYF = 1.00000F;
const float RGBBDbF = 1.772034373903893F;
const float RGBBDrF = 0.0002111539810593343F;

const int Shift = 20;
const int HalfShiftValue = 1 << (Shift - 1);

const int YDbDrYRI = (int)(YDbDrYRF * (1 << Shift) + 0.5);         // RGB转YDbDr的系数(整数类型)
const int YDbDrYGI = (int)(YDbDrYGF * (1 << Shift) + 0.5);
const int YDbDrYBI = (int)(YDbDrYBF * (1 << Shift) + 0.5);
const int YDbDrDbRI = (int)(YDbDrDbRF * (1 << Shift) + 0.5);
const int YDbDrDbGI = (int)(YDbDrDbGF * (1 << Shift) + 0.5);
const int YDbDrDbBI = (int)(YDbDrDbBF * (1 << Shift) + 0.5);
const int YDbDrDrRI = (int)(YDbDrDrRF * (1 << Shift) + 0.5);
const int YDbDrDrGI = (int)(YDbDrDrGF * (1 << Shift) + 0.5);
const int YDbDrDrBI = (int)(YDbDrDrBF * (1 << Shift) + 0.5);

const int RGBRYI = (int)(RGBRYF * (1 << Shift) + 0.5);              // YDbDr转RGB的系数(整数类型)
const int RGBRDbI = (int)(RGBRDbF * (1 << Shift) + 0.5);
const int RGBRDrI = (int)(RGBRDrF * (1 << Shift) + 0.5);
const int RGBGYI = (int)(RGBGYF * (1 << Shift) + 0.5);
const int RGBGDbI = (int)(RGBGDbF * (1 << Shift) + 0.5);
const int RGBGDrI = (int)(RGBGDrF * (1 << Shift) + 0.5);
const int RGBBYI = (int)(RGBBYF * (1 << Shift) + 0.5);
const int RGBBDbI = (int)(RGBBDbF * (1 << Shift) + 0.5);
const int RGBBDrI = (int)(RGBBDrF * (1 << Shift) + 0.5);

Mat RGB2YDbDr(Mat src) {
	int row = src.rows;
	int col = src.cols;
	Mat dst(row, col, CV_8UC3);
	for (int i = 0; i < row; i++) {
		for (int j = 0; j < col; j++) {
			int Blue = src.at<Vec3b>(i, j)[0];
			int Green = src.at<Vec3b>(i, j)[1];
			int Red = src.at<Vec3b>(i, j)[2];
			dst.at<Vec3b>(i, j)[0] = (uchar)((YDbDrYRI * Red + YDbDrYGI * Green + YDbDrYBI * Blue + HalfShiftValue) >> Shift);
			dst.at<Vec3b>(i, j)[1] = (uchar)(128 + ((YDbDrDbRI * Red + YDbDrDbGI * Green + YDbDrDbBI * Blue + HalfShiftValue) >> Shift));
			dst.at<Vec3b>(i, j)[2] = (uchar)(128 + ((YDbDrDrRI * Red + YDbDrDrGI * Green + YDbDrDrBI * Blue + HalfShiftValue) >> Shift));
		}
	}
	return dst;
}

Mat YDbDr2RGB(Mat src) {
	int row = src.rows;
	int col = src.cols;
	Mat dst(row, col, CV_8UC3);
	for (int i = 0; i < row; i++) {
		for (int j = 0; j < col; j++) {
			int Y = src.at<Vec3b>(i, j)[0];
			int Db = src.at<Vec3b>(i, j)[1] - 128;
			int Dr = src.at<Vec3b>(i, j)[2] - 128;
			int Red = Y + ((RGBRDbI * Db + RGBRDrI * Dr + HalfShiftValue) >> Shift);
			int Green = Y + ((RGBGDbI * Db + RGBGDrI * Dr + HalfShiftValue) >> Shift);
			int Blue = Y + ((RGBBDbI * Db + RGBBDrI * Dr + HalfShiftValue) >> Shift);
			if (Red > 255) Red = 255;
			else if (Red < 0) Red = 0;
			if (Green > 255) Green = 255;
			else if (Green < 0) Green = 0;
			if (Blue > 255) Blue = 0;
			else if (Blue < 0) Blue = 0;
			dst.at<Vec3b>(i, j)[0] = Blue;
			dst.at<Vec3b>(i, j)[1] = Green;
			dst.at<Vec3b>(i, j)[2] = Red;
		}
	}
	return dst;
}
2018-06-14 22:02:16 thecentry 阅读数 6106
//添加椒盐噪声
void salt(Mat& src,int number)
{
	for (int i = 0; i < number; i++)
	{
		int r = static_cast<int>(rng.uniform(0, src.rows));
		int c = static_cast<int>(rng.uniform(0, src.cols));
		int k = (static_cast<int>(rng.uniform(0, 1000))&1);
		if(k==1)
		    src.at<uchar>(r, c) = 255;
		else
			src.at<uchar>(r, c) = 0;
	}
	return;
}

 

/*
* @ drt :高斯方差
* @ Medium :高斯均值
*/
int Get_Gauss(int Medium, int drt)
{
	//产生高斯样本,以U为均值,D为均方差
	double sum = 0;
	for (int i = 0; i<12; i++) 
		sum += rand() / 32767.00;
	//计算机中rand()函数为-32767~+32767(2^15-1)
	//故sum+为0~1之间的均匀随机变量
	return int(Medium + drt*(sum - 6));
	//产生均值为U,标准差为D的高斯分布的样本,并返回
}
/*
* variance :高斯噪声的方差
*/

//添加高斯噪声
void ImgAddGaussNoise1( uchar * dstImgbuff, int srcwith, int srcheigh, int chanels)
{
	assert( srcwith > 0 && srcheigh > 0);
	int bytecount = srcwith * srcheigh * chanels;

	for (size_t i = 0; i < bytecount; i++)
	{
		int  iTemp = dstImgbuff[i] + Get_Gauss(0, 20);
		iTemp = iTemp > 255 ? 255 : iTemp;
		iTemp = iTemp < 0 ? 0 : iTemp;
		dstImgbuff[i] = iTemp;
	}
}

 

//均值求取
void Meanvalue(Mat* src, int indexrows, int indexcols, float* meanv, int ker)
{
	int lo = (ker - 1) / 2;
	float total = 0;
	for (int i = indexrows - lo; i <= indexrows + lo; i++)
	{
		for (int j = indexcols - lo; j <= indexcols + lo; j++)
		{
			total += src->at<uchar>(i, j);
		}
	}
	*meanv = total / (ker * ker);
	return;
}

 

//中值求取
void Media(Mat* src, int indexrows, int indexcols, int* meanv, int ker)
{
	int lo = (ker - 1) / 2;
	vector<int>moreo;
	for (int i = indexrows - lo; i <= indexrows + lo; i++)
	{
		for (int j = indexcols - lo; j <= indexcols + lo; j++)
		{
			moreo.push_back(src->at<uchar>(i, j));
		}
	}
	sort(moreo.begin(), moreo.end());
	*meanv = moreo.at(ker * ker / 2);
	return;
}

 

//局部方差求取
void Vvalue(Mat* src, int indexrows, int indexcols, int* vall, int ker, float mean)
{
	int lo = (ker - 1) / 2;
	float total = 0;
	for (int i = indexrows - lo; i <= indexrows + lo; i++)
	{
		for (int j = indexcols - lo; j <= indexcols + lo; j++)
		{
			total += pow((src->at<uchar>(i, j) - mean), 2);
		}
	}
	*vall = static_cast<int>(total);
	return;
}
//像素方差
void Variance(Mat& src, vector<test>& hierachy, int ker)
{
	int row = src.rows;
	int col = src.cols;
	int lo = (ker - 1) / 2;
	for (int ir = lo; ir < row - lo; ir++)
	{
		for (int jc = lo; jc < col - lo; jc++)
		{
			float means;
			int var;
			//计算均值
			Meanvalue(&src, ir, jc, &means, ker);
			Vvalue(&src, ir, jc, &var, ker, means);
			test temp;
			temp.menval = var;
			temp.x = ir;
			temp.y = jc;
			hierachy.push_back(temp);
		}
	}
	return;
}
//STL排序方式
bool SortByM1(const test &v1, const test &v2)//注意:本函数的参数的类型一定要与vector中元素的类型一致  
{
	return v1.menval < v2.menval;//升序排列  
}
//SSIM 结构相似比
Scalar getMSSIM(const Mat& i1, const Mat& i2)
{
	const double C1 = 6.5025, C2 = 58.5225;
	/***************************** INITS **********************************/
	int d = CV_32F;

	Mat I1, I2;
	i1.convertTo(I1, d);           // cannot calculate on one byte large values
	i2.convertTo(I2, d);

	int num = I1.channels();
	//cv::imshow("123", I1);
	//cv::waitKey();

	Mat I2_2 = I2.mul(I2);        // I2^2
	Mat I1_2 = I1.mul(I1);        // I1^2
	Mat I1_I2 = I1.mul(I2);        // I1 * I2

								   /*************************** END INITS **********************************/

	Mat mu1, mu2;   // PRELIMINARY COMPUTING
	GaussianBlur(I1, mu1, Size(11, 11), 1.5);
	GaussianBlur(I2, mu2, Size(11, 11), 1.5);

	Mat mu1_2 = mu1.mul(mu1);
	Mat mu2_2 = mu2.mul(mu2);
	Mat mu1_mu2 = mu1.mul(mu2);

	Mat sigma1_2, sigma2_2, sigma12;

	GaussianBlur(I1_2, sigma1_2, Size(11, 11), 1.5);
	sigma1_2 -= mu1_2;

	GaussianBlur(I2_2, sigma2_2, Size(11, 11), 1.5);
	sigma2_2 -= mu2_2;

	GaussianBlur(I1_I2, sigma12, Size(11, 11), 1.5);
	sigma12 -= mu1_mu2;

	///////////////////////////////// FORMULA ////////////////////////////////
	Mat t1, t2, t3;

	t1 = 2 * mu1_mu2 + C1;
	t2 = 2 * sigma12 + C2;
	t3 = t1.mul(t2);              // t3 = ((2*mu1_mu2 + C1).*(2*sigma12 + C2))

	t1 = mu1_2 + mu2_2 + C1;
	t2 = sigma1_2 + sigma2_2 + C2;
	t1 = t1.mul(t2);               // t1 =((mu1_2 + mu2_2 + C1).*(sigma1_2 + sigma2_2 + C2))

	Mat ssim_map;
	divide(t3, t1, ssim_map);      // ssim_map =  t3./t1;

	Scalar mssim = mean(ssim_map); // mssim = average of ssim map
	return mssim;
}
//功能:局部均值求取 局部方差求取
/* zc 2018/07/08
parameters:
Mat*         src;         //待处理的图像
float*       meanv;       //保存局部均值
float*       dev;         //保存局部方差
int          indexrows;   //要求局部所在行
int          indexcols;   //要求局部所在列
int          ker;         //窗口大小系数
*/
void Meanvalue(Mat* src, float* meanv, float* dev, int indexrows, int indexcols,  int ker)
{
	int lo = (ker - 1) / 2;
	float total = 0;
	float total2 = 0;
	for (int i = indexrows - lo; i <= indexrows + lo; i++)
	{
		for (int j = indexcols - lo; j <= indexcols + lo; j++)
		{
			float temp = static_cast<float>(src->at<uchar>(i, j));
			total += temp;
			total2 += temp*temp;
		}
	}
	int size = ker * ker;
	*meanv = total / size;                                      //均值
	*dev = (total2 - (total*total) / size) / size;              //方差
	return;
}

 

 

 

 

 

 

 

 

 

2017-07-09 21:23:39 u010455056 阅读数 765

OpenCV图像处理算法——初窥(一)

简介

本人也是新兵,一边学一边写,所以称为一篇入门笔记更恰当。但我觉得起个更能突出数学在编程中的重要性的名字也不是不可以,(绝非标题党)。这是一篇OpenCV描述基本图像处理算法的系列文章,主要涉及矩阵知识(推荐可汗学院线性代数公开课 https://www.khanacademy.org/math/linear-algebra)。不是文档的翻译,官方文档教程教程中能查到的术语示例,本文将不再赘述。
在下学习方向前端开发(数据可视化,web动画等),希望觅得同道知音一起拼搏江湖。打算一周写一篇,保证质量,初次写系列文章,难免会出错,期待能一起探讨学习 (邮箱 dferic@foxmail.com)。

准备

环境:OpenCV-3.2.0 + Visual Studio-2015 环境配置参考文档 http://docs.opencv.org/
配置完成后一个简单的示例来了解OpenCV中图像-矩阵关系,主要涉及Mat类,文档上有详尽介绍。

#include "stdafx.h"
#include "opencv2/imgcodecs.hpp"
#include <opencv2/highgui.hpp>
#include <iostream>

using namespace cv;

int main(void)
{
    Mat src1,src2,dst;
    src1 = imread("source.png",-1);//读入带alpha的png图片
    if (src1.empty()) { std::cout << "error load src1" << std::endl; return -1; };
    src2 = Mat(5, 5, CV_8UC4, Scalar(0, 0, 255,100));//用Mat构造函数创建一个5*5的Mat类
    dst = src1 + src2;//矩阵相加
    std::cout << "src1:" << src1 << std::endl;
    std::cout << "src2" <<src2 << std::endl;
    std::cout << "dst" <<dst << std::endl;
    imwrite("target.png", dst);//写入文件
    imshow("",dst);
    waitKey(0);
    return 0;
}

运行结果:
demo1

  1. 有不理解的函数希望能看看文档,或者直接转到声明-
  2. 分别以图像读入和Mat构造方法创建了两个mat类,存储的就是描述图像的矩阵,简单相加之后,得到新的mat类,可直接写入为图像,所以对图像的处理也就转换成对矩阵的操作

addWeighted函数

一般是混合两张图片用的,我觉得翻译为加权处理更适合,函数结构如下:

g(x)=(1α)f0(x)+αf1(x)

具体操作:

Mat src1,src2,dst;
    double alpha = 0.5;
    double beta = 1.0 - alpha;
    src1 = imread("source.png",-1);
    if (src1.empty()) { std::cout << "error load src1" << std::endl; return -1; };
    src2 = Mat(5, 5, CV_8UC4, Scalar(0, 0, 255,100));
    addWeighted(src1,alpha,src2,beta,0.0,dst);
    std::cout << "src1:" << std::endl << src1 << std::endl;
    std::cout << "src2" << std::endl << src2 << std::endl;
    std::cout << "dst" << std::endl << dst << std::endl;
    imwrite("target.png", dst);

结果:
demo2

这个OpenCV函数更直观描述了矩阵操作与图像图例之间关系,更复杂的图像操作就是矩阵的更复杂运算。

addWeighted(src1,alpha,src2,beta,0.0dst);

等价于
dst=alphasrc1+betasrc2+0.0;

结语

很后悔大学没有把线性代数这门课好好学,那时候真的不知道,也没人能让我知道数学和编程真的很紧密。如果有位老师能让我明白这门课在编程中重要性,我想开这门课的目的就达到一大半了。。。扯远了,如果忘了线代,找些资料重新学习学习,IT本来就是要不断学习,不断积累才会在行业中能走的更远

2018-10-28 23:02:40 Aidam_Bo 阅读数 810


 

//添加椒盐噪声
void salt(Mat& src,int number)
{
    for (int i = 0; i < number; i++)
    {
        int r = static_cast<int>(rng.uniform(0, src.rows));
        int c = static_cast<int>(rng.uniform(0, src.cols));
        int k = (static_cast<int>(rng.uniform(0, 1000))&1);
        if(k==1)
            src.at<uchar>(r, c) = 255;
        else
            src.at<uchar>(r, c) = 0;
    }
    return;
}
/*
* @ drt :高斯方差
* @ Medium :高斯均值
*/
int Get_Gauss(int Medium, int drt)
{
    //产生高斯样本,以U为均值,D为均方差
    double sum = 0;
    for (int i = 0; i<12; i++) 
        sum += rand() / 32767.00;
    //计算机中rand()函数为-32767~+32767(2^15-1)
    //故sum+为0~1之间的均匀随机变量
    return int(Medium + drt*(sum - 6));
    //产生均值为U,标准差为D的高斯分布的样本,并返回
}
/*
* variance :高斯噪声的方差
*/


 

//添加高斯噪声
void ImgAddGaussNoise1( uchar * dstImgbuff, int srcwith, int srcheigh, int chanels)
{
    assert( srcwith > 0 && srcheigh > 0);
    int bytecount = srcwith * srcheigh * chanels;
 
    for (size_t i = 0; i < bytecount; i++)
    {
        int  iTemp = dstImgbuff[i] + Get_Gauss(0, 20);
        iTemp = iTemp > 255 ? 255 : iTemp;
        iTemp = iTemp < 0 ? 0 : iTemp;
        dstImgbuff[i] = iTemp;
    }
}


 

//均值求取
void Meanvalue(Mat* src, int indexrows, int indexcols, float* meanv, int ker)
{
    int lo = (ker - 1) / 2;
    float total = 0;
    for (int i = indexrows - lo; i <= indexrows + lo; i++)
    {
        for (int j = indexcols - lo; j <= indexcols + lo; j++)
        {
            total += src->at<uchar>(i, j);
        }
    }
    *meanv = total / (ker * ker);
    return;
}
//中值求取
void Media(Mat* src, int indexrows, int indexcols, int* meanv, int ker)
{
    int lo = (ker - 1) / 2;
    vector<int>moreo;
    for (int i = indexrows - lo; i <= indexrows + lo; i++)
    {
        for (int j = indexcols - lo; j <= indexcols + lo; j++)
        {
            moreo.push_back(src->at<uchar>(i, j));
        }
    }
    sort(moreo.begin(), moreo.end());
    *meanv = moreo.at(ker * ker / 2);
    return;
}


 

//局部方差求取
void Vvalue(Mat* src, int indexrows, int indexcols, int* vall, int ker, float mean)
{
    int lo = (ker - 1) / 2;
    float total = 0;
    for (int i = indexrows - lo; i <= indexrows + lo; i++)
    {
        for (int j = indexcols - lo; j <= indexcols + lo; j++)
        {
            total += pow((src->at<uchar>(i, j) - mean), 2);
        }
    }
    *vall = static_cast<int>(total);
    return;
}

 

//像素方差
void Variance(Mat& src, vector<test>& hierachy, int ker)
{
    int row = src.rows;
    int col = src.cols;
    int lo = (ker - 1) / 2;
    for (int ir = lo; ir < row - lo; ir++)
    {
        for (int jc = lo; jc < col - lo; jc++)
        {
            float means;
            int var;
            //计算均值
            Meanvalue(&src, ir, jc, &means, ker);
            Vvalue(&src, ir, jc, &var, ker, means);
            test temp;
            temp.menval = var;
            temp.x = ir;
            temp.y = jc;
            hierachy.push_back(temp);
        }
    }
    return;
}

 

//STL排序方式
bool SortByM1(const test &v1, const test &v2)//注意:本函数的参数的类型一定要与vector中元素的类型一致  
{
    return v1.menval < v2.menval;//升序排列  
}

 

//SSIM 结构相似比
Scalar getMSSIM(const Mat& i1, const Mat& i2)
{
    const double C1 = 6.5025, C2 = 58.5225;
    /***************************** INITS **********************************/
    int d = CV_32F;
 
    Mat I1, I2;
    i1.convertTo(I1, d);           // cannot calculate on one byte large values
    i2.convertTo(I2, d);
 
    int num = I1.channels();
    //cv::imshow("123", I1);
    //cv::waitKey();
 
    Mat I2_2 = I2.mul(I2);        // I2^2
    Mat I1_2 = I1.mul(I1);        // I1^2
    Mat I1_I2 = I1.mul(I2);        // I1 * I2
 
                                   /*************************** END INITS **********************************/
 
    Mat mu1, mu2;   // PRELIMINARY COMPUTING
    GaussianBlur(I1, mu1, Size(11, 11), 1.5);
    GaussianBlur(I2, mu2, Size(11, 11), 1.5);
 
    Mat mu1_2 = mu1.mul(mu1);
    Mat mu2_2 = mu2.mul(mu2);
    Mat mu1_mu2 = mu1.mul(mu2);
 
    Mat sigma1_2, sigma2_2, sigma12;
 
    GaussianBlur(I1_2, sigma1_2, Size(11, 11), 1.5);
    sigma1_2 -= mu1_2;
 
    GaussianBlur(I2_2, sigma2_2, Size(11, 11), 1.5);
    sigma2_2 -= mu2_2;
 
    GaussianBlur(I1_I2, sigma12, Size(11, 11), 1.5);
    sigma12 -= mu1_mu2;
 
    ///////////////////////////////// FORMULA ////////////////////////////////
    Mat t1, t2, t3;
 
    t1 = 2 * mu1_mu2 + C1;
    t2 = 2 * sigma12 + C2;
    t3 = t1.mul(t2);              // t3 = ((2*mu1_mu2 + C1).*(2*sigma12 + C2))
 
    t1 = mu1_2 + mu2_2 + C1;
    t2 = sigma1_2 + sigma2_2 + C2;
    t1 = t1.mul(t2);               // t1 =((mu1_2 + mu2_2 + C1).*(sigma1_2 + sigma2_2 + C2))
 
    Mat ssim_map;
    divide(t3, t1, ssim_map);      // ssim_map =  t3./t1;
 
    Scalar mssim = mean(ssim_map); // mssim = average of ssim map
    return mssim;
}
//功能:局部均值求取 局部方差求取
/* zc 2018/07/08
parameters:
Mat*         src;         //待处理的图像
float*       meanv;       //保存局部均值
float*       dev;         //保存局部方差
int          indexrows;   //要求局部所在行
int          indexcols;   //要求局部所在列
int          ker;         //窗口大小系数
*/
void Meanvalue(Mat* src, float* meanv, float* dev, int indexrows, int indexcols,  int ker)
{
    int lo = (ker - 1) / 2;
    float total = 0;
    float total2 = 0;
    for (int i = indexrows - lo; i <= indexrows + lo; i++)
    {
        for (int j = indexcols - lo; j <= indexcols + lo; j++)
        {
            float temp = static_cast<float>(src->at<uchar>(i, j));
            total += temp;
            total2 += temp*temp;
        }
    }
    int size = ker * ker;
    *meanv = total / size;                                      //均值
    *dev = (total2 - (total*total) / size) / size;              //方差
    return;
}



 
 

2020-05-05 15:11:57 weixin_38278993 阅读数 176

一、直方图和滤波器

1、直方图:在灰度图中,每个点的像素范围为 0~255 ,密度是具有该值的图像像素数量。

2、色彩均衡:色彩均衡的目的是获得分布更加均匀的直方图。其结果将会导致图像的对比度增加。

3、LUT色彩空间转换表:生成一个256个元素的矩阵,取得要从源空间变换到目标色彩空间的变换函数f(x)。从0到255,对每个数字带入到f(x)中,将得到的结果存入到矩阵中对应的位置。然后,对要处理的图像使用这个表就可以将全部色彩元素对应到相应的色彩空间,这样节省了大量的计算性能。

4、滤波器:这里滤波器我们可以理解为一个矩阵,将这个矩阵在对应的图像上滑过与图像上的每个点做点积运算(这个过程叫做卷积),即对图像进行了处理。滤波器主要分为两种:去噪声滤波器、边缘检测滤波器

1、去噪声滤波器:代表 高斯滤波器、均值滤波器、中值滤波器

高斯滤波器:高斯滤波器是一种线性滤波器,其卷积模板中的系数随着与模板中心的距离增大而减小,相比于均值滤波器,高斯滤波器对整个图像模糊程度较小,能够有效抑制噪声,平滑图像。

均值滤波器:使用相邻像素的平均值来代替原来的像素值,采用线性的方式,对整个图像起到平滑的作用,但也破坏了图像的细节信息,使得图像变得模糊。

中值滤波器:中值滤波器采用非线性的方式,使用卷积模板的中值来代替像素值,适用于处理椒盐噪声。

2、边缘检测滤波器(算子和滤波器的关系点这里查看

sobel滤波器:Sobel算子是典型的基于一阶导数的边缘检测算子,是离散型的差分算子。

Roberts算子:Roberts边缘算子是一个2x2的模板,采用的是对角方向相邻的两个像素之差。从图像处理的实际效果来看,边缘定位较准,对噪声敏感。适用于边缘明显且噪声较少的图像分割。

Canny算子:该算子功能比前面几种都要好,但是它实现起来较为麻烦,Canny算子是一个具有滤波,增强,检测的多阶段的优化算子,在进行处理前,Canny算子先利用高斯平滑滤波器来平滑图像以除去噪声,Canny分割算法采用一阶偏导的有限差分来计算梯度幅值和方向,在处理过程中,Canny算子还将经过一个非极大值抑制的过程,最后Canny算子还采用两个阈值来连接边缘。

 

二、光学检查、对象分割、检测

1、背景去除算法

①减法 R = L - I:先取一张背景图,然后用包含检测目标的图与背景图做差,剩下的即为检测目标

②除法 R = 255 * (1 - (I / L)):方法同上不过运算时改用除法。

(注:如果无法获取背景,通常我们可以将原图采用图像大小的核的模糊方法,来获取背景)

2、二值化:即将图片分成色彩只有0和255的图像,一般用于灰度图。

3、连通组件:用于分割和识别二进制图像(二值化后的图像),如果两个像素具有相同的值并且他们是邻居则把他们连起来,有四连通、八连通两种形式。

4、查找轮廓:利用像素颜色深度等高线查找轮廓。

 

三、检测面部部位与覆盖面具

1、haar级联:一个基于haar特征的级联分类器,它是一组弱分类器的串联。(CascadeClassifier OpenCV中的类)

2、积分图像:简单来说就是以原点到各点所构成的矩形的面积做加减运算,从而得到所需要的计算区域的方法。这种方法因为计算公式固定不会受到图像放大或缩小的影响。

 

四、视频监控、背景建模、形态学操作

1、帧差分:计算当前帧与前一帧之间,以及当前帧与下一帧之间的绝对差值。然后采用这些帧差异并应用按位AND运算符。这一步运算将会突出显示图像中的移动部分。这种方法的主要问题是检测均匀着色的对象它只能检测均匀着色对象的边缘,原因是该对象的很大一部分有这非常低的像素差异。另外一个问题是无法检测对象是否朝相机运动或者远离相机运动。

2、高斯混合法:再每一帧图像的连续收集中每个部分将逐渐成为背景的一部分。如果场景是静态的,则模型会自行调整以确保背景模型被更新。

3、侵蚀:通过剥离图像中所有形状的边界层从而使形状变细的操作。

4、膨胀:通过向图像中所有的形状添加边界层来使形状变粗的操作。

5、形态开口:先侵蚀后膨胀

6、形态闭合:先膨胀后侵蚀

7、获取边界:获取膨胀和侵蚀之间的差异来获取

8、礼貌变换:输入图像与形态开口之间的差异。

9、黑帽变换:形态闭合与输入图像之间的差异。

 

五、对象跟踪

HSV:一种色彩空间,H色值,S饱和度,V明度

meanshift:当我们希望追踪一个感兴趣区域(ROI)时,在这个区域,我们根据颜色直方图选择若干点,并计算空间图心,如果图心位于该区域中心我们就知道这个对象没有移动,如果图心不在这个区域中心,我们就知道这个对象在某个方向移动了。它的问题是不允许改变边界框的大小。

CAMShift:在meanshift的基础上可以使边界适应对象的大小。选择好对象以后计算该对象的直方图反投影(一种识别图像与直方图模型匹配程度的方法)并提取所有信息。a、计算特定对象的直方图模型。b、使用这个模型在图像中找到该事物。 

兴趣点:在计算机视觉中也被称为特征点,或者特征。兴趣点基本上是图像中唯一检测到的到东西。

Harris角点检测:要确定一块区域是否有一个角点,考虑所有的相邻区域并计算所有相邻区域的强度差异。如果在所有方向的差异都很大,那么我们就知道该区域有一个角点。

流光跟踪:稠密流光跟踪(Lucas-Kanade)、稀疏流光跟踪(Farneback)(算法详解

 

 

 

 

 

图片来源:

https://zhuanlan.zhihu.com/p/111142437

https://zhuanlan.zhihu.com/p/56728333