彩色图像处理

Author Avatar
Euan 10月 15, 2019
  • 在其它设备中阅读本文章

彩色图像处理

实验目的

  1. 理解彩色图像的不同格式;
  2. 掌握彩色图像模型间的转化方法
  3. 掌握伪彩色图像增强及其实现方法

实验原理

彩色图像处理、伪彩色图像增强处理

实验内容或步骤

1)对“lena.jpg” 的彩色图像分离出R、G、B三个通道图像,分别采用直方图均衡化处理,分别显示,然后再合并为一张图像保存;
2)对“lena.jpg” 的彩色图像分离为H、S、V三个通道图像,分别采用平滑滤波处理,分别显示,且合并为一张图像保存;
3)对“车牌.jpg”彩色图像,使用HSV颜色模型,提取出车牌区域

参考:
不同色彩空间转换(void cv::cvtColor (InputArray src, OutputArray dst, int code, int dstCn=0))
https://docs.opencv.org/3.4.5/d8/d01/group__imgproc__color__conversions.html
https://blog.csdn.net/u011574296/article/details/70896811?locationNum=14&fps=1
多通道分离(void split (const Mat& src, Mat* mvbegin))
合并(void merge (InputArrayOfArrays mv, OutputArray dst))
https://docs.opencv.org/2.4/modules/core/doc/operations_on_arrays.html?highlight=split#void split(const Mat& src, Mat* mvbegin)
https://docs.opencv.org/2.4/modules/core/doc/operations_on_arrays.html?highlight=merge#void merge(InputArrayOfArrays mv, OutputArray dst)
https://blog.csdn.net/u011574296/article/details/70896811?locationNum=14&fps=1
HSV色彩空间
https://blog.csdn.net/taily_duan/article/details/51506776

实验报告

(1)描述实验的基本原理和步骤
(2)用数据和图片给出各个步骤中取得的实验结果 ,包括原始图像及其处理后的图像,并进行必要的讨论
(3)完整的源代码

实验内容

对“lena.jpg” 的彩色图像分离出R、G、B三个通道图像,分别采用直方图均衡化处理,分别显示,然后再合并为一张图像保存

K1lkh6.png

分离通道采用split函数,通过这个函数输出的多通道序列,顺序为bgr不是RGB!后通过push_back对空白矩阵和通道矩阵进行填充后得到通道分离后的图像。

直方图均衡把实验四的均衡化函数cp过来后,进行调用,由于是空白矩阵和grb单通道矩阵的均衡化,显示的即为但单通道矩阵。并且发现色色调貌似增加了180度(红变青色,绿色变品红,蓝色变黄色)

合并图像采用merge函数和push_back函数,依次调用三次单通道矩阵后即为合并后的图像。

补色合并

补色公式为:补色 = Max(R,G,B) + Min(R,G,B) – 原色

K4W4bQ.png

该图像适合色弱者

对“lena.jpg” 的彩色图像分离为H、S、V三个通道图像,分别采用平滑滤波处理,分别显示,且合并为一张图像保存

区别:RGB颜色模型都是面向硬件的,而HSV颜色模型是面向用户的。HSV模型的三维表示从RGB立方体演化而来。设想从RGB沿立方体对角线的白色顶点向黑色顶点观察,就可以看到立方体的六边形外形。六边形边界表示色彩,水平轴表示纯度,明度沿垂直轴测量。

补补知识:
色调H
用角度度量,取值范围为0°~360°,从红色开始按逆时针方向计算,红色为0°,绿色为120°,蓝色为240°。它们的补色是:黄色为60°,青色为180°,品红为300°;
饱和度S
饱和度S表示颜色接近光谱色的程度。一种颜色,可以看成是某种光谱色与白色混合的结果。其中光谱色所占的比例愈大,颜色接近光谱色的程度就愈高,颜色的饱和度也就愈高。饱和度高,颜色则深而艳。光谱色的白光成分为0,饱和度达到最高。通常取值范围为0%~100%,值越大,颜色越饱和。
明度V
明度表示颜色明亮的程度,对于光源色,明度值与发光体的光亮度有关;对于物体色,此值和物体的透射比或反射比有关。通常取值范围为0%(黑)到100%(白)。

