2016-12-19 16:29:59 hujiameihuxu 阅读数 990
  • OpenCV3.2 Java图像处理视频学习教程

    OpenCV3.2 Java图像处理视频培训课程:基于OpenCV新版本3.2.0详细讲述Java OpenCV图像处理部分内容,包括Mat对象使用、图像读写、 基于常用核心API讲述基本原理、使用方法、参数、代码演示、图像处理思路与流程讲授。主要内容包括opencv像素操作、滤波、边缘提取、直线与圆检测、形态学操作与分水岭、图像金子塔融合重建、多尺度模板匹配、opencv人脸检测、OpenCV跟Tomcat使用实现服务器端图像处理服务。

    4259 人正在学习 去看看 贾志刚

1 %% 第9章 形态学处理 2 3 %% imdilate膨胀 4 clc 5 clear 6 7 A1=imread('.\images\dipum_images_ch09\Fig0906(a)(broken-text).tif'); 8 info=imfinfo('.\images\dipum_images_ch09\Fig0906(a)(broken-text).tif') 9 B=[0 1 0 10 1 1 1 11 0 1 0]; 12 A2=imdilate(A1,B);%图像A1被结构元素B膨胀 13 A3=imdilate(A2,B); 14 A4=imdilate(A3,B); 15 16 subplot(221),imshow(A1); 17 title('imdilate膨胀原始图像'); 18 19 subplot(222),imshow(A2); 20 title('使用B后1次膨胀后的图像'); 21 22 subplot(223),imshow(A3); 23 title('使用B后2次膨胀后的图像'); 24 25 subplot(224),imshow(A4); 26 title('使用B后3次膨胀后的图像'); 27%imdilate图像膨胀处理过程运行结果如下:

 28 
 29 %% imerode腐蚀
 30 clc
 31 clear
 32 A1=imread('.\images\dipum_images_ch09\Fig0908(a)(wirebond-mask).tif');
 33 subplot(221),imshow(A1);
 34 title('腐蚀原始图像');
 35 
 36 %strel函数的功能是运用各种形状和大小构造结构元素
 37 se1=strel('disk',5);%这里是创建一个半径为5的平坦型圆盘结构元素
 38 A2=imerode(A1,se1);
 39 subplot(222),imshow(A2);
 40 title('使用结构原始disk(5)腐蚀后的图像');
 41 
 42 se2=strel('disk',10);
 43 A3=imerode(A1,se2);
 44 subplot(223),imshow(A3);
 45 title('使用结构原始disk(10)腐蚀后的图像');
 46 
 47 se3=strel('disk',20);
 48 A4=imerode(A1,se3);
 49 subplot(224),imshow(A4);
 50 title('使用结构原始disk(20)腐蚀后的图像');
 51 %图像腐蚀处理过程运行结果如下:
 52 
 53 %% 开运算和闭运算
 54 clc
 55 clear
 56 f=imread('.\images\dipum_images_ch09\Fig0910(a)(shapes).tif');
 57 %se=strel('square',5');%方型结构元素
 58 se=strel('disk',5');%圆盘型结构元素
 59 imshow(f);%原图像
 60 title('开闭运算原始图像')
 61%运行结果如下:

 62 
 63 %开运算数学上是先腐蚀后膨胀的结果
 64 %开运算的物理结果为完全删除了不能包含结构元素的对象区域,平滑
 65 %了对象的轮廓,断开了狭窄的连接,去掉了细小的突出部分
 66 fo=imopen(f,se);%直接开运算
 67 figure,subplot(221),imshow(fo);
 68 title('直接开运算');
 69 
 70 %闭运算在数学上是先膨胀再腐蚀的结果
 71 %闭运算的物理结果也是会平滑对象的轮廓,但是与开运算不同的是,闭运算
 72 %一般会将狭窄的缺口连接起来形成细长的弯口,并填充比结构元素小的洞
 73 fc=imclose(f,se);%直接闭运算
 74 subplot(222),imshow(fc);
 75 title('直接闭运算');
 76 
 77 foc=imclose(fo,se);%先开后闭运算
 78 subplot(223),imshow(foc);
 79 title('先开后闭运算');
 80 
 81 fco=imopen(fc,se);%先闭后开运算
 82 subplot(224),imshow(fco);
 83 title('先闭后开运算');
 84%开闭运算结果如下:

 85 
 86 %先膨胀再腐蚀
 87 fse=imdilate(f,se);%膨胀
 88 
 89 %gcf为得到当前图像的句柄,当前图像是指例如PLOT,TITLE,SURF等
 90 %get函数为得到物体的属性,get(0,'screensize')为返回所有物体screensize属性值
 91 %set函数为设置物体的属性
 92 figure,set(gcf,'outerposition',get(0,'screensize'));%具体目的是设置当前窗口的大小
 93 subplot(211),imshow(fse);
 94 title('使用disk(5)先膨胀后的图像');
 95 
 96 fes=imerode(fse,se);
 97 subplot(212),imshow(fes);
 98 title('使用disk(5)先膨胀再腐蚀后的图像');
 99%先膨胀后腐蚀图像如下:

100 
101 %先腐蚀再膨胀
102 fse=imerode(f,se);
103 figure,set(gcf,'outerposition',get(0,'screensize'))
104 subplot(211),imshow(fse);
105 title('使用disk(5)先腐蚀后的图像');
106 
107 fes=imdilate(fse,se);
108 subplot(212),imshow(fes);
109 title('使用disk(5)先腐蚀再膨胀后的图像');
110%先腐蚀后膨胀的图像如下:

111 
112 %% imopen imclose在指纹上的应用
113 clc
114 clear
115 f=imread('.\images\dipum_images_ch09\Fig0911(a)(noisy-fingerprint).tif');
116 se=strel('square',3);%边长为3的方形结构元素
117 subplot(121),imshow(f);
118 title('指纹原始图像');
119 
120 A=imerode(f,se);%腐蚀
121 subplot(122),imshow(A);
122 title('腐蚀后的指纹原始图像');
123%指纹原始图像和腐蚀后的图像结果如下:

124 
125 fo=imopen(f,se);
126 figure,subplot(221),imshow(fo);
127 title('使用square(3)开操作后的图像');
128 
129 fc=imclose(f,se);
130 subplot(222),imshow(fc);
131 title('使用square闭操作后的图像');
132 
133 foc=imclose(fo,se);
134 subplot(223),imshow(foc);
135 title('使用square(3)先开后闭操作后的图像')
136 
137 fco=imopen(fc,se);
138 subplot(224),imshow(fco);
139 title('使用square(3)先闭后开操作后的图像');
140%指纹图像开闭操作过程结果如下:

141 
142 %% bwhitmiss击中或击不中变换
143 clc
144 clear
145 f=imread('.\images\dipum_images_ch09\Fig0913(a)(small-squares).tif');
146 imshow(f);
147 title('击中或不击中原始图像');
148%击中或不击中原始图像显示结果如下:

149 
150 B1=strel([0 0 0;0 1 1;0 1 0]);%击中:要求击中所有1的位置
151 B2=strel([1 1 1;1 0 0;1 0 0]);%击不中,要求击不中所有1的位置
152 B3=strel([0 1 0;1 1 1;0 1 0]);%击中
153 B4=strel([1 0 1;0 0 0;0 0 0]);%击不中
154 B5=strel([0 0 0;0 1 0;0 0 0]);%击中
155 B6=strel([1 1 1;1 0 0;1 0 0]);%击不中
156 
157 g=imerode(f,B1)&imerode(~f,B2)%利用定义来实现击中或击不中
158 figure,subplot(221),imshow(g);
159 title('定义实现组1击中击不中图像');
160 
161 g1=bwhitmiss(f,B1,B2);
162 subplot(222),imshow(g1);
163 title('结构数组1击中击不中后的图像');
164 
165 g2=bwhitmiss(f,B3,B4);
166 subplot(223),imshow(g2);
167 title('结构数组2击中击不中的图像');
168 
169 g3=bwhitmiss(f,B5,B6);
170 subplot(224),imshow(g3);
171 title('结构数组3击中击不中的图像');
172%击中击不中变换后图像如下:

173 
174 %%makelut
175 clc
176 clear
177 
178 f=inline('sum(x(:))>=3');%inline是用来定义局部函数的
179 lut2=makelut(f,2)%为函数f构造一个接收2*2矩阵的查找表
180 lut3=makelut(f,3)
181 
182 %% Conway生命游戏
183 clc
184 clear
185 lut=makelut(@conwaylaws,3);
186 bw1=  [0     0     0     0     0     0     0     0     0     0
187        0     0     0     0     0     0     0     0     0     0
188        0     0     0     1     0     0     1     0     0     0
189        0     0     0     1     1     1     1     0     0     0
190        0     0     1     0     0     0     0     1     0     0
191        0     0     1     0     1     1     0     1     0     0
192        0     0     1     0     0     0     0     1     0     0
193        0     0     0     1     1     1     1     0     0     0
194        0     0     0     0     0     0     0     0     0     0
195        0     0     0     0     0     0     0     0     0     0  ];
196 subplot(221),imshow(bw1,'InitialMagnification','fit');
197 title('Generation 1');
198 
199 bw2=applylut(bw1,lut);
200 subplot(222),imshow(bw2,'InitialMagnification','fit'),
201 title('Generation 2');
202 
203 bw3=applylut(bw2,lut);
204 subplot(223),imshow(bw3,'InitialMagnification','fit');
205 title('Generation 3');
206 
207 temp=bw1;
208 for i=2:100
209     bw100=applylut(temp,lut);
210     temp=bw100;
211 end
212 subplot(224),imshow(bw100,'InitialMagnification','fit')
213 title('Generation 100');
214%显示Generation结果如下:

215 
216 %% getsequence
217 clc
218 clear
219 se=strel('diamond',5)
220 decomp=getsequence(se)%getsequence函数为得到分解的strel序列
221 decomp(1)
222 decomp(2)
223 
224 %% endpoints
225 clc
226 clear
227 
228 f1=imread('.\images\dipum_images_ch09\Fig0914(a)(bone-skel).tif');
229 subplot(121),imshow(f1);
230 title('原始形态骨架图像');
231 
232 g1=endpoints(f1);
233 %set(gcf,'outerposition',get(0,'screensize'));%运行完后自动生成最大的窗口
234 subplot(122),imshow(g1);
235 title('骨架图像的端点图像');
236 %骨架头像端点检测头像如下:
237 
238 f2=imread('.\images\dipum_images_ch09\Fig0916(a)(bone).tif');
239 figure,subplot(121),imshow(f2);
240 title('原始骨头图像');
241 
242 g2=endpoints(f2);
243 subplot(122),imshow(g2);
244 title('骨头图像端点头像');%结果是没有端点
245%骨头头像端点检测图像如下:

246 
247 %% bwmorph组合常见形态学之细化
248 clc
249 clear
250 f=imread('.\images\dipum_images_ch09\Fig0911(a)(noisy-fingerprint).tif');
251 subplot(221),imshow(f);
252 title('指纹图像细化原图');
253 
254 g1=bwmorph(f,'thin',1);
255 subplot(222),imshow(g1);
256 title('指纹图像细化原图');
257 
258 g2=bwmorph(f,'thin',2);
259 subplot(223),imshow(g2);
260 title('指纹图像细化原图');
261 
262 g3=bwmorph(f,'thin',Inf);
263 subplot(224),imshow(g3);
264 title('指纹图像细化原图');
265%指纹图像细化过程显示如下:

266 
267 %% bwmorph组合常见形态学之骨骼化
268 clc
269 clear
270 f=imread('.\images\dipum_images_ch09\Fig0911(a)(noisy-fingerprint).tif');
271 subplot(131),imshow(f);
272 title('指纹图像骨骼化原图');
273 
274 fs=bwmorph(f,'skel',Inf);
275 subplot(132),imshow(fs);
276 title('指纹图像骨骼化');
277 
278 for k=1:5
279     fs=fs&~endpoints(fs);
280 end
281 subplot(133),imshow(fs);
282 title('指纹图像修剪后骨骼话');
283%指纹图像骨骼化过程显示:

284 
285 %% 使用函数bwlabel标注连通分量
286 clc
287 clear
288 f=imread('.\images\dipum_images_ch09\Fig0917(a)(ten-objects).tif');
289 imshow(f),title('标注连通分量原始图像');
290%其结果显示如下:

291 
292 [L,n]=bwlabel(f);%L为标记矩阵,n为找到连接分量的总数
293 [r,c]=find(L==3);%返回第3个对象所有像素的行索引和列索引
294 
295 rbar=mean(r);
296 cbar=mean(c);
297 
298 figure,imshow(f)
299 hold on%保持当前图像使其不被刷新
300 for k=1:n
301     [r,c]=find(L==k);
302     rbar=mean(r);
303     cbar=mean(c);
304     plot(cbar,rbar,'Marker','o','MarkerEdgeColor','k',...
305          'MarkerFaceColor','k','MarkerSize',10);%这个plot函数用法不是很熟悉
306     plot(cbar,rbar,'Marker','*','MarkerFaceColor','w');%其中的marker为标记
307 end
308 title('标记所有对象质心后的图像');

309 
310 %% 由重构做开运算
311 clc
312 clear
313 f=imread('.\images\dipum_images_ch09\Fig0922(a)(book-text).tif');
314 subplot(321),imshow(f);
315 title('重构原始图像');
316 
317 fe=imerode(f,ones(51,1));%竖线腐蚀
318 subplot(322),imshow(fe);
319 title('使用竖线腐蚀后的结果');
320 
321 fo=imopen(f,ones(51,1));%竖线做开运算
322 subplot(323),imshow(fo);
323 title('使用竖线做开运算结果');
324 
325 fobr=imreconstruct(fe,f);%fe做标记
326 subplot(324),imshow(fobr);
327 title('使用竖线做重构开运算');
328 
329 ff=imfill(f,'holes');%对f进行孔洞填充
330 subplot(325),imshow(ff);
331 title('对f填充孔洞后的图像');
332 
333 fc=imclearborder(f,8);%清除边界,2维8邻接
334 subplot(326),imshow(fc);
335 title('对f清除边界后的图像');
336%图像重构过程显示如下:

337 
338 %% 使用顶帽变换和底帽变换
339 clc
340 clear
341 f=imread('.\images\dipum_images_ch09\Fig0926(a)(rice).tif');
342 subplot(221),imshow(f);
343 title('顶帽底帽变换原始图像');
344 
345 se=strel('disk',10);%产生结构元素
346 %顶帽变换是指原始图像减去其开运算的图像
347 %而开运算可用于补偿不均匀的背景亮度,所以用一个大的结构元素做开运算后
348 %然后用原图像减去这个开运算,就得到了背景均衡的图像,这也叫做是图像的顶帽运算
349 f1=imtophat(f,se);%使用顶帽变换
350 subplot(222),imshow(f1);
351 title('使用顶帽变换后的图像');
352 
353 %底帽变换是原始图像减去其闭运算后的图像
354 f2=imbothat(imcomplement(f),se);%使用底帽变换,为什么原图像要求补呢?
355 %f2=imbothat(f,se);%使用底帽变换
356 subplot(223),imshow(f2);
357 title('使用底帽变换后的图像');
358 
359 %顶帽变换和底帽变换联合起来用,用于增加对比度
360 f3=imsubtract(imadd(f,imtophat(f,se)),imbothat(f,se));%里面参数好像不合理?
361 subplot(224),imshow(f3);
362 title('使用顶帽底帽联合变换后图像');
363%顶帽底帽变换过程图像如下:

364 
365 %%使用开运算和闭运算做形态学平滑
366 %由于开运算可以除去比结构元素更小的明亮细节,闭运算可以除去比结构元素更小的暗色细节
367 %所以它们经常组合起来一起进行平滑图像并去除噪声
368 clc
369 clear
370 f=imread('.\images\dipum_images_ch09\Fig0925(a)(dowels).tif');
371 subplot(221),imshow(f);
372 title('木钉图像原图');
373 
374 se=strel('disk',5);%disk其实就是一个八边形
375 fo=imopen(f,se);%经过开运算
376 subplot(222),imshow(f);
377 title('使用半径5的disk开运算后的图像');
378 
379 foc=imclose(fo,se);
380 subplot(223),imshow(foc);
381 title('先开后闭的图像');
382 
383 fasf=f;
384 for i=2:5
385     se=strel('disk',i);
386     fasf=imclose(imopen(fasf,se),se);
387 end
388 subplot(224),imshow(fasf);
389 title('使用开闭交替滤波后图像');
390%使用开运算和闭运算做形态学平滑结果如下:

391 
392 %% 颗粒分析
393 clc
394 clear
395 f=imread('.\images\dipum_images_ch09\Fig0925(a)(dowels).tif');
396 
397 sumpixels=zeros(1,36);
398 for k=0:35
399     se=strel('disk',k);
400     fo=imopen(f,se);
401     sumpixels(k+1)=sum(fo(:));
402 end
403 
404 %可以看到,连续开运算之间的表面积会减少
405 plot(0:35,sumpixels),xlabel('k'),ylabel('surface area');
406 title('表面积和结构元素半径之间的关系');
407%其运算结果如下:   

408 
409 figure,plot(-diff(sumpixels));%diff()函数为差分或者近似倒数,即相邻2个之间的差值
410 xlabel('k'),ylabel('surface area reduction');
411 title('减少的表面积和结构元素半径之间的关系');
412%其运算结果如下:

413 
414 %% 使用重构删除复杂图像的背景
415 clc
416 clear
417 f=imread('.\images\dipum_images_ch09\Fig0930(a)(calculator).tif');
418 subplot(221),imshow(f);
419 title('灰度级重构原图像');
420 
421 f_obr=imreconstruct(imerode(f,ones(1,71)),f);
422 subplot(222),imshow(f_obr);
423 title('经开运算重构图');
424 
425 f_o=imopen(f,ones(1,71));
426 subplot(223),imshow(f_o);
427 title('经开运算后图');
428 
429 f_thr=imsubtract(f,f_obr);
430 subplot(224),imshow(f_thr);
431 title('顶帽运算重构图')
432%使用重构删除复杂图像的背景1:

433 
434 f_th=imsubtract(f,f_o)
435 figure,subplot(221),imshow(f_th);
436 title('经顶帽运算图');
437 
438 g_obr=imreconstruct(imerode(f_thr,ones(1,11)),f_thr);
439 subplot(222),imshow(g_obr);
440 title('用水平线对f_thr经开运算后重构图');
441 
442 g_obrd=imdilate(g_obr,ones(1,2));
443 subplot(223),imshow(g_obrd);
444 title('使用水平线对上图进行膨胀');
445 
446 f2=imreconstruct(min(g_obrd,f_thr),f_thr);
447 subplot(224),imshow(f2);
448 title('最后的重构结果');
449%使用重构删除复杂图像的背景2:

 

    形态学这一章很有用,因为它还可以应用在图像分割中。

 

 

2016-11-21 01:17:25 cai13160674275 阅读数 414
  • OpenCV3.2 Java图像处理视频学习教程

    OpenCV3.2 Java图像处理视频培训课程:基于OpenCV新版本3.2.0详细讲述Java OpenCV图像处理部分内容,包括Mat对象使用、图像读写、 基于常用核心API讲述基本原理、使用方法、参数、代码演示、图像处理思路与流程讲授。主要内容包括opencv像素操作、滤波、边缘提取、直线与圆检测、形态学操作与分水岭、图像金子塔融合重建、多尺度模板匹配、opencv人脸检测、OpenCV跟Tomcat使用实现服务器端图像处理服务。

    4259 人正在学习 去看看 贾志刚

a very good article for higher picture Handle

http://blog.csdn.net/huangli19870217/article/details/50754743

上面这个链接讲了很多高级的形态学算法,开操作,闭操作,顶帽,梯度,作用都很详细,下面只展示简单的腐蚀和膨胀



形态学处理:图像的膨胀和腐蚀
膨胀算法使图像扩大一圈。
腐蚀算法使二值图像减小一圈。
腐蚀:删除对象边界的某些像素
膨胀:给图像中的对象边界添加像素


#include "opencv2/imgproc/imgproc.hpp"
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
using namespace std;
using namespace cv;

int main(int argc, char** argv)
{
	Mat image;
	Mat image2;
	Mat image3;
	image=imread("/home/tgd/Videos/lane_pic/TSD-Lane-00000/TSD-Lane-00000-00000.png",3);
	Mat element = getStructuringElement(MORPH_RECT, Size(5, 5));
	dilate(image, image2, element);
	erode(image,image3,element);
	imshow("image",image);
	imshow("image2",image2);
	imshow("image3",image3);
	waitKey(0);
}




2018-04-04 11:49:48 xiaoxiaowenqiang 阅读数 2849
  • OpenCV3.2 Java图像处理视频学习教程

    OpenCV3.2 Java图像处理视频培训课程:基于OpenCV新版本3.2.0详细讲述Java OpenCV图像处理部分内容,包括Mat对象使用、图像读写、 基于常用核心API讲述基本原理、使用方法、参数、代码演示、图像处理思路与流程讲授。主要内容包括opencv像素操作、滤波、边缘提取、直线与圆检测、形态学操作与分水岭、图像金子塔融合重建、多尺度模板匹配、opencv人脸检测、OpenCV跟Tomcat使用实现服务器端图像处理服务。

    4259 人正在学习 去看看 贾志刚

opencv 图像处理 形态学操作 腐蚀 膨胀 开闭运算 阈值二值化 图像卷积 图像金字塔 Sobel算子 Laplacian 算子 candy边缘检测  霍夫变换 直方图

github代码

博文末尾支持二维码赞赏哦 _

一、形态学操作 腐蚀 膨胀 开闭运算

 

 形态学操作就是基于形状的一系列图像处理操作。
  通过将 结构元素 作用于输入图像来产生输出图像。
  最基本的形态学操作有二:
  腐蚀与膨胀(Erosion 与 Dilation)。 
  他们的运用广泛:
      消除噪声
      分割(isolate)独立的图像元素,以及连接(join)相邻的元素。
      寻找图像中的明显的极大值区域或极小值区域。 连通域

 【1】膨胀Dilation
  选择核内部的最大值(值越大越亮 约白)
  此操作将图像 A 与任意形状的内核 (B),通常为正方形或圆形,进行卷积。
  内核 B 有一个可定义的 锚点, 通常定义为内核中心点。
  进行膨胀操作时,将内核 B 划过图像,将内核 B 覆盖区域的最大相素值提取,
  并代替锚点位置的相素。显然,这一最大化操作将会导致图像中的亮区开始”扩展” 
  (因此有了术语膨胀 dilation )。
  背景(白色)膨胀,而黑色字母缩小了。

 【2】腐蚀 Erosion 
  选择核内部的最小值(值越小越暗 约黑)
  腐蚀在形态学操作家族里是膨胀操作的孪生姐妹。它提取的是内核覆盖下的相素最小值。
  进行腐蚀操作时,将内核 B 划过图像,将内核 B 覆盖区域的最小相素值提取,并代替锚点位置的相素。
  以与膨胀相同的图像作为样本,我们使用腐蚀操作。
  从下面的结果图我们看到亮区(背景)变细,而黑色区域(字母)则变大了。


#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
#include <string>
using namespace std;
using namespace cv;

// 全局变量
Mat src, erosion_dst, dilation_dst;
int erosion_elem = 0;
int erosion_size = 0;
int dilation_elem = 0;
int dilation_size = 0;
int const max_elem = 2;
int const max_kernel_size = 21;

//窗口名字
string Erosion_w("Erosion 腐蚀 Demo");
string Dilation_w("Dilation 膨胀 Demo");

//函数声明
void Erosion( int, void* );
void Dilation( int, void* );

int main( int argc, char** argv )
{
  string imageName("../../common/data/77.jpeg"); // 图片文件名路径(默认值)
    if( argc > 1)
    {
        imageName = argv[1];//如果传递了文件 就更新
    }

  src = imread( imageName );
  if( src.empty() )
    { 
        cout <<  "can't load image " << endl;
	return -1;  
    }

  namedWindow( Erosion_w, WINDOW_AUTOSIZE );
  namedWindow( Dilation_w, WINDOW_AUTOSIZE );
  moveWindow( Dilation_w, src.cols, 0 );//新建一个

// 创建腐蚀 Trackbar
  createTrackbar( "Element:\n 0: Rect \n 1: Cross \n 2: Ellipse", Erosion_w,
          &erosion_elem, max_elem,// 滑动条 动态改变参数 erosion_elem 核窗口形状
          Erosion );//回调函数  Erosion 

  createTrackbar( "Kernel size:\n 2n +1", Erosion_w,
          &erosion_size, max_kernel_size,// 滑动条 动态改变参数 erosion_size 窗口大小
          Erosion );//回调函数  Erosion 


// 创建膨胀 Trackbar
  createTrackbar( "Element:\n 0: Rect \n 1: Cross \n 2: Ellipse", Dilation_w,
          &dilation_elem, max_elem,// 滑动条 动态改变参数 dilation_elem 核窗口形状
          Dilation );//回调函数 Dilation

  createTrackbar( "Kernel size:\n 2n +1", Dilation_w,
          &dilation_size, max_kernel_size,// 滑动条 动态改变参数 dilation_size 窗口大小
          Dilation );//回调函数 Dilation

// 默认 开始参数   长方形核 1核子大小
  Erosion( 0, 0 );
  Dilation( 0, 0 );
  waitKey(0);//等待按键
  return 0;
}

// 腐蚀操作 
void Erosion( int, void* )
{
  int erosion_type = 0;
  if( erosion_elem == 0 ){ erosion_type = MORPH_RECT; }// 矩形
  else if( erosion_elem == 1 ){ erosion_type = MORPH_CROSS; }// 交叉形
  else if( erosion_elem == 2) { erosion_type = MORPH_ELLIPSE; }// 椭圆形
  Mat element = getStructuringElement( erosion_type,//核形状
                       Size( 2*erosion_size + 1, 2*erosion_size+1 ),//核大小
                       Point( erosion_size, erosion_size ) );//锚点 默认锚点在内核中心位置
  erode( src, erosion_dst, element );
  imshow( Erosion_w, erosion_dst );
}


// 膨胀操作
void Dilation( int, void* )
{
  int dilation_type = 0;
  if( dilation_elem == 0 ){ dilation_type = MORPH_RECT; }// 矩形
  else if( dilation_elem == 1 ){ dilation_type = MORPH_CROSS; }// 交叉形
  else if( dilation_elem == 2) { dilation_type = MORPH_ELLIPSE; }// 椭圆形
  Mat element = getStructuringElement( dilation_type,//核形状
                       Size( 2*dilation_size + 1, 2*dilation_size+1 ),//核大小
                       Point( dilation_size, dilation_size ) );//锚点 默认锚点在内核中心位置
  dilate( src, dilation_dst, element );
  imshow( Dilation_w, dilation_dst );
}

1.2开闭运算提取线特征 水平线 垂直线

 

/*
利用形态学操作提取水平线和垂直线
腐蚀膨胀来提取线特征
*/
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;

int main(int argc, char** argv)
{
  string imageName("../../common/data/notes.png"); // 图片文件名路径(默认值)
    if( argc > 1)
    {
        imageName = argv[1];//如果传递了文件 就更新
    }

  Mat src = imread( imageName );
  if( src.empty() )
    { 
        cout <<  "can't load image " << endl;
	return -1;  
    }

    // 显示图像
    imshow("src", src);

    // 得到灰度图
    Mat gray;
    if (src.channels() == 3)//如果原图是彩色图
    {
        cvtColor(src, gray, CV_BGR2GRAY);//转换到灰度图
    }
    else
    {
        gray = src;
    }

    // 显示灰度图像
    imshow("gray", gray);

    // 灰度图二值化 ~ symbol
    Mat bw;
	       // 原图取反   输出  最大  自适应方法阈值           阈值类型  块大小
    adaptiveThreshold(~gray, bw, 255, CV_ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 15, -2);
    // THRESH_BINARY     大于阈值的都变为 255最大值 其余变为 0
    // THRESH_BINARY_INV 小于阈值的都变为 255最大值 其余变为 0 

    // 显示二值图
    imshow("binary", bw);
    // 创建水平线和垂直线图像
    Mat horizontal = bw.clone();
    Mat vertical = bw.clone();

//========水平线提取 参考列数=====================
    int horizontalsize = horizontal.cols / 30;

    // 水平线 提取框  核子 窗口大小
    Mat horizontalStructure = getStructuringElement(MORPH_RECT, Size(horizontalsize,1));

    // 腐蚀+膨胀 =  开运算 (Opening)  去除 小型 白洞 保留水平白线
    erode(horizontal, horizontal, horizontalStructure, Point(-1, -1));
    dilate(horizontal, horizontal, horizontalStructure, Point(-1, -1));
    // 显示水平线
    imshow("horizontal", horizontal);


//========垂直线提取==========================
    int verticalsize = vertical.rows / 30;//
    // 核子 窗口大小
    Mat verticalStructure = getStructuringElement(MORPH_RECT, Size( 1,verticalsize));
    // 腐蚀+膨胀 =  开运算 (Opening)
    erode(vertical, vertical, verticalStructure, Point(-1, -1));
    dilate(vertical, vertical, verticalStructure, Point(-1, -1));
    // 显示垂直线
    imshow("vertical", vertical);

// 垂直线图 反向二值化
    bitwise_not(vertical, vertical);
    imshow("vertical_bit", vertical);

// Extract edges and smooth image according to the logic
// 1. extract edges
// 2. dilate(edges)
// 3. src.copyTo(smooth)
// 4. blur smooth img
// 5. smooth.copyTo(src, edges)
    //Step 1 提取边缘 
    Mat edges;
    adaptiveThreshold(vertical, edges, 255, CV_ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 3, -2);
    imshow("edges", edges);
    // Step 2 膨胀操作
    Mat kernel = Mat::ones(2, 2, CV_8UC1);//核大小
    dilate(edges, edges, kernel);
    imshow("dilate", edges);
    // Step 3 得到平滑图像
    Mat smooth;
    vertical.copyTo(smooth);
    // Step 4 平滑图像
    blur(smooth, smooth, Size(2, 2));
    // Step 5
    smooth.copyTo(vertical, edges);
    // Show final result
    imshow("smooth", vertical);
    waitKey(0);
    return 0;
}

二、图像阈值处理 阈值二值化

 

图像阈值操作

 最简单的图像分割的方法。

 应用举例:从一副图像中利用阈值分割出我们需要的物体部分
 (当然这里的物体可以是一部分或者整体)。
这样的图像分割方法是基于图像中物体与背景之间的灰度差异,而且此分割属于像素级的分割。
为了从一副图像中提取出我们需要的部分,应该用图像中的每一个
像素点的灰度值与选取的阈值进行比较,并作出相应的判断。

 (注意:阈值的选取依赖于具体的问题。即:物体在不同的图像中有可能会有不同的灰度值。
一旦找到了需要分割的物体的像素点,我们可以对这些像素点设定一些特定的值来表示。

 (例如:可以将该物体的像素点的灰度值设定为:‘0’(黑色),

 其他的像素点的灰度值为:‘255’(白色);当然像素点的灰度值可以任意,

 但最好设定的两种颜色对比度较强,方便观察结果)。

 【1】阈值类型1:二值阈值化
     大于阈值的 设置为最大值 255  其余为0
     先要选定一个特定的阈值量,比如:125,
    这样,新的阈值产生规则可以解释为大于125的像素点的灰度值设定为最大值(如8位灰度值最大为255),
    灰度值小于125的像素点的灰度值设定为0。

 【2】阈值类型2:反二进制阈值化
    小于阈值的 设置为最大值 255  其余为0

 【3】阈值类型3:截断阈值化
    大于阈值的 设置为阈值  其余保持原来的值

 【4】阈值类型4:阈值化为0
    大于阈值的 保持原来的值 其余设置为0

 【5】阈值类型5:反阈值化为0 
    大于阈值的 设置为0  其余保持原来的值 
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
using namespace cv;
using namespace std;

// 全局变量定义及赋值
int threshold_value = 0;
int threshold_type = 3;
int const max_value = 255;
int const max_type = 4;
int const max_BINARY_value = 255;

Mat src, src_gray, dst;
const char* window_name = "Threshold Demo";//窗口名
// 滑动条显示
const char* trackbar_type = "Type: \n 0: Binary \n 1: Binary Inverted \n 2: Truncate \n 3: To Zero \n 4: To Zero Inverted";

const char* trackbar_value = "Value";

//阈值
void Threshold_Demo( int, void* );

int main( int argc, char** argv )
{
    string imageName("../../common/data/notes.png"); // 图片文件名路径(默认值)
    if( argc > 1)
    {
        imageName = argv[1];//如果传递了文件 就更新
    }

  src = imread( imageName );
  if( src.empty() )
    { 
        cout <<  "can't load image " << endl;
	return -1;  
    }
  //转成灰度图
  cvtColor( src, src_gray, COLOR_RGB2GRAY );
  //显示
  namedWindow( window_name, WINDOW_AUTOSIZE );

  // 阈值类型
  createTrackbar( trackbar_type,
                  window_name, &threshold_type,
                  max_type, Threshold_Demo );
  // 阈值大小
  createTrackbar( trackbar_value,
                  window_name, &threshold_value,
                  max_value, Threshold_Demo );
  // 初始化为
  Threshold_Demo( 0, 0 );
  //检测按键
  for(;;)
    {
      int c;
      c = waitKey( 20 );
      if( (char)c == 27 )//esc键退出
    { break; }
    }
}

void Threshold_Demo( int, void* )
{
  /* 0: Binary          二值
     1: Binary Inverted 二值反
     2: Threshold Truncated 截断
     3: Threshold to Zero   阈值化为0 大于阈值的 保持原来的值 其余设置为0
     4: Threshold to Zero Inverted  大于阈值的 设置为0  其余保持原来的值 
   */
  threshold( src_gray, dst, threshold_value, max_BINARY_value,threshold_type );
  imshow( window_name, dst );
}

三、图像卷积操作 平滑滤波 自定义滤波器算子 sobel求梯度算子

【1】卷积
高度概括地说,卷积是在每一个图像块与某个算子(核)之间进行的运算。

【2】核是什么?
核说白了就是一个固定大小的数值数组。该数组带有一个 锚点 ,一般位于数组中央。

自定义滤波器核
用OpenCV函数 filter2D 创建自己的线性滤
      kernel = Mat::ones( kernel_size, kernel_size, CV_32F )/ (float)(kernel_size*kernel_size);

#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
using namespace cv;
using namespace std;
//主函数
int main ( int argc, char** argv )
{
  /// 声明变量
  Mat src, dst;

  Mat kernel;
  Point anchor;
  double delta;
  int ddepth;
  int kernel_size;
  char* window_name = "filter2D Demo";

  int c;

    string imageName("../../common/data/notes.png"); // 图片文件名路径(默认值)
    if( argc > 1)
    {
        imageName = argv[1];//如果传递了文件 就更新
    }

  src = imread( imageName );
  if( src.empty() )
    { 
        cout <<  "can't load image " << endl;
	return -1;  
    }
  /// 创建窗口
  namedWindow( window_name, CV_WINDOW_AUTOSIZE );

  /// 初始化滤波器参数
  anchor = Point( -1, -1 );//锚点
  delta = 0;// 偏置 delta: 在卷积过程中,该值会加到每个像素上。默认情况下,这个值为 0 
  ddepth = -1;
// ddepth: dst 的深度。若为负值(如 -1 ),则表示其深度与源图像相等。

  /// 循环 - 每隔0.5秒,用一个不同的核来对图像进行滤波
  int ind = 0;
  while( true )
    {
      c = waitKey(500);
      /// 按'ESC'可退出程序
      if( (char)c == 27 )
        { break; }

      /// 更新归一化块滤波器的核大小
      kernel_size = 3 + 2*( ind%5 );//ind%5  0,1,2,3,4
      // 核的大小 设置为 [3,11] 范围内的奇数
      // 第二行代码把1填充进矩阵,并执行归一化——除以矩阵元素数——以构造出所用的核。
      kernel = Mat::ones( kernel_size, kernel_size, CV_32F )/ (float)(kernel_size*kernel_size);

      /// 使用滤波器
// ddepth: dst 的深度。若为负值(如 -1 ),则表示其深度与源图像相等。
// delta: 在卷积过程中,该值会加到每个像素上。默认情况下,这个值为 0 
      filter2D(src, dst, ddepth , kernel, anchor, delta, BORDER_DEFAULT );
      imshow( window_name, dst );
      ind++;//核子尺寸参数
    }

  return 0;
}

 

3.2 Sobel 算子x方向、y方向导数 合成导数

 

一个最重要的卷积运算就是导数的计算(或者近似计算).

为什么对图像进行求导是重要的呢? 假设我们需要检测图像中的 边缘 球图像梯度大的地方

【1】Sobel算子
    Sobel 算子是一个离散微分算子 (discrete differentiation operator)。
          它用来计算图像灰度函数的近似梯度。
    Sobel 算子结合了高斯平滑和微分求导。

假设被作用图像为 I:
    在两个方向求导:
        水平变化: 将 I 与一个奇数大小的内核 G_{x} 进行卷积。比如,当内核大小为3时, G_{x} 的计算结果为:
        G_{x} = [-1 0 +1
                     -2 0 +2
                     -1 0 +1]
        垂直变化: 将:m I 与一个奇数大小的内核 G_{y} 进行卷积。
                  比如,当内核大小为3时, G_{y} 的计算结果为:
        G_{y} = [-1 -2 -1
                    0  0  0
                   +1 +2 +1]
       在图像的每一点,结合以上两个结果求出近似 梯度:
             G = sqrt(GX^2 + GY^2)


Sobel内核

当内核大小为 3 时, 以上Sobel内核可能产生比较明显的误差(毕竟,Sobel算子只是求取了导数的近似值)。
 为解决这一问题,OpenCV提供了 Scharr 函数,但该函数仅作用于大小为3的内核。
该函数的运算与Sobel函数一样快,但结果却更加精确,其内核为:

        G_{x} = [-3  0 +3
                   -10 0 +10
                    -3  0 +3]
        G_{y} = [-3 -10 -3
                     0  0  0

                    +3 +10 +3] 

#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
using namespace std;
using namespace cv;

/** @function main */
int main( int argc, char** argv )
{

  Mat src, src_gray;
  Mat grad;
  char* window_name = "Sobel Demo - Simple Edge Detector";
  int scale = 1;// 计算导数 放大因子 scale ?
  int delta = 0;// 偏置 delta: 在卷积过程中,该值会加到每个像素上。默认情况下,这个值为 0 
  int ddepth = CV_16S;// ddepth: 输出图像的深度,设定为 CV_16S 避免外溢。

  int c;

 string imageName("../../common/data/77.jpeg"); // 图片文件名路径(默认值)
    if( argc > 1)
    {
        imageName = argv[1];//如果传递了文件 就更新
    }

  src = imread( imageName );
  if( src.empty() )
    { 
        cout <<  "can't load image " << endl;
	return -1;  
    }
  // 高斯平滑  降噪 ( 内核大小 = 3 )
  GaussianBlur( src, src, Size(3,3), 0, 0, BORDER_DEFAULT );

  // 将降噪后的图像转换为灰度图:
  cvtColor( src, src_gray, CV_RGB2GRAY );

  /// 创建显示窗口
  namedWindow( window_name, CV_WINDOW_AUTOSIZE );

  /// 创建 水平和垂直梯度图像 grad_x 和 grad_y 
  Mat grad_x, grad_y;
  Mat abs_grad_x, abs_grad_y;

  /// 求 X方向梯度
  //Scharr( src_gray, grad_x, ddepth, 1, 0, scale, delta, BORDER_DEFAULT );
  Sobel( src_gray, grad_x, ddepth, 1, 0, 3, scale, delta, BORDER_DEFAULT );
  convertScaleAbs( grad_x, abs_grad_x );
// ddepth: 输出图像的深度,设定为 CV_16S 避免外溢。
// 偏置 delta: 在卷积过程中,该值会加到每个像素上。默认情况下,这个值为 0 
// 计算导数 放大因子 scale ?

  /// 求Y方向梯度
  //Scharr( src_gray, grad_y, ddepth, 0, 1, scale, delta, BORDER_DEFAULT );
  Sobel( src_gray, grad_y, ddepth, 0, 1, 3, scale, delta, BORDER_DEFAULT );
  convertScaleAbs( grad_y, abs_grad_y );
 
  /// 合并梯度(近似)
  addWeighted( abs_grad_x, 0.5, abs_grad_y, 0.5, 0, grad );

  imshow( window_name, grad );

  waitKey(0);

  return 0;
  }


四、图像金字塔 下采样 + 高斯核卷积

图像金字塔
使用OpenCV函数 pyrUp 和 pyrDown 对图像进行向上和向下采样。
然后高斯平滑

当我们需要将图像转换到另一个尺寸的时候, 有两种可能:

    放大 图像 或者

    缩小 图像。

 

我们首先学习一下使用 图像金字塔 来做图像缩放, 图像金字塔是视觉运用中广泛采用的一项技术。

图像金字塔:
    一个图像金字塔是一系列图像的集合 -
所有图像来源于同一张原始图像 - 通过梯次向下采样获得,直到达到某个终止条件才停止采样。

有两种类型的图像金字塔常常出现在文献和应用中:
   【1】 高斯金字塔(Gaussian pyramid):        用来向下采样
   【2】 拉普拉斯金字塔(Laplacian pyramid): 用来从金字塔低层图像重建上层未采样图像
在这篇文档中我们将使用 高斯金字塔 。

高斯金字塔:想想金字塔为一层一层的图像,层级越高,图像越小。

高斯内核:
1/16 [1  4  6  4  1
        4  16 24 16 4
        6  24 36 24 6
        4  16 24 16 4
        1  4  6  4  1]

下采样:
    将 图像 与高斯内核做卷积
    将所有偶数行和列去除。
    显而易见,结果图像只有原图的四分之一。

如果将图像变大呢?:
    首先,将图像在每个方向扩大为原来的两倍,新增的行和列以0填充(0)
    使用先前同样的内核(乘以4)与放大后的图像卷积,获得 “新增像素” 的近似值。

这两个步骤(向下和向上采样) 分别通过OpenCV函数 pyrUp 和 pyrDown 实现,
 

#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <math.h>
#include <stdlib.h>
#include <stdio.h>

using namespace cv;

/// 全局变量
Mat src, dst, tmp;
char* window_name = "Pyramids Demo";


// 主函数
int main( int argc, char** argv )
{
  /// 指示说明
  printf( "\n Zoom In-Out demo  \n " );
  printf( "------------------ \n" );
  printf( " * [u] -> Zoom in  \n" );
  printf( " * [d] -> Zoom out \n" );
  printf( " * [ESC] -> Close program \n \n" );

  /// 测试图像 - 尺寸必须能被 2^{n} 整除
  src = imread("../../common/data/chicky_512.png");
  if( !src.data )
    { printf(" No data! -- Exiting the program \n");
      return -1; }

  tmp = src;
  dst = tmp;

  /// 创建显示窗口
  namedWindow( window_name, CV_WINDOW_AUTOSIZE );
  imshow( window_name, dst );

  /// 循环 检测 按键响应
  while( true )
  {
    int c;
    c = waitKey(10);//获得按键

    if( (char)c == 27 )//esc
      { break; }
    if( (char)c == 'u' )//上采样
      { pyrUp( tmp, dst, Size( tmp.cols*2, tmp.rows*2 ) );
        printf( "** Zoom In: Image x 2 \n" );
      }
    else if( (char)c == 'd' )//下采样
     { pyrDown( tmp, dst, Size( tmp.cols/2, tmp.rows/2 ) );
       printf( "** Zoom Out: Image / 2 \n" );
     }

    imshow( window_name, dst );
    tmp = dst;
  }
  return 0;
}

 

五、Laplacian 算子  图像二阶导数 梯度的梯度 0值的话 边缘概率较大

 

 

  Laplacian 算子 的离散模拟。 图像二阶倒数  梯度的梯度 0值的话 边缘概率较大
  Sobel 算子 ,其基础来自于一个事实,即在边缘部分,像素值出现”跳跃“或者较大的变化。

 如果在此边缘部分求取一阶导数,你会看到极值的出现。

 你会发现在一阶导数的极值位置,二阶导数为0。

 所以我们也可以用这个特点来作为检测图像边缘的方法。

 但是, 二阶导数的0值不仅仅出现在边缘(它们也可能出现在无意义的位置),

 但是我们可以过滤掉这些点。
  Laplacian 算子
      从以上分析中,我们推论二阶导数可以用来 检测边缘 。 
      因为图像是 “2维”, 我们需要在两个方向求导。使用Laplacian算子将会使求导过程变得简单。

      Laplacian 算子 的定义:
      Laplace(f) = df^2/ dx^2 + df^2 / dy^2

      OpenCV函数 Laplacian 实现了Laplacian算子。 
      实际上,由于 Laplacian使用了图像梯度,它内部调用了 Sobel 算子。

 

#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <stdlib.h>
#include <stdio.h>
#include <iostream>

using namespace std;
using namespace cv;
/** @函数 main */
int main( int argc, char** argv )
{
  Mat src, src_gray, dst;
  int kernel_size = 3;
  int scale = 1;
  int delta = 0;
  int ddepth = CV_16S;
  char* window_name = "Laplace Demo";

  int c;

  string imageName("../../common/data/77.jpeg"); // 图片文件名路径(默认值)
    if( argc > 1)
    {
        imageName = argv[1];//如果传递了文件 就更新
    }

  src = imread( imageName );
  if( src.empty() )
    { 
        cout <<  "can't load image " << endl;
	return -1;  
    }


  /// 使用高斯滤波消除噪声
  GaussianBlur( src, src, Size(3,3), 0, 0, BORDER_DEFAULT );

  /// 转换为灰度图
  cvtColor( src, src_gray, CV_RGB2GRAY );

  /// 创建显示窗口
  namedWindow( window_name, CV_WINDOW_AUTOSIZE );

  /// 使用Laplace函数
  Mat abs_dst;

  Laplacian( src_gray, dst, ddepth, kernel_size, scale, delta, BORDER_DEFAULT );
  convertScaleAbs( dst, abs_dst );
// ddepth: 输出图像的深度,设定为 CV_16S 避免外溢。
// kernel_size 卷积核大小
// 偏置 delta: 在卷积过程中,该值会加到每个像素上。默认情况下,这个值为 0 
// 计算导数 放大因子 scale ?
// 边界填充 BORDER_DEFAULT 默认使用 复制填充

  /// 显示结果
  imshow( window_name, abs_dst );

  waitKey(0);

  return 0;
}

六、candy边缘检测 消噪 计算梯度幅值和方向 非极大值抑制 滞后阈值

 

Canny 边缘检测 边缘检测最优算法
综合使用 高斯平滑 soble梯度检测 非极大值抑制 滞后阈值 等操作来检测物体边缘

Canny 边缘检测算法 是 John F. Canny 于 1986年开发出来的一个多级边缘检测算法,
也被很多人认为是边缘检测的 最优算法, 最优边缘检测的三个主要评价标准是:

        低错误率: 标识出尽可能多的实际边缘,同时尽可能的减少噪声产生的误报。
        高定位性: 标识出的边缘要与图像中的实际边缘尽可能接近。
        最小响应: 图像中的边缘只能标识一次。

步骤::
 【1】消除噪声。 使用高斯平滑滤波器卷积降噪。 下面显示了一个 size = 5 的高斯内核示例:
      K = 1/159 [2  4  5  4  2
                      4  9  12 9  4
                      5 12  15 12 5
                      4  9  12 9  4
                      2  4  5  4  2]
 【2】计算梯度幅值和方向。
       此处,按照Sobel滤波器的步骤:
       在两个方向求导:
       水平变化: 将 I 与一个奇数大小的内核 G_{x} 进行卷积。比如,当内核大小为3*3时, G_{x} 的计算结果为:
        G_{x} = [-1 0 +1
                    -2 0 +2
                    -1 0 +1]
        垂直变化: 将:  I 与一个奇数大小的内核 G_{y} 进行卷积。
                  比如,当内核大小为3×3时, G_{y} 的计算结果为:
        G_{y} = [-1 -2 -1
                     0  0  0
                    +1 +2 +1]
       在图像的每一点,结合以上两个结果求出近似 梯度:
              G = sqrt(GX^2 + GY^2)
              梯度角度方向 = arctan(GY/GX)

              梯度方向近似到四个可能角度之一(一般 0, 45, 90, 135)

  【3】梯度大小非极大值 抑制。 这一步排除非边缘像素, 仅仅保留了一些细线条(候选边缘)。

  【4】滞后阈值: 最后一步,Canny 使用了滞后阈值,滞后阈值需要两个阈值(高阈值 和 低阈值):

    如果某一像素位置的幅值超过 高 阈值, 该像素被保留为边缘像素。
    如果某一像素位置的幅值小于 低 阈值, 该像素被排除。
    如果某一像素位置的幅值在两个阈值之间,该像素在连接到一个高于 高阈值的像素时被保留。

Canny 推荐的 高:低 阈值比在 2:1 到3:1之间。

#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <stdlib.h>
#include <stdio.h>

#include <iostream>
using namespace std;
using namespace cv;

/// 全局变量

Mat src, src_gray;
Mat dst, detected_edges;

int edgeThresh = 1;
int lowThreshold;
int const max_lowThreshold = 100;//最大 低阈值
int ratio = 3;// 高低阈值 比值
int kernel_size = 3;//candy 核尺寸
char* window_name = "Edge Map";

// 回调函数 CannyThreshold
//@简介: trackbar 交互回调 - Canny阈值输入比例1:3

void CannyThreshold(int, void*)
{
  /// 使用 3x3内核 均值滤波 降噪
  blur( src_gray, detected_edges, Size(3,3) );

  /// 运行Canny算子
  Canny( detected_edges, detected_edges, lowThreshold, lowThreshold*ratio, kernel_size );

  /// 使用 Canny算子输出边缘作为掩码显示原图像
  dst = Scalar::all(0);

  src.copyTo( dst, detected_edges);
  imshow( window_name, dst );
 }


/** @函数 main */
int main( int argc, char** argv )
{

 string imageName("../../common/data/77.jpeg"); // 图片文件名路径(默认值)
    if( argc > 1)
    {
        imageName = argv[1];//如果传递了文件 就更新
    }

  src = imread( imageName );
  if( src.empty() )
    { 
        cout <<  "can't load image " << endl;
	return -1;  
    }
  /// 创建与src同类型和大小的矩阵(dst)
  dst.create( src.size(), src.type() );

  /// 原图像转换为灰度图像
  cvtColor( src, src_gray, CV_BGR2GRAY );

  /// 创建显示窗口
  namedWindow( window_name, CV_WINDOW_AUTOSIZE );

  /// 创建trackbar  滑动条 调节 低阈值参数   回调函数 CannyThreshold 
  createTrackbar( "Min Threshold:", window_name, &lowThreshold, max_lowThreshold, CannyThreshold );

  /// 显示图像
  CannyThreshold(0, 0);

  /// 等待用户反应
  waitKey(0);//按键后结束

  return 0;
  }

 

七、霍夫线变换 霍夫圆变换 先candy边缘检测 再找圆 极坐标再找直线

 

霍夫线变换  检测图像中的直线 先candy边缘检测 在找直线
使用OpenCV的以下函数 HoughLines 和 HoughLinesP 来检测图像中的直线.

霍夫线变换
    霍夫线变换是一种用来寻找直线的方法.
    是用霍夫线变换之前, 首先要对图像进行边缘检测的处理,
    也即霍夫线变换的直接输入只能是边缘二值图像.

众所周知, 一条直线在图像二维空间可由两个变量表示. 例如:

    在 笛卡尔坐标系: 可由参数: (m,b) 斜率和截距表示.  y = m*x + b
    在 极坐标系: 可由参数: (r,\theta) 极径和极角表示  r = x * cos(theta) + y*sin(theta)

   一般来说, 一条直线能够通过在平面 theta - r 寻找交于一点的曲线数量来 检测.
   越多曲线交于一点也就意味着这个交点表示的直线由更多的点组成.
   一般来说我们可以通过设置直线上点的 阈值 来定义多少条曲线交于一点我们才认为 检测 到了一条直线.

   这就是霍夫线变换要做的. 它追踪图像中每个点对应曲线间的交点.
   如果交于一点的曲线的数量超过了 阈值,
   那么可以认为这个交点所代表的参数对 (theta, r_{theta}) 在原图像中为一条直线.

标准霍夫线变换和统计概率霍夫线变换
OpenCV实现了以下两种霍夫线变换:
    【1】标准霍夫线变换
    它能给我们提供一组参数对 (\theta, r_{\theta}) 的集合来表示检测到的直线
        在OpenCV 中通过函数 HoughLines 来实现

    【2】统计概率霍夫线变换

        这是执行起来效率更高的霍夫线变换.
        它输出检测到的直线的端点 (x_{0}, y_{0}, x_{1}, y_{1})

        在OpenCV 中它通过函数 HoughLinesP 来实现

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"

#include <iostream>

using namespace cv;
using namespace std;

void help()
{
 cout << "\nThis program demonstrates line finding with the Hough transform.\n"
         "Usage:\n"
         "./houghlines <image_name>, Default is pic1.jpg\n" << endl;
}

int main(int argc, char** argv)
{
 const char* filename = argc >= 2 ? argv[1] : "../../common/data/77.jpeg";

 Mat src = imread(filename, 0);
 if(src.empty())
 {
     help();
     cout << "can not open " << filename << endl;
     return -1;
 }

 Mat dst, cdst;
// 检测边缘
 Canny(src, dst, 50, 200, 3);//低阈值 高阈值 核尺寸

 cvtColor(dst, cdst, CV_GRAY2BGR);//灰度图

//【1】标准霍夫线变换
 #if 0
  vector<Vec2f> lines;//得到 直线的参数 r theta
  HoughLines(dst, lines, 1, CV_PI/180, 100, 0, 0 );

  for( size_t i = 0; i < lines.size(); i++ )
  {
     float rho = lines[i][0], theta = lines[i][1];
     Point pt1, pt2;
     double a = cos(theta), b = sin(theta);
     double x0 = a*rho, y0 = b*rho;
     pt1.x = cvRound(x0 + 1000*(-b));
     pt1.y = cvRound(y0 + 1000*(a));
     pt2.x = cvRound(x0 - 1000*(-b));
     pt2.y = cvRound(y0 - 1000*(a));
     line( cdst, pt1, pt2, Scalar(0,0,255), 3, CV_AA);
  }
 #else
// 【2】统计概率霍夫线变换
  vector<Vec4i> lines;//直线首尾点
  HoughLinesP(dst, lines, 1, CV_PI/180, 100, 50, 10 );
// 以像素值为单位的分辨率. 我们使用 1 像素.
// theta: 参数极角  theta 以弧度为单位的分辨率. 我们使用 1度 (即CV_PI/180)
// threshold: 要”检测” 一条直线所需最少的的曲线交点 50
// minLineLength = 0,  最小线长
// maxLineGap = 0 , 最大线间隔  maxLineGap: 能被认为在一条直线上的亮点的最大距离.
  for( size_t i = 0; i < lines.size(); i++ )
  {
    Vec4i l = lines[i];
    line( cdst, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(0,0,255), 3, CV_AA);
  }
 #endif

 imshow("source", src);
 imshow("detected lines", cdst);

 waitKey();

 return 0;
}

/*
霍夫 圆变换   在图像中检测圆. 
*/


#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"

#include <iostream>

using namespace cv;
using namespace std;

void help()
{
 cout << "\nThis program demonstrates line finding with the Hough transform.\n"
         "Usage:\n"
         "./houghlines <image_name>, Default is pic1.jpg\n" << endl;
}

int main(int argc, char** argv)
{
 string filename = argc >= 2 ? argv[1] : "../../common/data/apple.jpeg";

 Mat src = imread(filename, IMREAD_COLOR); // 按圆图片颜色 读取
 if(src.empty())
 {
     help();
     cout << "can not open " << filename << endl;
     return -1;
 }


    // 得到灰度图
    Mat src_gray = src.clone();
    if (src.channels() == 3)//如果原图是彩色图
    {
        cvtColor(src, src_gray, CV_BGR2GRAY);//转换到灰度图
    }
    //else
    //{
    //    src_gray = src.clone();
    //}

  // 高斯平滑降噪
  GaussianBlur( src_gray, src_gray, Size(9, 9), 2, 2 );

// 执行霍夫圆变换
  vector<Vec3f> circles;//中性点(x,y)半价 r三个参数
  HoughCircles( src_gray, circles, CV_HOUGH_GRADIENT, 1, src_gray.rows/10, 80, 50, 0, 0 );
// CV_HOUGH_GRADIENT: 指定检测方法. 现在OpenCV中只有霍夫梯度法
// dp = 1: 累加器图像的反比分辨率
// min_dist = src_gray.rows/8: 检测到圆心之间的最小距离
// param_1 = 200: Canny边缘函数的高阈值
// param_2 = 100: 圆心检测阈值.
// min_radius = 0: 能检测到的最小圆半径, 默认为0.
// max_radius = 0: 能检测到的最大圆半径, 默认为0

  for( size_t i = 0; i < circles.size(); i++ )
  {
    Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));//圆中心点
    int radius = cvRound(circles[i][2]);//半径 像素值单位
    // 画圆中心点 circle center
    circle( src, center, 3, Scalar(0,255,255), -1, 8);// green 绿色  粗细 线形
    // 画圆外圈   circle outline
    circle( src, center, radius, Scalar(0,0,255), 3, 8);// red 红色 bgr
   }

  
 namedWindow( "Hough Circle Transform Demo", CV_WINDOW_AUTOSIZE );
 imshow("Hough Circle Transform Demo", src);
 //imshow("detected circles", cdst);

 waitKey(0);// 等待用户按键结束程序

 return 0;
}

 

八、图像像素重映射 remapping 随机矩阵空间映射 仿射变换

 

重映射是什么意思?
    把一个图像中一个位置的像素放置到另一个图片指定位置的过程.
    为了完成映射过程, 有必要获得一些插值为非整数像素坐标,
    因为源图像与目标图像的像素坐标不是一一对应的.

    我们通过重映射来表达每个像素的位置 (x,y) :

   goal(x,y) = f(s(s,y))

  
 #include "opencv2/highgui/highgui.hpp"
 #include "opencv2/imgproc/imgproc.hpp"
 #include <iostream>
 #include <stdio.h>

 using namespace cv;
 using namespace std;
 /// 全局变量
 Mat src, dst;
 Mat map_x, map_y;
 char* remap_window = "Remap demo";
 int ind = 0;

 /// 函数声明 更新映射关系
 void update_map( void );

/// 主函数
 int main( int argc, char** argv )
 {
  string imageName("../../common/data/77.jpeg"); // 图片文件名路径(默认值)
    if( argc > 1)
    {
        imageName = argv[1];//如果传递了文件 就更新
    }

  src = imread( imageName );
  if( src.empty() )
    { 
        cout <<  "can't load image " << endl;
	return -1;  
    }

  /// 创建几张映射矩阵
// map_x: x方向的映射参数. 它相当于方法 h(i,j) 的第一个参数
// map_y: y方向的映射参数. 注意 map_y 和 map_x 与 src 的大小一致。
  dst.create( src.size(), src.type() );
  map_x.create( src.size(), CV_32FC1 );
  map_y.create( src.size(), CV_32FC1 );

  /// 创建显示窗口
  namedWindow( remap_window, CV_WINDOW_AUTOSIZE );

  /// 循环
  while( true )
  {
    int c = waitKey( 1000 );//1s检测一次按键  按Esc键退出
    if( (char)c == 27 )
      { break; }

    /// 更新重映射图
    update_map();
// map_x: x方向的映射参数. 它相当于方法 h(i,j) 的第一个参数
// map_y: y方向的映射参数. 注意 map_y 和 map_x 与 src 的大小一致。
    remap( src, dst, map_x, map_y, CV_INTER_LINEAR, BORDER_CONSTANT, Scalar(0,0, 0) );

    /// 显示结果
    imshow( remap_window, dst );
  }
  return 0;
 }

// 更新函数 
 void update_map( void )
 {
   ind = ind%4;// 0 1 2 3

   for( int j = 0; j < src.rows; j++ )//每行
   { for( int i = 0; i < src.cols; i++ )//每列
       {
         switch( ind )
         {
           case 0:// 图像宽高缩小一半,并显示在中间:
             if( i > src.cols*0.25 && i < src.cols*0.75 && j > src.rows*0.25 && j < src.rows*0.75 )
               {
                 map_x.at<float>(j,i) = 2*( i - src.cols*0.25 ) + 0.5 ;//记录的是坐标
                 map_y.at<float>(j,i) = 2*( j - src.rows*0.25 ) + 0.5 ;
                }
             else
               { map_x.at<float>(j,i) = 0 ;
                 map_y.at<float>(j,i) = 0 ;
               }
                 break;
           case 1:// 图像上下颠倒
                 map_x.at<float>(j,i) = i ;//列不变
                 map_y.at<float>(j,i) = src.rows - j ;//行交换
                 break;
           case 2:// 图像左右颠倒
                 map_x.at<float>(j,i) = src.cols - i ;//列交换
                 map_y.at<float>(j,i) = j ;//行不变
                 break;
           case 3:// 上下颠倒 + 左右颠倒
                 map_x.at<float>(j,i) = src.cols - i ;
                 map_y.at<float>(j,i) = src.rows - j ;
                 break;
         } // end of switch
       }
    }
  ind++;
}

 

8.2 仿射变换

 

    使用OpenCV函数 warpAffine 来实现一些简单的重映射.
    使用OpenCV函数 getRotationMatrix2D 来获得一个 3 * 3 旋转矩阵

什么是仿射变换?
    一个任意的仿射变换都能表示为 乘以一个矩阵 (线性变换) 接着再 加上一个向量 (平移).
    综上所述, 我们能够用仿射变换来表示:
         旋转 (线性变换)
         平移 (向量加)
         缩放操作 (线性变换)

    你现在可以知道, 事实上, 仿射变换代表的是两幅图之间的 关系 . [R T]

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
#include <stdio.h>

using namespace cv;
using namespace std;

/// 全局变量
char* source_window = "Source image";
char* warp_window = "Warp";
char* warp_rotate_window = "Warp + Rotate";

// 主函数
 int main( int argc, char** argv )
 {
   Point2f srcTri[3];//原来 3个点
   Point2f dstTri[3];//目标三点

   Mat rot_mat( 2, 3, CV_32FC1 );//
   Mat warp_mat( 2, 3, CV_32FC1 );
   Mat src, warp_dst, warp_rotate_dst;// 储存中间和目标图像的Mat

   /// 加载源图像
   string imageName("../../common/data/77.jpeg"); // 图片文件名路径(默认值)
    if( argc > 1)
    {
        imageName = argv[1];//如果传递了文件 就更新
    }

   src = imread( imageName );
   if( src.empty() )
    { 
        cout <<  "can't load image " << endl;
	return -1;  
    }
   /// 设置目标图像的大小和类型与源图像一致
   warp_dst = Mat::zeros( src.rows, src.cols, src.type() );

   /// 设置源图像和目标图像上的三组点以计算仿射变换
   srcTri[0] = Point2f( 0,0 );
   srcTri[1] = Point2f( src.cols - 1, 0 );
   srcTri[2] = Point2f( 0, src.rows - 1 );

   dstTri[0] = Point2f( src.cols*0.0, src.rows*0.33 );
   dstTri[1] = Point2f( src.cols*0.85, src.rows*0.25 );
   dstTri[2] = Point2f( src.cols*0.15, src.rows*0.7 );

   /// 求得仿射变换
   // 通过这两组点, 我们能够使用OpenCV函数 getAffineTransform 来求出仿射变换:
   warp_mat = getAffineTransform( srcTri, dstTri );

   /// 对源图像应用上面求得的仿射变换
   warpAffine( src, warp_dst, warp_mat, warp_dst.size() );


   /** 对图像扭曲后再旋转 */
   /// 计算绕图像中点顺时针旋转50度缩放因子为0.6的旋转矩阵
   Point center = Point( warp_dst.cols/2, warp_dst.rows/2 );
   double angle = -50.0;//逆时针为正  旋转
   double scale = 0.6;  // 缩放

   /// 通过上面的旋转细节信息求得旋转矩阵
   rot_mat = getRotationMatrix2D( center, angle, scale );//旋转矩阵
   /// 旋转已扭曲图像  将刚刚求得的仿射变换应用到源图像
   warpAffine( warp_dst, warp_rotate_dst, rot_mat, warp_dst.size() );

   /// 显示结果
   namedWindow( source_window, CV_WINDOW_AUTOSIZE );
   imshow( source_window, src );

   namedWindow( warp_window, CV_WINDOW_AUTOSIZE );
   imshow( warp_window, warp_dst );

   namedWindow( warp_rotate_window, CV_WINDOW_AUTOSIZE );
   imshow( warp_rotate_window, warp_rotate_dst );

   /// 等待用户按任意按键退出程序
   waitKey(0);

   return 0;
  }

 

九、直方图计算 直方图均衡化 直方图反向投影 查找 搜索匹配

 

图像的直方图计算
单个通道内的像素值进行统计
什么是直方图?
    直方图是对数据的集合 统计 ,并将统计结果分布于一系列预定义的 bins 中。
    这里的 数据 不仅仅指的是灰度值 (如上一篇您所看到的),
     统计数据可能是任何能有效描述图像的特征。
    先看一个例子吧。 假设有一个矩阵包含一张图像的信息 (灰度值 0-255):

OpenCV提供了一个简单的计算数组集(通常是图像或分割后的通道)

的直方图函数 calcHist 。 支持高达 32 维的直方图。

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
#include <stdio.h>

using namespace std;
using namespace cv;

/** @函数 main */
int main( int argc, char** argv )
{
  Mat src, dst;

 /// 加载源图像
   string imageName("../../common/data/77.jpeg"); // 图片文件名路径(默认值)
    if( argc > 1)
    {
        imageName = argv[1];//如果传递了文件 就更新
    }

   src = imread( imageName );
   if( src.empty() )
    { 
        cout <<  "can't load image " << endl;
	return -1;  
    }


 /// split分割成3个单通道图像 ( R, G 和 B )
 vector<Mat> rgb_planes;
 split( src, rgb_planes );

 /// 设定bin数目
 int histSize = 255;

 /// 设定取值范围 ( R,G,B) )
 float range[] = { 0, 255 } ;
 const float* histRange = { range };

 bool uniform = true; bool accumulate = false;

 Mat r_hist, g_hist, b_hist;

 /// 计算直方图:
 calcHist( &rgb_planes[0], 1, 0, Mat(), r_hist, 1, &histSize, &histRange, uniform, accumulate );
 calcHist( &rgb_planes[1], 1, 0, Mat(), g_hist, 1, &histSize, &histRange, uniform, accumulate );
 calcHist( &rgb_planes[2], 1, 0, Mat(), b_hist, 1, &histSize, &histRange, uniform, accumulate );
// 1: 输入数组的个数 (这里我们使用了一个单通道图像,我们也可以输入数组集 )
// 0: 需要统计的通道 (dim)索引 ,这里我们只是统计了灰度 (且每个数组都是单通道)所以只要写 0 就行了。
// Mat(): 掩码( 0 表示忽略该像素), 如果未定义,则不使用
// r_hist: 储存直方图的矩阵
// 1: 直方图维数
// histSize: 每个维度的bin数目
// histRange: 每个维度的取值范围
// uniform 和 accumulate: bin大小相同,清除直方图痕迹

 // 创建直方图画布
 int hist_w = 400; int hist_h = 400;
 int bin_w = cvRound( (double) hist_w/histSize );

 Mat histImage( hist_w, hist_h, CV_8UC3, Scalar( 0,0,0) );

 /// 将直方图归一化到范围 [ 0, histImage.rows ]
 normalize(r_hist, r_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );
 normalize(g_hist, g_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );
 normalize(b_hist, b_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );
// 0 及 histImage.rows: 这里,它们是归一化 r_hist 之后的取值极限

 /// 在直方图画布上画出直方图
 for( int i = 1; i < histSize; i++ )
   {
     line( histImage, Point( bin_w*(i-1), hist_h - cvRound(r_hist.at<float>(i-1)) ) ,
                      Point( bin_w*(i), hist_h - cvRound(r_hist.at<float>(i)) ),
                      Scalar( 0, 0, 255), 2, 8, 0  );
     line( histImage, Point( bin_w*(i-1), hist_h - cvRound(g_hist.at<float>(i-1)) ) ,
                      Point( bin_w*(i), hist_h - cvRound(g_hist.at<float>(i)) ),
                      Scalar( 0, 255, 0), 2, 8, 0  );
     line( histImage, Point( bin_w*(i-1), hist_h - cvRound(b_hist.at<float>(i-1)) ) ,
                      Point( bin_w*(i), hist_h - cvRound(b_hist.at<float>(i)) ),
                      Scalar( 255, 0, 0), 2, 8, 0  );
    }

 /// 显示直方图
 namedWindow("calcHist Demo", CV_WINDOW_AUTOSIZE );
 imshow("calcHist Demo", histImage );

 waitKey(0);

 return 0;

}

求得对直方图均衡化的映射矩阵 在对原图像进行映射
图像的直方图是什么?
    直方图是图像中像素强度分布的图形表达方式.
    它统计了每一个强度值(灰度 0~255 256个值)所具有的像素点个数.

直方图均衡化是什么?

    直方图均衡化是通过拉伸像素强度分布范围来增强图像对比度的一种方法.
    说得更清楚一些, 以上面的直方图为例, 你可以看到像素主要集中在中间的一些强度值上.
 直方图均衡化要做的就是 拉伸 这个范围. 见下面左图: 绿圈圈出了 少有像素分布其上的 强度值.
对其应用均衡化后, 得到了中间图所示的直方图. 均衡化的图像见下面右图.

直方图均衡化是怎样做到的?
    均衡化指的是把一个分布 (给定的直方图) 映射
    到另一个分布 (一个更宽更统一的强度值分布), 所以强度值分布会在整个范围内展开.
    要想实现均衡化的效果, 映射函数应该是一个 累积分布函数 (cdf)

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
#include <stdio.h>

using namespace cv;
using namespace std;

// 主函数
int main( int argc, char** argv )
{
  Mat src, dst;

  char* source_window = "Source image";
  char* equalized_window = "Equalized Image";

   /// 加载源图像
   string imageName("../../common/data/77.jpeg"); // 图片文件名路径(默认值)
    if( argc > 1)
    {
        imageName = argv[1];//如果传递了文件 就更新
    }

   src = imread( imageName );
   if( src.empty() )
    { 
        cout <<  "can't load image " << endl;
	return -1;  
    }

  /// 转为灰度图
  cvtColor( src, src, CV_BGR2GRAY );

  /// 应用直方图均衡化
  equalizeHist( src, dst );

  /// 显示结果
  namedWindow( source_window, CV_WINDOW_AUTOSIZE );
  namedWindow( equalized_window, CV_WINDOW_AUTOSIZE );

  imshow( source_window, src );
  imshow( equalized_window, dst );

  /// 等待用户按键退出程序
  waitKey(0);

  return 0;
}


直方图对比
    如何使用OpenCV函数 compareHist 产生一个表达两个直方图的相似度的数值。
    如何使用不同的对比标准来对直方图进行比较。

要比较两个直方图( H_1 and H_2 ),
首先必须要选择一个衡量直方图相似度的 对比标准 (d(H_{1}, H_{2})) 。
OpenCV 函数 compareHist 执行了具体的直方图对比的任务。
该函数提供了4种对比标准来计算相似度:

【1】相关关系 Correlation ( CV_COMP_CORREL )  与均值的偏差 积

【2】平方差  Chi-Square ( CV_COMP_CHISQR )

【3】交集?Intersection ( CV_COMP_INTERSECT ) 对应最小值集合

【4】Bhattacharyya 距离( CV_COMP_BHATTACHARYYA ) 巴氏距离(巴塔恰里雅距离 / Bhattacharyya distance)

本程序做什么?

    装载一张 基准图像 和 两张 测试图像 进行对比。
    产生一张取自 基准图像 下半部的图像。
    将图像转换到HSV格式。
    计算所有图像的H-S直方图,并归一化以便对比。
    将 基准图像 直方图与 两张测试图像直方图,基准图像半身像直方图,以及基准图像本身的直方图分别作对比。

 

    显示计算所得的直方图相似度数值。

 

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
#include <stdio.h>

using namespace std;
using namespace cv;

/** @函数 main */
int main( int argc, char** argv )
{
  Mat src_base, hsv_base;
  Mat src_test1, hsv_test1;
  Mat src_test2, hsv_test2;
  Mat hsv_half_down;

  /// 装载三张背景环境不同的图像
  if( argc < 4 )
    { printf("** Error. Usage: ./compareHist_Demo <image_settings0> <image_setting1> <image_settings2>\n");
      return -1;
    }

  src_base = imread( argv[1], 1 );
  src_test1 = imread( argv[2], 1 );
  src_test2 = imread( argv[3], 1 );

  /// 转换到 HSV
  cvtColor( src_base, hsv_base, CV_BGR2HSV );
  cvtColor( src_test1, hsv_test1, CV_BGR2HSV );
  cvtColor( src_test2, hsv_test2, CV_BGR2HSV );

  hsv_half_down = hsv_base( Range( hsv_base.rows/2, hsv_base.rows - 1 ), Range( 0, hsv_base.cols - 1 ) );

  /// 对hue通道使用30个bin,对saturatoin通道使用32个bin
  int h_bins = 50; int s_bins = 60;
  int histSize[] = { h_bins, s_bins };

  // hue的取值范围从0到256, saturation取值范围从0到180
  float h_ranges[] = { 0, 256 };
  float s_ranges[] = { 0, 180 };

  const float* ranges[] = { h_ranges, s_ranges };

  // 使用第0和第1通道
  int channels[] = { 0, 1 };

  /// 直方图
  MatND hist_base;
  MatND hist_half_down;
  MatND hist_test1;
  MatND hist_test2;

  /// 计算HSV图像的直方图
  calcHist( &hsv_base, 1, channels, Mat(), hist_base, 2, histSize, ranges, true, false );
  normalize( hist_base, hist_base, 0, 1, NORM_MINMAX, -1, Mat() );

  calcHist( &hsv_half_down, 1, channels, Mat(), hist_half_down, 2, histSize, ranges, true, false );
  normalize( hist_half_down, hist_half_down, 0, 1, NORM_MINMAX, -1, Mat() );

  calcHist( &hsv_test1, 1, channels, Mat(), hist_test1, 2, histSize, ranges, true, false );
  normalize( hist_test1, hist_test1, 0, 1, NORM_MINMAX, -1, Mat() );

  calcHist( &hsv_test2, 1, channels, Mat(), hist_test2, 2, histSize, ranges, true, false );
  normalize( hist_test2, hist_test2, 0, 1, NORM_MINMAX, -1, Mat() );

  ///应用不同的直方图对比方法
  for( int i = 0; i < 4; i++ )
     { int compare_method = i;
       double base_base = compareHist( hist_base, hist_base, compare_method );
       double base_half = compareHist( hist_base, hist_half_down, compare_method );
       double base_test1 = compareHist( hist_base, hist_test1, compare_method );
       double base_test2 = compareHist( hist_base, hist_test2, compare_method );

       printf( " Method [%d] Perfect, Base-Half, Base-Test(1), Base-Test(2) : %f, %f, %f, %f \n", i, base_base, base_half , base_test1, base_test2 );
     }

  printf( "Done \n" );

  return 0;
 }

 

反向投影   利用直方图模型 搜索 对应的 图像区域

    反向投影是一种记录给定图像中的像素点如何适应直方图模型像素分布的方式。
    简单的讲, 所谓反向投影就是首先计算某一特征的直方图模型,然后使用模型去寻找图像中存在的该特征。
    例如, 你有一个肤色直方图 ( Hue-Saturation 直方图 ),你可以用它来寻找图像中的肤色区域:

http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/imgproc/histograms/back_projection/back_projection.html

 

 

#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"

#include <iostream>

using namespace cv;
using namespace std;

/// 全局变量
Mat src; Mat hsv; Mat hue;
int bins = 25;

/// 函数申明
void Hist_and_Backproj(int, void* );

/** @函数 main */
int main( int argc, char** argv )
{
  /// 读取图像
  src = imread( argv[1], 1 );
  /// 转换到 HSV 空间
  cvtColor( src, hsv, CV_BGR2HSV );

  /// 分离 Hue 通道
  hue.create( hsv.size(), hsv.depth() );
  int ch[] = { 0, 0 };
  mixChannels( &hsv, 1, &hue, 1, ch, 1 );

  /// 创建 Trackbar 来输入bin的数目
  char* window_image = "Source image";
  namedWindow( window_image, CV_WINDOW_AUTOSIZE );
  createTrackbar("* Hue  bins: ", window_image, &bins, 180, Hist_and_Backproj );
  Hist_and_Backproj(0, 0);

  /// 现实图像
  imshow( window_image, src );

  /// 等待用户反应
  waitKey(0);
  return 0;
}


/**
 * @函数 Hist_and_Backproj
 * @简介:Trackbar事件的回调函数
 */
void Hist_and_Backproj(int, void* )
{
  MatND hist;
  int histSize = MAX( bins, 2 );
  float hue_range[] = { 0, 180 };
  const float* ranges = { hue_range };

  /// 计算直方图并归一化
  calcHist( &hue, 1, 0, Mat(), hist, 1, &histSize, &ranges, true, false );
  normalize( hist, hist, 0, 255, NORM_MINMAX, -1, Mat() );

  /// 计算反向投影
  MatND backproj;
  calcBackProject( &hue, 1, 0, hist, backproj, &ranges, 1, true );

  /// 显示反向投影
  imshow( "BackProj", backproj );

  /// 显示直方图
  int w = 400; int h = 400;
  int bin_w = cvRound( (double) w / histSize );
  Mat histImg = Mat::zeros( w, h, CV_8UC3 );

  for( int i = 0; i < bins; i ++ )
     { rectangle( histImg, Point( i*bin_w, h ), Point( (i+1)*bin_w, h - cvRound( hist.at<float>(i)*h/255.0 ) ), Scalar( 0, 0, 255 ), -1 ); }

  imshow( "Histogram", histImg );
}

十、计算物体的凸包 物体边缘包围圈 矩形和圆形边界框 可倾斜的边界框和椭圆

/* 计算物体的凸包  边缘包围圈
对图像进行二值化     candy边缘检测也得到 二值图
寻找轮廓 
对每个轮廓计算其凸包
绘出轮廓及其凸包
*/

#include "opencv2/highgui/highgui.hpp"
 #include "opencv2/imgproc/imgproc.hpp"
 #include <iostream>
 #include <stdio.h>
 #include <stdlib.h>

 using namespace cv;
 using namespace std;

 Mat src; Mat src_gray;
 int thresh = 100;
 int max_thresh = 255;
 RNG rng(12345);

 /// Function header
 void thresh_callback(int, void* );

/** @function main */
int main( int argc, char** argv )
 {
  /// 加载源图像
    string imageName("../../common/data/apple.jpeg"); // 图片文件名路径(默认值)
    if( argc > 1)
    {
        imageName = argv[1];//如果传递了文件 就更新
    }

    src = imread( imageName );
    if( src.empty() )
     { 
        cout <<  "can't load image " << endl;
	return -1;  
     }
   /// 转成灰度图并进行模糊降噪
   cvtColor( src, src_gray, CV_BGR2GRAY );
   blur( src_gray, src_gray, Size(3,3) );

   /// 创建窗体
   char* source_window = "Source";
   namedWindow( source_window, CV_WINDOW_AUTOSIZE );
   imshow( source_window, src );

   createTrackbar( " Threshold:", "Source", &thresh, max_thresh, thresh_callback );
   thresh_callback( 0, 0 );

   waitKey(0);
   return(0);
 }


// 回调函数
 void thresh_callback(int, void* )
 {
   Mat src_copy = src.clone();
   Mat threshold_output;
   vector<vector<Point> > contours;
   vector<Vec4i> hierarchy;

   /// 对图像进行二值化  这里 candy边缘检测也得到 二值图
   threshold( src_gray, threshold_output, thresh, 255, THRESH_BINARY );

   /// 寻找轮廓
   findContours( threshold_output, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0) );

   /// 对每个轮廓计算其凸包
   vector<vector<Point> >hull( contours.size() );
   for( int i = 0; i < contours.size(); i++ )
      {  convexHull( Mat(contours[i]), hull[i], false ); }

   /// 绘出轮廓及其凸包
   Mat drawing = Mat::zeros( threshold_output.size(), CV_8UC3 );
   for( int i = 0; i< contours.size(); i++ )
      {
        Scalar color = Scalar( rng.uniform(0, 255), rng.uniform(0,255), rng.uniform(0,255) );
        drawContours( drawing, contours, i, color, 1, 8, vector<Vec4i>(), 0, Point() );
        drawContours( drawing, hull, i, color, 1, 8, vector<Vec4i>(), 0, Point() );
      }

   /// 把结果显示在窗体
   namedWindow( "Hull demo", CV_WINDOW_AUTOSIZE );
   imshow( "Hull demo", drawing );
 }


