目录:OpenCV学习笔记
最近也在玩手势识别,资料找了很多,基本可以分为静态手势识别和动态手势识别,先弄个简单的静态手势识别给大家看看。
基本流程如下:
先滤波去噪-->转换到HSV空间-->根据皮肤在HSV空间的分布做出阈值判断,这里用到了inRange函数,然后进行一下形态学的操作,去除噪声干扰,是手的边界更加清晰平滑-->得到的2值图像后用findContours找出手的轮廓,去除伪轮廓后,再用convexHull函数得到凸包络。
结果如下:
源代码:
控制台版本main.cpp:
1.
2.// #include "stdafx.h"
3.//#include "cv.h"
4.
5.#include
6.#include
7.
8.//#include
9.//#include
10.
11.#include
12.
https://www.wendangku.net/doc/c85952832.html,ing namespace std;
14.
15.int pmsf_value = 5;//均值飘逸分割平滑系数
16.int MopEx_value = 2;//开运算
17.int Hmatch_value = 25;//模板匹配系数
18.
19.//亮度
20.int V_low = 30;
21.int V_high = 250;
22.//饱和度
23.int S_low = 10;
24.int S_high = 170;
25.//色相
26.int H_low_max = 40;
27.int H_high_min = 100;
28.int if_high_light = 1; //是否高光补偿
29.
30.IplImage *src = 0;
31.IplImage *srcResize = 0;
32.
33.IplImage *img_YCrCb = 0;
34.CvSize newSize;
35.CvSize sz;
36.
37.IplImage *tmp1;
38.IplImage *tmp2;
39.IplImage *tmp3;
40.IplImage *src2;
41.IplImage *src1;
42.
43.///////////////////////////////////////////////////////////////////////
/////////////////////////////////////////
44.IplImage *YCrCb;
45.IplImage *YCrCb_mask;
46.IplImage *Y_channel,*Cr_channel, *Cb_channel;
47.IplImage *Y_cmp,*Cr_cmp, *Cb_cmp;
48.
49.CvScalar Y_lower;
50.CvScalar Y_upper;
51.
52.CvScalar Cr_lower;
53.CvScalar Cr_upper;
54.
55.CvScalar Cb_lower;
56.CvScalar Cb_upper;
57.
58.CvScalar YCrCb_lower;
59.CvScalar YCrCb_upper;
60.
61.///////////////////////////////////////////////////////////////////////
////////////////////////////////////////
62.
63.void init_hand_YCrCb()
64.{
65. //
66. img_YCrCb = cvCreateImage( sz, 8, 3);
67. YCrCb_mask = cvCreateImage(sz,IPL_DEPTH_8U,1);;
68.
69. //最终的图片
70. YCrCb = cvCreateImage(sz,IPL_DEPTH_8U,3);
71.
72.
73. //三通道
74. Y_channel = cvCreateImage(sz,IPL_DEPTH_8U,1);
75. Cr_channel = cvCreateImage(sz,IPL_DEPTH_8U,1);
76. Cb_channel = cvCreateImage(sz,IPL_DEPTH_8U,1);
77.
78. //按范围截取后
79. Y_cmp = cvCreateImage(sz,IPL_DEPTH_8U,1);
80. Cr_cmp = cvCreateImage(sz,IPL_DEPTH_8U,1);
81. Cb_cmp = cvCreateImage(sz,IPL_DEPTH_8U,1);
82.
83. //Y,Cr,Cb的颜色范围
84. Y_lower = CV_RGB(0,0,130);
85. Y_upper = CV_RGB(0,0,130);
86.
87. Cr_lower = CV_RGB(0,0,125);
88. Cr_upper = CV_RGB(0,0,125);
89.
90. Cb_lower = CV_RGB(0,0,132);
91. Cb_upper = CV_RGB(0,0,147);
92.
93. YCrCb_lower = cvScalar(0,0,132,0);
94. YCrCb_upper = cvScalar(130,125,147,0);
95.}
96.///////////////////////////////////////////////////////////////////////
/////////////////////////////////////////
97.void hand_YCrCb()
98.{
99. //转换到YCrBr
100. cvCvtColor(src2,img_YCrCb, CV_RGB2YCrCb);
101.
102.
103. //分割到Y,Cr,Cb
104. cvSplit(img_YCrCb,Y_channel,Cr_channel,Cb_channel,0);
105.
106. //将Y_channel的位于 Y_lower 和 Y_upper 之间的元素复制到 Y_tmp中107. cvInRangeS( Y_channel, Y_lower, Y_upper, Y_cmp);
108. cvInRangeS(Cr_channel,Cr_lower,Cr_upper ,Cr_cmp);
109. cvInRangeS(Cb_channel,Cb_lower,Cb_upper ,Cb_cmp);
110.
111. //合并Y,Cr,Cb通道到YCrCb中
112. cvMerge(Y_cmp,Cr_cmp,Cb_cmp,0,YCrCb);
113.
114. //显示结果
115. cvShowImage("YCrCb_mask",YCrCb);
116.
117.
118. //cvInRangeS (img_YCrCb, YCrCb_lower, YCrCb_upper, YCrCb_mask); 119. //cvShowImage( "YCrCb_mask", YCrCb_mask);
120.
121.
122.}
123./////////////////////////////////////////////////////////////////////// ///////////////////////////////////////
124.
125. IplImage* hsv_image;
126. IplImage* hsv_mask;
127. CvScalar hsv_min;
128. CvScalar hsv_max;
129.
130. IplImage *H_img,*S_img, *V_img;
131. IplImage *H_mask, *H_mask1,*S_mask, *S_mask1, *V_mask, *V_mask1, *V_mask2;
132./////////////////////////////////////////////////////////////////////// ////////////////////////////////////////
133.void init_hand_HSV()
134.{
135.
136. hsv_image = cvCreateImage( sz, 8, 3);
137. hsv_mask = cvCreateImage( sz, 8, 1);
138. hsv_min = cvScalar(0, 20, 20, 0);
139. hsv_max = cvScalar(20, 250, 255, 0);
140. //hsv_mask->origin = 1;
141.
142. //方法2: 单独处理各个通道
143. H_img = cvCreateImage(sz,IPL_DEPTH_8U,1);
144. S_img = cvCreateImage(sz,IPL_DEPTH_8U,1);
145. V_img = cvCreateImage(sz,IPL_DEPTH_8U,1);
146.
147. H_mask = cvCreateImage(sz,IPL_DEPTH_8U,1);
148. H_mask1 = cvCreateImage(sz,IPL_DEPTH_8U,1);
149. S_mask = cvCreateImage(sz,IPL_DEPTH_8U,1);
150. S_mask1 = cvCreateImage(sz,IPL_DEPTH_8U,1);
151. V_mask = cvCreateImage(sz,IPL_DEPTH_8U,1);
152. V_mask2 = cvCreateImage(sz,IPL_DEPTH_8U,1);
153. V_mask1 = cvCreateImage(sz,IPL_DEPTH_8U,1);
154.
155.
156.
157.}
158./////////////////////////////////////////////////////////////////////// ///////////////////////////////////////
159.
160.void color_blance()
161.{
162. CvScalar avg = cvAvg(H_img, 0);
163. printf("%f, %f, %f, %f\n",
avg.val[0],avg.val[1],avg.val[2],avg.val[3]);
164. double d = 128 - avg.val[0];
165. avg.val[0] = d;
166. cvAddS(H_img, avg, H_img, 0);
167.
168.
169.}
170.
171.
172.
173.
174.
175./////////////////////////////////////////////////////////////////////// ////////////////////////////////////////
176.void hand_HSV()
177.{
178.
179. cvCvtColor(src2, hsv_image, CV_BGR2HSV);
180. //cvInRangeS (hsv_image, hsv_min, hsv_max, hsv_mask); 181. //cvShowImage( "hsv_msk", hsv_mask);
182.
183.
184.
185.
186. //方法2: 单独处理各个通道
187. cvSplit(hsv_image,H_img,S_img,V_img,0);
188.
189. //color_blance();
190. //cvMerge(H_img,S_img,V_img,0,hsv_image);
191. //cvShowImage( "色彩平衡后", hsv_image);
192. //cvShowImage( "H通道", H_img);
193.
194.
195.
196.
197.
198. //直方图均衡化(效果更差)
199. //cvEqualizeHist(H_img, H_img);
200.
201. //cvShowImage( "H通道_均衡化", H_img);
202.
203. //自适应
204. //cvAdaptiveThreshold(H_img, H_mask, 30, 0, 0, 3, 5); 205.
206. //cvShowImage( "H", H_img);
207. //cvShowImage( "S", S_img);
208. //cvShowImage( "V", V_img);
209.
210. //色相
211. cvInRangeS(H_img,cvScalar(0,0,0,0),cvScalar(H_low_max,0 ,0,0),H_mask);//红色区
212. cvInRangeS(H_img,cvScalar(256 -
H_high_min,0,0,0),cvScalar(256,0,0,0),H_mask1);//紫色区
213.
214. //饱和度
215. cvInRangeS(S_img,cvScalar(S_low,0,0,0),cvScalar(S_high, 0,0,0),S_mask); //中间区
216. //cvInRangeS(S_img,cvScalar(20,0,0,0),cvScalar(100,0,0,
0),S_mask1); //低饱和度
217.
218.
219.
220. //亮度
221. cvInRangeS(V_img,cvScalar(V_high,0,0,0),cvScalar(256,0, 0,0),V_mask);//高亮区
222. cvInRangeS(V_img,cvScalar(V_low,0,0,0),cvScalar(V_high, 0,0,0),V_mask1); //中间区
223. //cvInRangeS(V_img,cvScalar(150,0,0,0),cvScalar(250,0,0 ,0),V_mask2); //较亮区
224.
225.
226. //红黄, 和蓝紫的混合
227. cvOr(H_mask1, H_mask, H_mask, 0);
228.
229. //消除饱和度过低区域
230. cvAnd(H_mask,S_mask,H_mask,0);
231.
232. //cvShowImage( "饱和度过滤", H_mask);
233.
234. //消去过亮过暗区域
235. cvAnd(H_mask,V_mask1,H_mask,0);
236.
237. //cvShowImage( "亮度过滤", H_mask);
238.
239. //cvShowImage( "hsv_msk", H_mask);
240.
241.
242.
243. //补偿过亮区域
244. if(if_high_light) cvOr(H_mask,V_mask,H_mask,0);
245.
246. //cvShowImage( "补偿高光", H_mask);
247.
248. //cvShowImage( "曝光过度 V", V_mask);
249. //cvShowImage( "曝光补偿", S_mask);
250.
251.
252. //是否补偿曝光过度
253. hsv_mask = H_mask;
254.
255. //cvShowImage( "hsv_msk", H_mask);
256.
257.}
258.
259./////////////////////////////////////////////////////////////////////// ////////////////////////////////////////
260.
261.
262.//阀值化
263.IplImage* thd_src;
264.IplImage* thd_dst1;
265.IplImage* thd_dst2;
266.int thd_max = 255;
267.int thd_val = 100;
268.
269.void inti_threshold()
270.{
271. thd_src = cvCreateImage(sz,IPL_DEPTH_8U,1);
272. thd_dst1 = cvCreateImage(sz,IPL_DEPTH_8U,1);
273. thd_dst2 = cvCreateImage(sz,IPL_DEPTH_8U,1);
274.}
275.
276.void threshold()
277.{
278. cvCvtColor(src1, thd_src, CV_RGB2GRAY);
279. cvAdaptiveThreshold(thd_src, thd_dst1, thd_max,
CV_ADAPTIVE_THRESH_MEAN_C, CV_THRESH_BINARY, 3, 5);
280. cvThreshold(thd_src, thd_dst2, thd_val, thd_max, CV_THRESH_BINARY);
281.
282.
283. cvShowImage( "阀值前", thd_src);
284.
285. cvShowImage( "阀值化1", thd_dst1);
286. cvCreateTrackbar( "thd_max", "阀值化1", &thd_max, 256, 0); 287. cvShowImage( "阀值化2", thd_dst2);
288. cvCreateTrackbar( "thd_val", "阀值化2", &thd_val, 256, 0); 289.
290.}
291.
292.
293.
294./////////////////////////////////////////////////////////////////////// ////////////////////////////////////////
295.void resizeSrc()
296.{
297. double scale = 0.5;
298.
299.
300. //获得图像大小
301. sz = cvGetSize(src);
302. newSize.height = (int)(sz.height * scale);
303. newSize.width = (int)(sz.width * scale);
304.
305. src = cvCreateImage(newSize,IPL_DEPTH_8U,3);
306.
307. cvResize(src, src, CV_INTER_LINEAR);
308.
309.}
310.
311./////////////////////////////////////////////////////////////////////// ////////////////////////////////////////
312.
313.IplImage* smooth1;
314.IplImage* smooth2;
315.IplImage* smooth3;
316.IplImage* smooth4;
317.IplImage* smooth5;
318.
319.void reduce_noise()
320.{
321. //cvSmooth(hsv_mask, smooth1, CV_GAUSSIAN, 3, 0, 0, 0);
322. //cvSmooth(hsv_mask, smooth1, CV_MEDIAN, 3, 0, 0, 0);
323.
324. //cvSmooth(smooth1, smooth1, CV_BILATERAL, 3, 0, 0, 0);
325.
326. //cvDilate(hsv_mask, smooth1, 0 ,2);
327. //cvErode(smooth1 ,smooth2, 0, 2);
328.
329. cvMorphologyEx(hsv_mask, smooth1, 0, CV_SHAPE_RECT, CV_MOP_CLOSE, MopEx_value);
330. //cvMorphologyEx(smooth1, smooth2, 0, CV_SHAPE_RECT, CV_MOP_OPEN,
1);
331.
332. cvShowImage( "扩张腐蚀", smooth1);
333.
334. //cvSmooth(smooth2, smooth3, CV_MEDIAN, 9, 0, 0, 0);
335.
336. //cvShowImage( "平滑", smooth3);
337.}
338.
339.
340.
341./////////////////////////////////////////////////////////////////////// /////////////////////////////////////////
342.
343.//轮廓匹配
344.
345.IplImage* g_image = NULL;
346.IplImage* g_gray = NULL;
347.int g_thresh = 100;
348.CvMemStorage* g_storage = NULL;
349.CvMemStorage* g_storage1 = NULL;
350.CvMemStorage* g_storage2 = NULL;
351.
352.CvSeq* seqMidObj = 0;//塞选后的轮廓集合
353.int handNum = 0;
354.
355.
356./////////////////////////////////////////////////////////////////////// ////
357.
358.void hand_contours(IplImage* dst) {
359.
360. if( g_storage==NULL ) {
361. g_storage = cvCreateMemStorage(0);
362. g_storage1 = cvCreateMemStorage(0);
363. g_storage2 = cvCreateMemStorage(0);
364. } else {
365. cvClearMemStorage( g_storage );
366. cvClearMemStorage( g_storage1 );
367. cvClearMemStorage( g_storage2 );
368. }
369.
370.
371. int i = 0, j=0;
372. CvSeq* contours = 0;
373. CvSeq* contoursHead = 0;
374. CvSeq* p = NULL;
375. CvSeq* q = NULL;
376.
377.
378. seqMidObj = 0;
379. handNum = 0;
380.
381. //cvThreshold( g_gray, g_gray, g_thresh, 255, CV_THRESH_BINARY ); 382.
383. cvFindContours( dst, g_storage, &contours, sizeof(CvContour), CV_RETR_EXTERNAL);//只查找外轮廓
384. contoursHead = contours;//contours的头
385. //cvZero( g_gray );
386.
387.
388.
389. /*CvSeq* seqApprox;
390.
391. CvSeq* head;
392. int j=0;*/
393. /*for(p=contours;p != NULL; p = p->h_next){
394.
395. j++;
396.
397. seqApprox = cvApproxPoly(p, sizeof(CvContour),g_storage1, CV_POLY_APPROX_DP, 2, 0);
398. seqApprox = seqApprox->h_next;
399. if(p==contours) head = seqApprox;
400.
401. }
402. printf("total = %d\n ",j );
432.
433. if ( (double)contArea/imgArea < 0.015 ){continue;} 434.
435. //如果边界与窗口相连, 则排除
436. bound = cvBoundingRect(contours, 0);
437.
438. if( bound.x < dat
439. || bound.y < dat
440. || bound.x + bound.width + dat > newSize.width 441. || bound.y + bound.height + dat > newSize.height) 442. {
443. //printf(" %d, %d, %d, %d\n",bound.x, bound.y, bound.width, bound.height );
444. //cvRectangle(tmp3, cvPoint(bound.x, bound.y),cvPoint(bound.x + bound.width, bound.y +
bound.height),cvScalar(255,255,255,255),1,8,0);
445. continue;
446. }
447.
448. //建立轮廓链表
449. q = p;
450. //p = cvCloneSeq(contours, g_storage2);
451. p = contours;
452.
453. if(q == NULL){
454. seqMidObj = p;
455. //p->h_next = NULL;
456. //p->h_prev = NULL;
457. //printf("第1个!");
458. }else{
459. q->h_next = p;
460. p->h_prev = q;
461. //printf("1个!");
462. }
463. //j++;
464. handNum++;
465.
466. }
467.
468. //printf("找到轮廓: %d 个塞选: %d 个\n", i,j);
469.
470.
471.
472. if(seqMidObj){
473. seqMidObj->h_prev = NULL;
474. p->h_next = NULL;
475. }
476. if(handNum>0) printf("找到手: %d ",handNum);
477. //CvSeq* seqMidObj_head = seqMidObj;
478.
479.
480.
481.
482. cvZero( tmp3 );
483. if( seqMidObj )cvDrawContours( tmp3, seqMidObj,
cvScalarAll(255),cvScalar(255,0,0,0),1);//绘制轮廓
484.
485. //cvShowImage( "轮廓塞选", tmp3);
486.
487.
488.}
489./////////////////////////////////////////////////////////////////////// //////////////////////////////////////
490.
491.IplImage* tmp_img = 0;
492.CvMemStorage* storage_tmp = 0;
493.
494.CvSeq* handT = 0;
495.CvSeq* handT1 = 0;
496.CvSeq* handT2 = 0;
497.
498.
499.int handTNum = 10;//10个模板
500.
501.char *tmp_names[] =
{"1.bmp","2.bmp","3.bmp","4.bmp","5.bmp","6.bmp","7.bmp","8.bmp","9.bmp ","10.bmp"};
502.char *num_c[] = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10"}; 503.
504.
505.
506./////////////////////////////////////////////////////
507.
508.//载入模板的轮廓
509.void init_hand_template()
510.{
511.
512. storage_tmp = cvCreateMemStorage(0);
513.
514. int i = 0;
515. for(i=0; i 516. 517. tmp_img = cvLoadImage(tmp_names[i], CV_LOAD_IMAGE_GRAYSCALE); 518. if(!tmp_img){ 519. printf("未找到文件: %s\n", tmp_names[i]); 520. continue; 521. } 522. //cvShowImage("载入模板", tmp_img); 523. handT1 = handT2; 524. cvFindContours( tmp_img, storage_tmp, &handT2, sizeof(CvContour), CV_RETR_EXTERNAL); 525. 526. if(handT2){ 527. printf("载入模板: %s 成功!\n",tmp_names[i]); 528. if(handT1 == NULL){ 529. printf("载入第一个模板!\n"); 530. handT = handT2; 531. }else{ 532. handT2->h_prev = handT1; 533. handT1->h_next = handT2; 534. } 535. 536. } 537. 538. } 539. 540. 541.} 542./////////////////////////////////////////////////////////////////////// ////////////////////////////////////// 543. 544.bool if_match_num = false; 545.int match_num = -1; 546. 547.//模板匹配手 548.void hand_template_match(CvSeq* handT, CvSeq* hand){ 549. int i=0; 550. int kind = -1; 551. double hu = 1; 552. 553. double hutmp; 554. CvSeq* handp = handT; 555. int method = CV_CONTOURS_MATCH_I1; 556. 557. match_num = 0; 558. 559. if(handT==NULL){return;printf("handT==NULL!\n");} 560. if(hand==NULL){return;printf("hand==NULL!\n");} 561. 562. for(i=0; i 563. hutmp = cvMatchShapes(handp, hand, method, 0); 564. handp = handp->h_next; 565. 566. //找到hu矩最小的模板 567. if(hu > hutmp){ 568. hu = hutmp; 569. kind = i+1; 570. } 571. 572. //printf("%f ", hu); 573. } 574. 575. //显示匹配结果 576. if(hu<((double)Hmatch_value)/100){ 577. printf("匹配模板: %d (%f)", kind, hu); 578. match_num = kind; 579. if_match_num = true; 580. }else{ 581. if_match_num = false;