GaussianBlur
函数原型:
void GaussianBlur(InputArray src, OutputArray dst, Size ksize, double sigmaX, double sigmaY=0, int borderType=BORDER_DEFAULT);
参数详解如下:
src,输入图像,即源图像,填Mat类的对象即可。它可以是单独的任意通道数的图片,但需要注意,图片深度应该为CV_8U,CV_16U, CV_16S, CV_32F 以及 CV_64F之一。
dst,即目标图像,需要和源图片有一样的尺寸和类型。比如可以用Mat::Clone,以源图片为模板,来初始化得到如假包换的目标图。
ksize,高斯内核的大小。其中ksize.width和ksize.height可以不同,但他们都必须为正数和奇数(并不能理解)。或者,它们可以是零的,它们都是由sigma计算而来。
sigmaX,表示高斯核函数在X方向的的标准偏差。
sigmaY,表示高斯核函数在Y方向的的标准偏差。若sigmaY为零,就将它设为sigmaX,如果sigmaX和sigmaY都是0,那么就由ksize.width和ksize.height计算出来。

K1lE9K.png

颜色空间转换(二)转换函数 cvtColor()

老师推荐的这篇博客是好文章,通过cvtColor函数转换为HSV,并且知道了还可以转换为Lab,不用从头为hsv进行转换分割

分割还是使用split,滤波采用了高斯滤波,一开始使用滑动条自动改变滤波的内核值,后来做到合并的时候无法实时合并,所以就去除了了滑动条,内核默认为(5,5)

合并采用merge进行mat合并

对“车牌.jpg”彩色图像,使用HSV颜色模型,提取出车牌区域

之前做过车牌识别神经网络的比赛,不过因为太忙就弃赛了,这次实验就当作车牌识别的预处理把

这个实验的原理就跟步骤差不多把,代码也注释得很详细了,其中不懂的函数也都查过且记住

  1. 求得原图像的sobel边缘
  2. 在HSV空间内利用车牌颜色阈值对图像进行二值化处理,一开始不知道调什么值,然后做成滑动条自己调试,然后值确定后,再给为明确参数调用
  3. 由下面的判别标准得到图像bw_blue_edge
  4. 再用边缘检测提取矩形轮廓
  5. 找出最大的矩形,并提取其中的左上角坐标和长宽,使用ROI进行截取

K1lG38.png

K1lnnH.png

K1leje.png

在网上找了其他的车牌图片(黑色车辆),提取成功

K1lZcD.png

改进

  • 在进行轮廓提取时可以使用形态学闭操作,补全小孔之类的,使得矩形更加完整
  • 代码中的找出最大的矩形时自己调试而出的,可以进行遍历查找,不过由于懒(累),这个也只是个实验测试,如果要保存其他的车牌则需要添加遍历查找最大矩形的函数
  • 可以对车牌进行矫正,可以使用仿射变换或者透视变换进行旋转

对“gf3.jpg”灰度图像转成伪彩色图,并显示和保存

伪彩色:自定义连续范围为多个彩色图像
彩虹图:根据一定的算法转换成多个彩色图像

转换过程中,RGB 彩色图像可以使用 ,类型分别存放RGB三个数值,该题直接看了老师推荐的文档,代码也很简单每个彩色设置阀值后显示就可以了.

K4csSO.png

源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406

#include <opencv2/opencv.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/core/core.hpp>
using namespace cv;

using namespace std;
using namespace cv;


int gray[256] = { 0 }; //记录每个灰度级别下的像素个数
double gray_prob[256] = { 0 }; //记录灰度分布密度
double gray_distribution[256] = { 0 }; //记录累计密度
int gray_equal[256] = { 0 }; //均衡化后的灰度值
int gray_sum = 0; //像素总数
int g_nGaussianBlurValue = 6; //高斯滤波内核值

//灰度值归一化
Mat bgr;
//输入图像
Mat img;
//HSV图像
Mat hsv;
//色相
int hmin = 67;
int hmin_Max = 360;
int hmax = 325;
int hmax_Max = 360;
//饱和度
int smin = 139;
int smin_Max = 255;
int smax = 255;
int smax_Max = 255;
//亮度
int vmin = 109;
int vmin_Max = 255;
int vmax = 237;
int vmax_Max = 255;
//输出图像
Mat out;
int threadhold_value = 100;
Mat range,canny_out, graysrc,dstt,rects;