/*
创建包围轮廓的矩形和圆形边界框
使用Threshold检测边缘  二值化 阈值 thresh
找到轮廓  findContours
*/

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
#include <stdio.h>
#include <stdlib.h>

using namespace cv;
using namespace std;

Mat src; Mat src_gray;
int thresh = 100;// 二值化阈值
int max_thresh = 255;
RNG rng(12345);

/// 函数声明 回调函数
void thresh_callback(int, void* );

// @主函数
int main( int argc, char** argv )
{
/// 加载源图像
    string imageName("../../common/data/apple.jpeg"); // 图片文件名路径(默认值)
    if( argc > 1)
    {
        imageName = argv[1];//如果传递了文件 就更新
    }

    src = imread( imageName );
    if( src.empty() )
     { 
        cout <<  "can't load image " << endl;
	return -1;  
     }
  /// 转化成灰度图像并进行平滑
  cvtColor( src, src_gray, CV_BGR2GRAY );
  blur( src_gray, src_gray, Size(3,3) );

  /// 创建窗口
  char* source_window = "Source";
  namedWindow( source_window, CV_WINDOW_AUTOSIZE );
  imshow( source_window, src );

  createTrackbar( " Threshold:", "Source", &thresh, max_thresh, thresh_callback );
  thresh_callback( 0, 0 );

  waitKey(0);
  return(0);
}

/** @thresh_callback 函数 */
void thresh_callback(int, void* )
{
  Mat threshold_output;//阈值检测边缘
  vector<vector<Point> > contours;
  vector<Vec4i> hierarchy;

  /// 使用Threshold检测边缘  二值化 阈值 thresh
  threshold( src_gray, threshold_output, thresh, 255, THRESH_BINARY );
  /// 找到轮廓  轮廓  contours
  findContours( threshold_output, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0) );

  /// 多边形逼近轮廓 + 获取矩形和圆形边界框
  vector<vector<Point> > contours_poly( contours.size() );// 多边形逼近轮廓
  vector<Rect> boundRect( contours.size() );//矩形边界框
  vector<Point2f>center( contours.size() );//圆形边界框 中心
  vector<float>radius( contours.size() );//圆形边界框 半径

  for( int i = 0; i < contours.size(); i++ )//对于每一个轮廓
     { approxPolyDP( Mat(contours[i]), contours_poly[i], 3, true );// 多边形逼近轮廓
       boundRect[i] = boundingRect( Mat(contours_poly[i]) );//矩形边界框
       minEnclosingCircle( contours_poly[i], center[i], radius[i] );//圆形边界框
     }


  /// 画多边形轮廓 + 包围的矩形框 + 圆形框
  Mat drawing = Mat::zeros( threshold_output.size(), CV_8UC3 );
  for( int i = 0; i< contours.size(); i++ )
     {
       Scalar color = Scalar( rng.uniform(0, 255), rng.uniform(0,255), rng.uniform(0,255) );
       drawContours( drawing, contours_poly, i, color, 1, 8, vector<Vec4i>(), 0, Point() );//多边形轮廓 
       rectangle( drawing, boundRect[i].tl(), boundRect[i].br(), color, 2, 8, 0 );//矩形框
       circle( drawing, center[i], (int)radius[i], color, 2, 8, 0 );//圆形框
     }

  /// 显示在一个窗口
  namedWindow( "Contours", CV_WINDOW_AUTOSIZE );
  imshow( "Contours", drawing );
}

 