void Hist_Eql(Mat image)
{
//计算像素分布
int NumPixel[4][256] = { 0 };
for (int y = 0; y < image.rows; ++y)
for (int x = 0; x < image.cols; ++x)
for (int c = 0; c < image.channels(); c++)
NumPixel[c][image.at<Vec3b>(y, x)[c]] += 1;

//计算像素分布密度
double ProbPixel[4][256] = { 0 };
for (int i = 0; i < image.channels(); i++)
for (int j = 0; j < 256; j++)
{
ProbPixel[i][j] = NumPixel[i][j] / (image.rows * image.cols * 1.0);
}

//计算累计直方图分布
double CumuPixel[4][256] = { 0 };
for (int i = 0; i < image.channels(); i++)
for (int j = 0; j < 256; j++)
{
if (j == 0)
CumuPixel[i][j] = ProbPixel[i][j];
else
CumuPixel[i][j] = CumuPixel[i][j - 1] + ProbPixel[i][j];
}

//累计分布取整
for (int i = 0; i < image.channels(); i++)
for (int j = 0; j < 256; j++)
{
CumuPixel[i][j] = (int)(CumuPixel[i][j] * 255 + 0.5);
}

//映射
for (int y = 0; y < image.rows; ++y)
for (int x = 0; x < image.cols; ++x)
image.at<Vec3b>(y, x) = Vec3b(CumuPixel[0][image.at<Vec3b>(y, x)[0]], CumuPixel[1][image.at<Vec3b>(y, x)[1]], CumuPixel[2][image.at<Vec3b>(y, x)[2]]);
}

//回调函数
Mat Bi_inrange(int, void*)
{
//输出图像分配内存
out = Mat::zeros(img.size(), CV_32FC3);
//掩码
Mat mask;
inRange(hsv, Scalar(hmin, smin / float(smin_Max), vmin / float(vmin_Max)), Scalar(hmax, smax / float(smax_Max), vmax / float(vmax_Max)), mask);
//只保留
for (int r = 0; r < bgr.rows; r++)
{
for (int c = 0; c < bgr.cols; c++)
{
if (mask.at<uchar>(r, c) == 255)
{
out.at<Vec3f>(r, c) = bgr.at<Vec3f>(r, c);
}
}
}
//输出图像
//imshow("inrange", out);
//保存图像
out.convertTo(out, CV_8UC3, 255.0, 0);
//imwrite("HSV_inRange.jpg", out);
return out;
}