/*
为轮廓创建可倾斜的边界框和椭圆
*/

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
#include <stdio.h>
#include <stdlib.h>

using namespace cv;
using namespace std;

Mat src; Mat src_gray;
int thresh = 100;
int max_thresh = 255;
RNG rng(12345);

/// 回调函数Function header
void thresh_callback(int, void* );

// @function main 
int main( int argc, char** argv )
{
// 加载源图像
    string imageName("../../common/data/apple.jpeg"); // 图片文件名路径(默认值)
    if( argc > 1)
    {
        imageName = argv[1];//如果传递了文件 就更新
    }

    src = imread( imageName );
    if( src.empty() )
     { 
        cout <<  "can't load image " << endl;
	return -1;  
     }

  /// 转为灰度图并模糊化
  cvtColor( src, src_gray, CV_BGR2GRAY );
  blur( src_gray, src_gray, Size(3,3) );

  /// 创建窗体
  char* source_window = "Source";
  namedWindow( source_window, CV_WINDOW_AUTOSIZE );
  imshow( source_window, src );

  // 滑动条 动态改变参数   二值化 阈值边界检测  thresh
  createTrackbar( " Threshold:", "Source", &thresh, max_thresh, thresh_callback );
  thresh_callback( 0, 0 );

  waitKey(0);
  return(0);
}

// @function thresh_callback 
void thresh_callback(int, void* )
{
  Mat threshold_output;
  vector<vector<Point> > contours;
  vector<Vec4i> hierarchy;

  /// 阈值化检测边界 二值化 阈值边界检测
  threshold( src_gray, threshold_output, thresh, 255, THRESH_BINARY );
  /// 寻找轮廓
  findContours( threshold_output, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0) );

  /// 对每个找到的轮廓创建可倾斜的边界框和椭圆
  vector<RotatedRect> minRect( contours.size() );// 可倾斜的边界框
  vector<RotatedRect> minEllipse( contours.size() );// 椭圆

  for( int i = 0; i < contours.size(); i++ )
     { minRect[i] = minAreaRect( Mat(contours[i]) );// 可倾斜的边界框
       if( contours[i].size() > 5 )
         { minEllipse[i] = fitEllipse( Mat(contours[i]) ); }// 椭圆
     }

  /// 绘出轮廓及其可倾斜的边界框和边界椭圆
  Mat drawing = Mat::zeros( threshold_output.size(), CV_8UC3 );
  for( int i = 0; i< contours.size(); i++ )
     {
       Scalar color = Scalar( rng.uniform(0, 255), rng.uniform(0,255), rng.uniform(0,255) );
       // contour 轮廓
       drawContours( drawing, contours, i, color, 1, 8, vector<Vec4i>(), 0, Point() );
       // ellipse 椭圆
       ellipse( drawing, minEllipse[i], color, 2, 8 );
       // rotated rectangle 可倾斜的边界框
       Point2f rect_points[4]; minRect[i].points( rect_points );
       for( int j = 0; j < 4; j++ )
          line( drawing, rect_points[j], rect_points[(j+1)%4], color, 1, 8 );
     }

  /// 结果在窗体中显示
  namedWindow( "Contours", CV_WINDOW_AUTOSIZE );
  imshow( "Contours", drawing );
}

 