Mat getContours(int, void*) {
Mat Canny_out;
vector<vector<Point>>points;//每个轮廓由一系列点组成
//vector<Vec4i> h;//层次 图像的拓扑结构
Canny(graysrc, Canny_out, threadhold_value, threadhold_value * 2);
vector<vector<Point>> contours;
vector<Vec4i> hierarcy;
findContours(Canny_out, points, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
//绘制轮廓
//因为要用不同颜色区分轮廓,所以这里用的CV_8UC3 3通道彩色图,如果是CV_8UC1 绘制的就是灰度图,
//感官上不好区分
vector<Rect> boundRect(contours.size()); //定义外接矩形集合
vector<RotatedRect> box(contours.size()); //定义最小外接矩形集合
Point2f rect[4];
for (int i = 0; i < contours.size(); i++)
{
box[i] = minAreaRect(Mat(contours[i])); //计算每个轮廓最小外接矩形
boundRect[i] = boundingRect(Mat(contours[i]));
circle(rects, Point(box[i].center.x, box[i].center.y), 5, Scalar(0, 255, 0), -1, 8); //绘制最小外接矩形的中心点
box[i].points(rect); //把最小外接矩形四个端点复制给rect数组
rectangle(rects, Point(boundRect[i].x, boundRect[i].y), Point(boundRect[i].x + boundRect[i].width, boundRect[i].y + boundRect[i].height), Scalar(0, 255, 0), 2, 8);
for (int j = 0; j < 4; j++)
{
line(rects, rect[j], rect[(j + 1) % 4], Scalar(0, 0, 255), 2, 8); //绘制最小外接矩形每条边
}
}
imshow("rect_range", rects);
/*
dstt = Mat::zeros(range.size(), CV_8UC3);
RNG rng(12345);
for (int i = 0; i < points.size(); i++) {
Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
drawContours(dstt, points, i, color);
//imshow("dst", dstt);
}*/
return rects;
}

int main()
{
Mat src = imread("lena.jpg");
//imshow("src", src);
// 分离多通道
vector<Mat> rgbChannels(3);
split(src, rgbChannels);

// 展示单独的色彩通道
Mat blank_ch, fin_img;
blank_ch = Mat::zeros(Size(src.cols, src.rows), CV_8UC1);

// R
// G和B通道保持为零矩阵
vector<Mat> channels_r;
channels_r.push_back(blank_ch);
channels_r.push_back(blank_ch);
channels_r.push_back(rgbChannels[2]);

/// 合并
merge(channels_r, fin_img);
imshow("R", fin_img);
Mat img_r;
img_r = fin_img;
Hist_Eql(img_r);
//imshow("hist_r", img_r);

// G
vector<Mat> channels_g;
channels_g.push_back(blank_ch);
channels_g.push_back(rgbChannels[1]);
channels_g.push_back(blank_ch);
merge(channels_g, fin_img);
imshow("G", fin_img);
Mat img_g;
img_g = fin_img;
Hist_Eql(img_g);
//imshow("hist_g", img_g);

// B
vector<Mat> channels_b;
channels_b.push_back(rgbChannels[0]);
channels_b.push_back(blank_ch);
channels_b.push_back(blank_ch);
merge(channels_b, fin_img);
imshow("B", fin_img);
Mat img_b;
img_b = fin_img;
Hist_Eql(img_b);
//imshow("hist_b", img_b);

// 补色

Mat dst1;
src.copyTo(dst1);
for (int i = 0; i < src.rows; ++i){
for (int j = 0; j < src.cols; ++j) {
int b = src.at<Vec3b>(i, j)[0];//blue
int g = src.at<Vec3b>(i, j)[1];//green
int r = src.at<Vec3b>(i, j)[2];//red
int maxrgb = max(max(r, g), b);
int minrgb = min(min(r, g), b);
dst1.at<Vec3b>(i, j)[0] = maxrgb + minrgb - b;
dst1.at<Vec3b>(i, j)[1] = maxrgb + minrgb - g;
dst1.at<Vec3b>(i, j)[2] = maxrgb + minrgb - r;
}
}
imshow("补色", dst1);

// 合并
vector<Mat> channels_merge;
channels_merge.push_back(rgbChannels[0]);
channels_merge.push_back(rgbChannels[1]);
channels_merge.push_back(rgbChannels[2]);
merge(channels_merge, fin_img);
// imshow("merge", fin_img);
imwrite("merge.png", fin_img);

//RGB->HSV,分割
Mat HSVImage,Image[3],guass_h, guass_s, guass_v;
cvtColor(src, HSVImage, COLOR_BGR2HSV);
split(HSVImage, Image);
//imshow("HSV", HSVImage);

//高斯
//imshow("H", Image[0]);
GaussianBlur(Image[0], guass_h, cv::Size(5, 5), 3, 3);
//imshow("guass_h", guass_h);
//imshow("S", Image[1]);
GaussianBlur(Image[1], guass_s, cv::Size(5, 5), 3, 3);
//imshow("guass_s", guass_s);
//imshow("V", Image[2]);
GaussianBlur(Image[2], guass_v, cv::Size(5, 5), 3, 3);
//imshow("guass_v", guass_v);

//通道合并
Mat newChannels[3] = { guass_h, guass_s, guass_v };
Mat mergedHSV;
merge(newChannels, 3, mergedHSV);
//imshow("mergedHSV", mergedHSV);
imwrite("mergedHSV.png", mergedHSV);

// 第三问 车牌区域提取
Mat xdst, ydst, dst_sobel, grayImage;
//img = imread("aa.jpg");
img = imread("plate.png");
imshow("plate", img);
cvtColor(img, grayImage, CV_BGR2GRAY);
//imshow("灰度图", grayImage);
Sobel(grayImage, xdst, -1, 1, 0);
//imshow("xdst", xdst);
Sobel(grayImage, ydst, -1, 0, 1);
//imshow("ydst", ydst);
addWeighted(xdst, 0.5, ydst, 0.5, 1, dst_sobel);
imshow("sobel", dst_sobel);


Mat carImage[3];
//彩色图像的灰度值归一化
img.convertTo(bgr, CV_32FC3, 1.0 / 255, 0);
cvtColor(bgr, hsv, COLOR_BGR2HSV);
split(hsv, carImage);
imshow("HSV", hsv);

//imshow("car_h", carImage[0]);
//imshow("car_s", carImage[1]);
//imshow("car_v", carImage[2]);

//定义输出图像的显示窗口
//namedWindow("inrange", WINDOW_GUI_EXPANDED);
range = Bi_inrange(0, 0);
imshow("inrange", range);
rects = range.clone();

cvtColor(range, range, CV_BGR2GRAY);
//threshold(range, range, 100, 255, CV_THRESH_BINARY); //二值化
//imshow("range", range);

vector<vector<Point>> contours;
vector<Vec4i> hierarcy;
findContours(range, contours, hierarcy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);
vector<Rect> boundRect(contours.size()); //定义外接矩形集合
vector<RotatedRect> box(contours.size()); //定义最小外接矩形集合
Point2f rect[4];
Rect m_select;
for (int i = 0; i < contours.size(); i++)
{
box[i] = minAreaRect(Mat(contours[i])); //计算每个轮廓最小外接矩形
boundRect[i] = boundingRect(Mat(contours[i]));
/*cout << box[i].angle << endl;
cout << box[i].center << endl;
cout << box[i].size.width << endl;
cout << box[i].size.height << endl;*/
if (i == 2) {
cout << boundRect[2].x << endl;
cout << boundRect[2].y << endl;
cout << boundRect[2].width << endl;
cout << boundRect[2].height << endl;
m_select = Rect(boundRect[2].x, boundRect[2].y, boundRect[2].width, boundRect[2].height);
//imshow("aa", img);
Mat ROI = img(m_select);
imwrite("result.png", ROI);
//imwrite("a.png", ROI);
}
circle(rects, Point(box[i].center.x, box[i].center.y), 5, Scalar(0, 255, 0), -1, 8); //绘制最小外接矩形的中心点
box[i].points(rect); //把最小外接矩形四个端点复制给rect数组

rectangle(rects, Point(boundRect[i].x, boundRect[i].y), Point(boundRect[i].x + boundRect[i].width, boundRect[i].y + boundRect[i].height), Scalar(0, 255, 0), 2, 8);
for (int j = 0; j < 4; j++)
{
line(rects, rect[j], rect[(j + 1) % 4], Scalar(0, 0, 255), 2, 8); //绘制最小外接矩形每条边
}
}
//Mat ROI = img(m_select);
imshow("rects", rects);
//imwrite("result", ROI);
/*
Mat candy = getContours(0, 0);
imshow("candy", candy);
*/

Mat gf = imread("gf3.jpg", CV_LOAD_IMAGE_GRAYSCALE);//采用灰度格式读取图片
namedWindow("gf3_img");
imshow("gf3_img", gf);

Mat img_pseudocolor(gf.rows, gf.cols, CV_8UC3);//构造RGB图像,参数CV_8UC3教程文档里面有讲解
int tmp = 0;
for (int y = 0; y < gf.rows; y++)//转为伪彩色图像的具体算法
{
for (int x = 0; x < gf.cols; x++)
{
tmp = gf.at<unsigned char>(y, x);
img_pseudocolor.at<Vec3b>(y, x)[0] = abs(255 - tmp); //blue
img_pseudocolor.at<Vec3b>(y, x)[1] = abs(127 - tmp); //green
img_pseudocolor.at<Vec3b>(y, x)[2] = abs(0 - tmp); //red
}
}
namedWindow("img_pseudocolor");
imshow("img_pseudocolor", img_pseudocolor);
imwrite("gf3_pseudocolor.png", img_pseudocolor);

Mat img_color(gf.rows, gf.cols, CV_8UC3);//构造RGB图像
#define IMG_B(gf,y,x) gf.at<Vec3b>(y,x)[0]
#define IMG_G(gf,y,x) gf.at<Vec3b>(y,x)[1]
#define IMG_R(gf,y,x) gf.at<Vec3b>(y,x)[2]
uchar tmp2 = 0;
for (int y = 0; y < gf.rows; y++)//转为彩虹图的具体算法,主要思路是把灰度图对应的0~255的数值分别转换成彩虹色:红、橙、黄、绿、青、蓝。
{
for (int x = 0; x < gf.cols; x++)
{
tmp2 = gf.at<uchar>(y, x);
if (tmp2 <= 51)
{
IMG_B(img_color, y, x) = 255;
IMG_G(img_color, y, x) = tmp2 * 5;
IMG_R(img_color, y, x) = 0;
}
else if (tmp2 <= 102)
{
tmp2 -= 51;
IMG_B(img_color, y, x) = 255 - tmp2 * 5;
IMG_G(img_color, y, x) = 255;
IMG_R(img_color, y, x) = 0;
}
else if (tmp2 <= 153)
{
tmp2 -= 102;
IMG_B(img_color, y, x) = 0;
IMG_G(img_color, y, x) = 255;
IMG_R(img_color, y, x) = tmp2 * 5;
}
else if (tmp2 <= 204)
{
tmp2 -= 153;
IMG_B(img_color, y, x) = 0;
IMG_G(img_color, y, x) = 255 - uchar(128.0 * tmp2 / 51.0 + 0.5);
IMG_R(img_color, y, x) = 255;
}
else
{
tmp2 -= 204;
IMG_B(img_color, y, x) = 0;
IMG_G(img_color, y, x) = 127 - uchar(127.0 * tmp2 / 51.0 + 0.5);
IMG_R(img_color, y, x) = 255;
}
}
}
namedWindow("img_ rainbowcolor");
imshow("img_ rainbowcolor", img_color);
imwrite("gf3_rainbowcolor.png", img_color);

waitKey(0);
return 0;
}

本文使用 CC BY-NC-SA 3.0 中国大陆 协议许可
具体请参见 知识共享协议

本文链接:https://zyhang8.github.io/2019/10/15/cv-exp3/