十一、轮廓矩

 

/*
轮廓矩


因为我们常常会将随机变量(先假定有任意阶矩)作一个线性变换,
把一阶矩(期望)归零,
二阶矩(方差)归一,以便统一研究一些问题。
三阶矩,就是我们所称的「偏度」。
    典型的正偏度投资,就是彩票和保险:
      一般来说,你花的那一点小钱就打水漂了,但是这一点钱完全是在承受范围内的;
       而这点钱则部分转化为小概率情况下的巨大收益。
    而负偏度变量则正好相反,「一般为正,极端值为负」,
      可以参照一些所谓的「灰色产业」:
      一般情况下是可以赚到一点钱的,但是有较小的概率「东窗事发」,赔得血本无归。


四阶矩,又称峰度,简单来说相当于「方差的方差」,
和偏度类似,都可以衡量极端值的情况。峰度较大通常意味着极端值较常出现,
峰度较小通常意味着极端值即使出现了也不会「太极端」。
峰度是大还是小通常与3(即正态分布的峰度)相比较。
至于为什么五阶以上的矩没有专门的称呼,主要是因为我们习惯的线性变换,
只有两个自由度,故最多只能将前两阶矩给「标准化」。
这样,标准化以后,第三、第四阶的矩就比较重要了,前者衡量正负,
后者衡量偏离程度,与均值、方差的关系类似。换句话说,
假如我们能把前四阶矩都给「标准化」了,那么五阶、六阶的矩就会比较重要了吧。



*/
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
#include <stdio.h>
#include <stdlib.h>

using namespace cv;
using namespace std;

Mat src; Mat src_gray;
int thresh = 100;
int max_thresh = 255;
RNG rng(12345);

/// 回调函数声明
void thresh_callback(int, void* );

// @主函数 
int main( int argc, char** argv )
{
// 加载源图像
    string imageName("../../common/data/apple.jpeg"); // 图片文件名路径(默认值)
    if( argc > 1)
    {
        imageName = argv[1];//如果传递了文件 就更新
    }

    src = imread( imageName );
    if( src.empty() )
     { 
        cout <<  "can't load image " << endl;
	return -1;  
     }

  /// 把原图像转化成灰度图像并进行平滑
  cvtColor( src, src_gray, CV_BGR2GRAY );
  blur( src_gray, src_gray, Size(3,3) );

  /// 创建新窗口
  char* source_window = "Source";
  namedWindow( source_window, CV_WINDOW_AUTOSIZE );
  imshow( source_window, src );

  // 滑动条 动态改变参数   二值化 阈值边界检测  thresh
  createTrackbar( " Canny thresh:", "Source", &thresh, max_thresh, thresh_callback );
  thresh_callback( 0, 0 );

  waitKey(0);
  return(0);
}

// @thresh_callback 函数 
void thresh_callback(int, void* )
{
  Mat canny_output;
  vector<vector<Point> > contours;
  vector<Vec4i> hierarchy;

  /// 使用Canndy检测边缘  低阈值 thresh 高阈值 thresh*2  核大小
  Canny( src_gray, canny_output, thresh, thresh*2, 3 );
  /// 找到轮廓
  findContours( canny_output, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0) );

  /// 计算矩
  vector<Moments> mu(contours.size() );
  for( int i = 0; i < contours.size(); i++ )
     { mu[i] = moments( contours[i], false ); }

  ///  计算中心矩:
  vector<Point2f> mc( contours.size() );
  for( int i = 0; i < contours.size(); i++ )
     { mc[i] = Point2f( mu[i].m10/mu[i].m00 , mu[i].m01/mu[i].m00 ); }

  /// 绘制轮廓
  Mat drawing = Mat::zeros( canny_output.size(), CV_8UC3 );
  for( int i = 0; i< contours.size(); i++ )
     {
       Scalar color = Scalar( rng.uniform(0, 255), rng.uniform(0,255), rng.uniform(0,255) );
       drawContours( drawing, contours, i, color, 2, 8, hierarchy, 0, Point() );
       circle( drawing, mc[i], 4, color, -1, 8, 0 );
     }

  /// 显示到窗口中
  namedWindow( "Contours", CV_WINDOW_AUTOSIZE );
  imshow( "Contours", drawing );

  /// 通过m00计算轮廓面积并且和OpenCV函数比较
  printf("\t Info: Area and Contour Length \n");
  for( int i = 0; i< contours.size(); i++ )
     {
       printf(" * Contour[%d] - Area (M_00) = %.2f - Area OpenCV: %.2f - Length: %.2f \n", i, mu[i].m00, contourArea(contours[i]), arcLength( contours[i], true ) );
       Scalar color = Scalar( rng.uniform(0, 255), rng.uniform(0,255), rng.uniform(0,255) );
       drawContours( drawing, contours, i, color, 2, 8, hierarchy, 0, Point() );
       circle( drawing, mc[i], 4, color, -1, 8, 0 );
     }
}

十二、基于距离变换和分水岭算法的图像匹配

图像 模板匹配  是一项在一幅图像中寻找与另一幅模板图像最匹配(相似)部分的技术.
使用OpenCV函数 matchTemplate 在模板块和输入图像之间寻找匹配, 获得匹配结果图像
使用OpenCV函数 minMaxLoc 在给定的矩阵(上述得到的匹配结果矩阵)中寻找最大和最小值(包括它们的位置).


什么是模板匹配?
模板匹配是一项在一幅图像中寻找与另一幅模板图像最匹配(相似)部分的技术.
我们需要2幅图像:
    原图像 (I): 在这幅图像里,我们希望找到一块和模板匹配的区域
    模板 (T): 将和原图像比照的图像块
我们的目标是检测最匹配的区域:
为了确定匹配区域, 我们不得不滑动模板图像和原图像进行 比较 :

通过 滑动, 我们的意思是图像块一次移动一个像素 (从左往右,从上往下).
在每一个位置, 都进行一次度量计算来表明它是 “好” 或 “坏” 地与那个位置匹配 (或者说块图像和原图像的特定区域有多么相似).
对于 T 覆盖在 I 上的每个位置,你把度量值 保存 到 结果图像矩阵 (R) 中. 在 R 中的每个位置 (x,y) 都包含匹配度量值:

上图就是 TM_CCORR_NORMED 方法处理后的结果图像 R . 最白的位置代表最高的匹配. 正如您所见, 红色椭圆框住的位置很可能是结果图像矩阵中的最大数值, 所以这个区域 (以这个点为顶点,长宽和模板图像一样大小的矩阵) 被认为是匹配的.
实际上, 我们使用函数 minMaxLoc 来定位在矩阵 R 中的最大值点 (或者最小值, 根据函数输入的匹配参数) .

OpenCV中支持哪些匹配算法
【1】 平方差匹配 method=CV_TM_SQDIFF  square dirrerence(error)
     这类方法利用平方差来进行匹配,最好匹配为0.匹配越差,匹配值越大.
【2】标准平方差匹配 method=CV_TM_SQDIFF_NORMED  standard  square dirrerence(error)

【3】 相关匹配 method=CV_TM_CCORR
     这类方法采用模板和图像间的乘法操作,所以较大的数表示匹配程度较高,0标识最坏的匹配效果.
【4】 标准相关匹配 method=CV_TM_CCORR_NORMED

【5】 相关匹配 method=CV_TM_CCOEFF
     这类方法将模版对其均值的相对值与图像对其均值的相关值进行匹配,1表示完美匹配,
     -1表示糟糕的匹配,0表示没有任何相关性(随机序列).

【6】标准相关匹配 method=CV_TM_CCOEFF_NORMED

通常,随着从简单的测量(平方差)到更复杂的测量(相关系数),
我们可获得越来越准确的匹配(同时也意味着越来越大的计算代价).
最好的办法是对所有这些设置多做一些测试实验,
以便为自己的应用选择同时兼顾速度和精度的最佳方案.


在这程序实现了什么?

    载入一幅输入图像和一幅模板图像块 (template)
    通过使用函数 matchTemplate 实现之前所述的6种匹配方法的任一个. 用户可以通过滑动条选取任何一种方法.
    归一化匹配后的输出结果
    定位最匹配的区域
    用矩形标注最匹配的区域
 

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
#include <stdio.h>

using namespace std;
using namespace cv;

/// 全局变量
Mat img; Mat templ; Mat result;
char* image_window = "Source Image";
char* result_window = "Result window";

int match_method;
int max_Trackbar = 5;

/// 函数声明
void MatchingMethod( int, void* );

// @主函数
int main( int argc, char** argv )
{
  /// 载入原图像和模板块
  img = imread( argv[1], 1 );
  templ = imread( argv[2], 1 );

  /// 创建窗口
  namedWindow( image_window, CV_WINDOW_AUTOSIZE );
  namedWindow( result_window, CV_WINDOW_AUTOSIZE );

  /// 创建滑动条
  char* trackbar_label = "Method: \n 0: SQDIFF \n 1: SQDIFF NORMED \n 2: TM CCORR \n 3: TM CCORR NORMED \n 4: TM COEFF \n 5: TM COEFF NORMED";
  createTrackbar( trackbar_label, image_window, &match_method, max_Trackbar, MatchingMethod );

  MatchingMethod( 0, 0 );

  waitKey(0);
  return 0;
}

/**
 * @函数 MatchingMethod
 * @简单的滑动条回调函数
 */
void MatchingMethod( int, void* )
{
  /// 将被显示的原图像
  Mat img_display;
  img.copyTo( img_display );

  /// 创建输出结果的矩阵 
// 创建了一幅用来存放匹配结果的输出图像矩阵. 仔细看看输出矩阵的大小(它包含了所有可能的匹配位置)
  int result_cols =  img.cols - templ.cols + 1;
  int result_rows = img.rows - templ.rows + 1;
  result.create( result_cols, result_rows, CV_32FC1 );

  /// 进行匹配和标准化
// 很自然地,参数是输入图像 I, 模板图像 T, 结果图像 R 还有匹配方法 (通过滑动条给出)
  matchTemplate( img, templ, result, match_method );
  normalize( result, result, 0, 1, NORM_MINMAX, -1, Mat() );//对结果进行归一化:

  /// 通过函数 minMaxLoc 定位最匹配的位置
  double minVal; double maxVal; Point minLoc; Point maxLoc;
  Point matchLoc;

  minMaxLoc( result, &minVal, &maxVal, &minLoc, &maxLoc, Mat() );
  /// 对于方法 SQDIFF 和 SQDIFF_NORMED, 越小的数值代表更高的匹配结果. 而对于其他方法, 数值越大匹配越好
  if( match_method  == CV_TM_SQDIFF || match_method == CV_TM_SQDIFF_NORMED )
    { matchLoc = minLoc; }
  else
    { matchLoc = maxLoc; }

  /// 让我看看您的最终结果
// 源图上显示
  rectangle( img_display, matchLoc, Point( matchLoc.x + templ.cols , matchLoc.y + templ.rows ), Scalar::all(0), 2, 8, 0 );
// 匹配结果图上显示
  rectangle( result, matchLoc, Point( matchLoc.x + templ.cols , matchLoc.y + templ.rows ), Scalar::all(0), 2, 8, 0 );

  imshow( image_window, img_display );
  imshow( result_window, result );

  return;
}

2019-04-23 17:26:34 qq_36162036 阅读数 173
  • OpenCV3.2 Java图像处理视频学习教程

    OpenCV3.2 Java图像处理视频培训课程:基于OpenCV新版本3.2.0详细讲述Java OpenCV图像处理部分内容,包括Mat对象使用、图像读写、 基于常用核心API讲述基本原理、使用方法、参数、代码演示、图像处理思路与流程讲授。主要内容包括opencv像素操作、滤波、边缘提取、直线与圆检测、形态学操作与分水岭、图像金子塔融合重建、多尺度模板匹配、opencv人脸检测、OpenCV跟Tomcat使用实现服务器端图像处理服务。

    4259 人正在学习 去看看 贾志刚

#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>
using namespace std;
using namespace cv;
//全局变量声明
Mat g_srcImage, g_dstImage;
int g_nElementShape=MORPH_RECT;//元素结构形状
int g_nMaxIterationNum = 10;
int g_nOpenCloseNum = 0;//
int g_nErordeDilateNum = 0;//
int g_nTopBlackHatNum = 10;//
//轨迹条回调函数
static void on_OpenClose(int, void*);
static void on_ErodeDilate(int, void*);
static void on_TopBlackHat(int, void*);
static void on_ShowHelpText(int, void*);
static void Showhelptext();//显示帮助文字

int main(){
    system("color 5E");
    g_srcImage = imread("靶标原图.png", 1);
    if (!g_srcImage.data){
        printf("打开图片错误,请检查路径\n");
        return false;
    }
    namedWindow("原图窗口", 1);
    imshow("原图窗口", g_srcImage);
    //开闭运算
    namedWindow("开闭运算", 1);
    createTrackbar("迭代值", "开闭运算", &g_nOpenCloseNum,g_nMaxIterationNum*2+1,on_OpenClose);


    //腐蚀与膨胀
    namedWindow("腐蚀膨胀", 1);
    createTrackbar("迭代值", "腐蚀膨胀", &g_nErordeDilateNum,g_nMaxIterationNum * 2 + 1, on_ErodeDilate);

    //顶帽黑帽
    namedWindow("顶帽黑帽", 1);
    createTrackbar("内核值", "顶帽黑帽", &g_nTopBlackHatNum,g_nMaxIterationNum * 2 + 1,on_TopBlackHat);

    while (1)
    {
        on_OpenClose(g_nOpenCloseNum, 0);
        on_ErodeDilate(g_nErordeDilateNum, 0);
        on_TopBlackHat(g_nTopBlackHatNum, 0);
        int c = waitKey(0);
        if ((char)c == 'q' || (char)c == 27)//presh the button q or ESC
            break;
        if ((char)c == 49)
            g_nErordeDilateNum = MORPH_ELLIPSE;//presh the button 1
        if ((char)c == 50)
            g_nElementShape = MORPH_RECT;
        if ((char)c == 51)
            g_nElementShape = MORPH_CROSS;
        else if ((char)c == ' ')
            g_nElementShape = (g_nElementShape + 1) % 3;
    }
    return 0;
}
static void on_OpenClose(int, void*){
    //设置偏移变量
    int offset = g_nOpenCloseNum - g_nMaxIterationNum;
    int Absolute_offset = offset > 0 ? offset : -offset;
    Mat element = getStructuringElement(g_nElementShape, Size(Absolute_offset * 2 + 1, Absolute_offset * 2 + 1), Point(Absolute_offset, Absolute_offset));
    if (offset < 0)
        morphologyEx(g_srcImage, g_dstImage, MORPH_OPEN, element);
    else
    {
        morphologyEx(g_srcImage, g_dstImage, MORPH_CLOSE, element);
    }
    imshow("开闭运算",g_dstImage);

static void on_ErodeDilate(int, void*){
    //设置偏移变量
    int offset = g_nErordeDilateNum - g_nMaxIterationNum;
    int Absolute_offset = offset > 0 ? offset : -offset;
    Mat element = getStructuringElement(g_nElementShape, Size(Absolute_offset * 2 + 1, Absolute_offset * 2 + 1), Point(Absolute_offset, Absolute_offset));
    if (offset < 0)
        erode(g_srcImage, g_dstImage,element);
    else
    {
        erode(g_srcImage, g_dstImage,element);
    }
    imshow("腐蚀膨胀", g_dstImage);
}
static void on_TopBlackHat(int,void*){
    //设置偏移变量
    int offset = g_nTopBlackHatNum - g_nMaxIterationNum;
    int Absolute_offset = offset > 0 ? offset : -offset;
    Mat element = getStructuringElement(g_nElementShape, Size(Absolute_offset * 2 + 1, Absolute_offset * 2 + 1), Point(Absolute_offset, Absolute_offset));
    if (offset < 0)
        morphologyEx(g_srcImage, g_dstImage, MORPH_TOPHAT, element);
    else
    {
        morphologyEx(g_srcImage, g_dstImage, MORPH_BLACKHAT, element);
    }
    imshow("顶帽黑帽", g_dstImage);
}

 

2017-10-17 21:48:05 u012679707 阅读数 287
  • OpenCV3.2 Java图像处理视频学习教程

    OpenCV3.2 Java图像处理视频培训课程:基于OpenCV新版本3.2.0详细讲述Java OpenCV图像处理部分内容,包括Mat对象使用、图像读写、 基于常用核心API讲述基本原理、使用方法、参数、代码演示、图像处理思路与流程讲授。主要内容包括opencv像素操作、滤波、边缘提取、直线与圆检测、形态学操作与分水岭、图像金子塔融合重建、多尺度模板匹配、opencv人脸检测、OpenCV跟Tomcat使用实现服务器端图像处理服务。

    4259 人正在学习 去看看 贾志刚

一、形态学图像处理知识框架



二、二值图像基本形态学运算

2.1 腐蚀

%% 图像放缩  imgErode.m
%{
函数: I1=imerode(I,se);          %图像腐蚀
       se=strel(shape,parameters);%产生结构元素se
函数说明:图像腐蚀
参数说明: I:输入图像
          se:由strel函数返回的自定义或预设的结构元素对象
返回值:腐蚀后目标图像

备注:strel函数中的shape参数,可以为‘square’(正方形)、‘rectangle’(矩形)等
%}

I=imread('erode_dilate.bmp');

se=strel('square',3);%产生边长为3的正方形结构元素
I1=imerode(I,se);   %腐蚀操作


se=strel('square',6);%产生边长为6的正方形结构元素
I2=imerode(I,se);   %腐蚀操作


subplot(2,3,1);
imshow(I);
title('source');

subplot(2,3,2);
imshow(I1);
title('erode_3');

subplot(2,3,3);
imshow(I2);
title('erode_6');


运算结果:



2.2膨胀

%% 图像膨胀  imgdilate.m
%{
函数: I1=imdilate(I,se);          %图像腐蚀
       se=strel(shape,parameters);%产生结构元素se
函数说明:图像膨胀
参数说明: I:输入图像
          se:由strel函数返回的自定义或预设的结构元素对象
返回值:膨胀后图像

备注:strel函数中的shape参数,可以为‘square’(正方形)、‘rectangle’(矩形)等
%}

I=imread('starcraft.bmp');

se=strel('square',3);%产生边长为3的正方形结构元素
I1=imerode(I,se);   %腐蚀操作


se=strel('square',6);%产生边长为6的正方形结构元素
I2=imerode(I,se);   %腐蚀操作


se=strel('square',3);%产生边长为6的正方形结构元素
I3=imdilate(I,se);   %膨胀操作

se=strel('square',6);%产生边长为6的正方形结构元素
I4=imdilate(I,se);   %膨胀操作


subplot(2,3,1);
imshow(I);
title('source');

subplot(2,3,2);
imshow(I1);
title('erode_3');

subplot(2,3,3);
imshow(I2);
title('erode_6');

subplot(2,3,4);
imshow(I3);
title('dilate_3');

subplot(2,3,5);
imshow(I4);
title('dilate_6');

运算结果:




2.3开及闭运算

%% 二值图像的开和闭运算  imgOpen_Close.m
%{
函数: I1=imopen(I,se);          %图像开运算(先腐蚀后膨胀)
       I1=imclose(I,se);         %图像闭运算(先膨胀后腐蚀)
函数说明:图像开和闭
参数说明: I:输入图像
          se:由strel函数返回的自定义或预设的结构元素对象
返回值:运算后图像

备注:strel函数中的shape参数,可以为‘square’(正方形)、‘rectangle’(矩形)等
备注:开操作,和先用erode后用dilate效果是一样的
%}

I=imread('erode_dilate.bmp');

se=strel('square',6);%产生边长为3的正方形结构元素
I1=imopen(I,se);   %开操作
I2=imclose(I,se);   %闭操作



subplot(2,3,1);
imshow(I);
title('source');

subplot(2,3,2);
imshow(I1);
title('open');

subplot(2,3,3);
imshow(I2);
title('close');

运算结果:



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