Ở hình trên, mỗi gương mặt đều được thử nghiệm qua 3 mô hình màu nhưng ảnh kết quả chỉ lấy ảnh tốt nhất từ 1 trong 3 mô hình. Vậy thì 3 kết quả trên từ những mô hình nào chúng ta sẽ xem tiếp biểu đồ họ thống kê.
Gương mặt nam đầu tiên chính là cột màu cam, gương mặt nữ là cột màu xanh lá và gương mặt nam cuối là màu xanh lam.
Nhìn vào hình 3.5, chúng ta thấy số pixel phát hiện màu da nhiều nhất là của mô hình màu RGB với 15387 pixels, tiếp theo đó mới là YCbCr là 15321 pixels và thấp nhất là HSV : 15255 pixels. Suy ra tấm hình kết quả của hình đầu tiên là từ mô hình màu RGB.
Tương tự với 2 hình kế tiếp thì số pixel của mô hình màu YCbCr lại cao hơn 2 mô hình kia nên ta có thể kết luận kết quả phát hiện màu da của 2 khung hình trên là từ mô hình màu YCbCr.
Qua tài liệu trên ta có thể thấy rằng trong 3 kết quả tốt nhất được trả về thì đã có 2 kết quả là từ mô hình màu YCbCr và 1 từ RGB. Cho nên đối với phương pháp phát hiện màu da này, sinh viên sẽ dùng mô hình màu YCbCr hay còn gọi là YUV để thực hiện cho đề tài của mình.
3.2.2. Phương pháp trừ nền
Phương pháp trừ nền là phương pháp dùng để phát hiện chuyển động. Cách thức hoạt động của phương pháp trừ nền là lấy khung ảnh đầu tiên làm ảnh nền và đem đi so sánh với các khung ảnh sau để tìm ra điểm khác biệt giữa 2 khung ảnh. Chính điểm khác biệt đó thể hiện sự chuyển động. Ở đây sinh viên sử dụng phương pháp trừ nền MOG.
Hình 3.6. Ảnh trái ( ảnh nền) trừ với ảnh đầu vào ( ảnh phải)
Nhưng vì ảnh đọc từ camera hoặc lấy từ video đều thuộc mô hình màu RGB mà câu lệnh trừ nền trong ngôn ngữ C lại yêu cầu ảnh đầu vào chỉ có 1 kênh màu. Trong
xử lý ảnh, ảnh có một kênh màu đó là ảnh xám và ảnh nhị phân. Nên lúc này sinh viên sẽ thực hiện biến đổi ảnh cho phù hợp với yêu cầu của lệnh.
Hình 3.7. Biến đổi 2 ảnh mô hình màu RGB thành ảnh xám
Sau đó sinh viên dùng lệnh trừ nền để tìm ra điểm khác biệt của 2 ảnh xám trên và biến đổi kết quả trừ nền sang ảnh nhị phân.
Hình 3.9. Ảnh nhị phân của kết quả trừ nền
Kết quả của ảnh nhị phân thể hiện vùng tay chuyển động so với ảnh nền và có sự xuất hiện của tấm ván bên phải. Trong ảnh nhị phân trên, vùng sinh viên mong muốn là vùng tay, còn vùng mặt do trong quá trình thực hiện vẫn cử động nên đã xuất hiện kèm theo sự có mặt của miếng ván mà sinh viên không mong muốn.
Chính vì trong xử lý ảnh ở thời gian thực luôn xuất hiện những đối tượng mà sinh viên không mong muốn ( vùng nhiễu) cho nên sinh viên sẽ sử dụng hai phép toán hình thái học ( xói mòn và giãn nở) để thu nhỏ kích thước của vùng nhiễu.
Hình 3.10. Ảnh nhị phân sau khi qua xói mòn
Xói mòn sẽ thu nhỏ tất cả vùng trắng trên ảnh nhị phân nhằm xóa bỏ những vùng nhiễu nhỏ. Nhưng vì vùng bàn tay là đối tượng mong muốn cũng bị xói mòn nên lúc này sinh viên sẽ giãn nở nó.
Kết quả trước và sau khi sử dụng 2 phép hình thái học xói mòn và giãn nở để lọc nhiễu ảnh.
Hình 3.12. Kết quả lọc nhiễu
Hình 3.13. Kết quả của phương pháp trừ nền
3.2.3. Phương pháp màu da
Ảnh trước tiên được đưa về một không gian màu thích hợp, sau đó, tùy vào phạm vi màu của màu da trong không gian màu đó mà các pixel có giá trị tương tự với màu da tay người được phân đoạn. Sinh viên sẽ dùng không gian màu YUV trong phương pháp màu da.
Với không gian màu YcbCr (YUV):
- Việc chuyển đổi và tách màu dễ dàng hơn nhiều so với các phương pháp khác. - Có thể được áp dụng cho những hình ảnh màu phức tạp với ánh sáng không đồng đều. Do đó, sinh viên chọn sử dụng không gian màu YUV cho phương pháp màu da.
Hình 3.14. Kết quả đổi mô hình màu ảnh input từ RGB sang YUV
Sau đó sinh viên sẽ chuyển mô hình màu YUV sang nhị phân và sử dụng phép lọc xói mòn và giãn nở để lọc ảnh ( vì ảnh nhị phân dùng phương thức màu da lúc này xuất hiện những điểm trắng không cần thiết).
Hình 3.15. Ảnh nhị phân dùng phương thức màu da
Hình 3.16. Ảnh nhị phân sau khi qua phép xói mòn
Lúc này nhiễu đã bị xóa bỏ gần hết nhưng cũng làm ảnh hưởng đến vùng tay. Vì vậy phải qua thêm bước lọc giãn nở để đưa vùng tay trở về trạng thái đầy đặn.
Hình 3.17. Kết quả sau khi giãn nở ảnh
3.2.4. Phép AND
Phép AND trong xử lý ảnh khá giống với cổng logic and. Vị trí các pixel tương ứng của 2 ảnh nếu có cùng màu trắng khi AND với nhau sẽ cho ra ảnh mới có vị trí pixel tương ứng với 2 ảnh trên là màu trắng. Còn ngược lại, nếu một ảnh có pixel màu trắng, còn pixel tương ứng của ảnh bên kia màu đen thì sau khi AND sẽ tạo ra ảnh mới có giá trị pixel tại đó là màu đen.
Sinh viên sẽ AND 2 ảnh nhị phân của trừ nền và màu da lại để lọc phần nhiễu của cả 2 ảnh.
Hình 3.18. AND ảnh nhị phân trừ nền và màu da
Hình 3.20. AND để xóa nhiễu
Ảnh sau khi AND cũng chưa thật sự tốt lắm vì còn vùng mặt vẫn còn nhiễu cho nên sinh viên sẽ thực hiện xói mòn và giãn nở ảnh.
Hình 3.21. Xói mòn ảnh
Hình 3.22. Giãn nở ảnh
Kết luận: Để có một ảnh nhị phân vùng tay rõ nét và ít bị nhiễu nhất có thể thì chúng ta sẽ phải dùng tới cả 2 phương pháp trừ nền và màu da để lọc nhiễu nhau và qua thêm nhiều bước lọc nhiễu hình thái học xói mòn và giãn nở.
Hình 3.23. Quá trình phân đoạn bàn tay
nhiễu không mong muốn này, ta sử dụng phương pháp gán nhãn các đốm màu. Phương pháp này sẽ gán nhãn cho mỗi pixel trong ảnh chứa đối tượng.
Các pixel có giá trị nhãn như nhau tức là có kết nối với nhau và được xem như là một đốm màu. Thông tin về kích thước của các hình chữ nhật nhỏ nhất bao quanh các đốm màu có thể dùng để loại bỏ nhiễu và chọn ra vùng tay trong ảnh chứa đối tượng. Trong khuôn khổ của khóa luận, vùng tay bên phải được xem như là đối tượng chính của hệ thống.
Hình 3.24. Vùng tay lúc này đã được gán nhãn
Sau khi có được vùng tay, ta cần loại bỏ phần cẳng tay để việc xác định tọa độ ngón tay được chính xác. Sử phương pháp chuyển đổi khoảng cách lên ảnh thu được ở bước trước, ta sẽ thu được ảnh một ảnh tương tự như khung xương của bàn tay. Tâm của bàn tay được xem như là điểm có khoảng cách lớn nhất tới điểm 0 gần nhất. Khoảng cách này cũng được xem như là bán kính của vòng tròn nội tiếp bàn tay.
Vùng cẳng tay được xác định bằng cách vẽ một vòng tròn từ tâm bàn tay với bán kính R = 50, các điểm nằm dưới vòng tròn này được xem như là vùng ngoài vùng bàn tay và được lọc bỏ.
Hình 3.25. Biến đổi vùng bàn tay thành ảnh khung xương
Hình 3.27. Loại bỏ phần nằm dưới hình tròn
Hình 3.28. Kết quả vùng bàn tay sau các bước xử lý trên
3.4. Số lượng ngón tay
3.4.2. Vùng lồi và điểm lõm trong xử lý ảnh
Tọa độ ngón tay và số lượng ngón là các yếu tố quan trọng cho hệ thống. Sử dụng phương pháp xây dựng vùng lồi (convex hull) và điểm lõm (convexity defect) cho vùng tay vừa tìm được ở trên, ta có thể xác định được số lượng ngón tay đang mở và tọa độ của chúng.
Hình 3.31. Vùng lồi và điểm lõm
Vì phương pháp xây dựng vùng lồi (convex hull) và điểm lõm (convexity defect) chỉ nhận ảnh nhị phân cho nên lúc này ta sẽ dùng hình 3.28 để tìm điểm lồi và lõm, sau đó vẽ các điểm đó trên ảnh màu hình 3.29.
Trong hình 3.32, các điểm màu đỏ là điểm lồi, các điểm màu xanh nhạt là điểm lõm, tâm bàn tay là xanh lá và điểm dài nhất của bàn tay ( ngón giữa ) là xanh dương.
3.4.3. Loại bỏ một số điểm lõm không cần thiết và phân biệt số ngón tay
Hình 3.33. Độ sâu của điểm lõm và 2 điểm lõm thừa
Hai điểm lõm mà mũi tên đỏ chỉ vào là 2 điểm thừa không cần tới. Để loại bỏ nó sinh viên sẽ đặt một cái ngưỡng độ sâu. Ở đây sinh viên coi đường kẻ màu trắng chính là độ sâu của từng điểm lõm. Và 2 điểm không cần tới có độ sâu khá ngắn và chỉ cần độ sâu 2 điểm trên thấp hơn ngưỡng độ sâu đặt ra thì sẽ bị loại.
Hình 3.34. Kết quả sau khi loại bỏ điểm lõm thừa
Số lượng các ngón tay bằng số lượng các vùng lõm cộng với 1. Khi bàn tay nắm lại hoặc chỉ có 1 ngón tay được mở ra thì không có vùng lõm được phát hiện. Trong trường hợp này, khoảng cách từ tâm lòng bàn tay đến điểm dài nhất của bàn tay ở
Sinh viên sẽ tiếp tục đặt ngưỡng độ dài, nếu khoảng cách này nhỏ hơn ngưỡng đặt ra thì bàn tay được xem như nắm lại, còn dài hơn ngưỡng đặt ra là trạng thái 1 ngón. Điểm cao nhất trên vỏ lồi được giả định là đại diện cho tọa độ ngón tay được sử dụng của bàn tay.
Hình 3.35. Đường màu vàng khi nắm lại ngắn hơn so với trạng thái 1 ngónCác kết quả phân loại trạng thái bàn tay ( xác định số ngón tay) Các kết quả phân loại trạng thái bàn tay ( xác định số ngón tay)
Hình 3.36. Bàn tay nắm lại – 0 có ngón nào
Hình 3.38. 2 ngón tay đưa ra
Hình 3.39. 3 ngón tay đưa ra
Hình 3.41. 5 ngón tay đưa ra
3.5. Xác định tọa độ ngón tay và vẽ
Ở bước số lượng ngón tay, sinh viên đã xác định được tọa độ điểm của ngón tay dài nhất và nó cũng chính là tọa độ dùng để vẽ. Bước này chỉ cần lấy tọa độ điểm dài nhất ra, lưu giá trị tọa độ cũ của nó, xét điều kiện ngón tay và vẽ từ giá trị tọa độ điểm cũ tới giá trị tọa độ điểm mới là sẽ hình thành nét vẽ.
CHƯƠNG 4: KẾT QUẢ THỰC NGHIỆM4.1. Thực nghiệm đánh giá độ chính xác khi đếm số ngón tay 4.1. Thực nghiệm đánh giá độ chính xác khi đếm số ngón tay
Trạng thái Số lần đúng Tỉ lệ chính xác 0 15 75% 1 16 80% 2 18 90% 3 18 90% 4 20 100% 5 20 100% Tổng số thử nghiệm : 20 lần
Bảng đánh giá độ chính xác trong việc xác định số lượng ngón tay
4.2. Kết quả vẽ tranh trên không trung dùng xử lý ảnh
Khi đã đếm được số ngón tay, lúc này sinh viên thực hiện sẽ xét trạng thái ngón tay để vẽ:
- Trường hợp 1 ngón thì bắt đầu vẽ tại điểm đầu ngón tay dài nhất. - Trường hợp 5 ngón tay sẽ xóa đi nét vẽ.
CHƯƠNG 5: KẾT LUẬN VÀ ĐỊNH HƯỚNG ĐỀ TÀI5.1. Kết quả đạt được 5.1. Kết quả đạt được
- Hoàn thành được khóa luận như mong muốn.
- Kỹ năng viết code gọi chương trình con cũng đã tiến bộ sau khóa luận trên.
5.2. Hạn chế
- Nét vẽ vẫn còn bị đứt nét và giai đoạn tiền xử lý ảnh cũng chưa tốt lắm. - Đôi khi đếm số ngón tay sai.
5.3. Hướng phát triển đề tài
PHỤ LỤC Code chương trình #include "opencv/cv.h" #include "opencv/highgui.h" #include "CblobLabeling.h" #include <opencv2/core/core_c.h> #include <opencv2/imgproc/imgproc.hpp> #include <iostream> #include <fstream> using namespace std;
void background_subtraction(IplImage*src, IplImage*dst, IplImage*bg); void skin_detection(IplImage*src, IplImage*dst);
void hand_detection(IplImage*src, IplImage*dst, IplImage*mask);
void fingers_counting(IplImage* src, IplImage* dst_hand, IplImage* dst); unsigned char num_fingers;
// pt is the center
// pt1 is the top left of blob // pt2 is the farthest point
CvPoint pt, pt1, pt2, pt2_f, pt2_l; #define no_fingers 65
#define threshold_fingers 25 //Đk sáng chói
//int minY = 53, maxY = 196, minU = 106, maxU = 140, minV = 128, maxV = 160; //Đk của video 1
//int minY = 170, maxY = 255, minU = 0, maxU = 140, minV = 119, maxV = 160; //Đk tại nhà
int minY = 190, maxY = 255, minU = 0, maxU = 140, minV = 119, maxV = 160; const char* window0 = "setupYUV";
void main() {
IplImage* frm1;
IplImage* frm_resize = cvCreateImage(cvSize(cvQueryFrame(capture)- >width / 2, (cvQueryFrame(capture))->height / 2), 8, 3);
IplImage* frm_bg_resize = cvCreateImage(cvGetSize(frm_resize), 8, 3); IplImage* frm_bg = cvCreateImage(cvGetSize(frm_resize), 8, 1);
IplImage* frm_bg_subtraction = cvCreateImage(cvGetSize(frm_resize), 8, 1); IplImage* frm_skin_detection = cvCreateImage(cvGetSize(frm_resize), 8, 1); IplImage* frm_hand_detection = cvCreateImage(cvGetSize(frm_resize), 8, 1); IplImage* hand = cvCreateImage(cvGetSize(frm_resize), 8, 3);
IplImage* frm_draw = cvCreateImage(cvGetSize(cvQueryFrame(capture)), 8, 3); IplImage* frm_draw_binary = cvCreateImage(cvGetSize(cvQueryFrame(capture)), 8, 1); frm1 = cvQueryFrame(capture); cvSetZero(frm_draw); cvSetZero(frm_draw_binary); cvResize(frm1, frm_bg_resize, 1); //cvFlip(frm_bg_resize, frm_bg_resize, 1); //cvShowImage("Nen", Old_frame); cvCvtColor(frm_bg_resize, frm_bg, CV_RGB2GRAY); cvSmooth(frm_bg, frm_bg, CV_GAUSSIAN, 5); //cvShowImage("Nen2", Old_frame_Gray); //save fps ofstream myfile;
myfile.open("C:\\Users\\Huynh Tan Cuong\\Desktop\\fps.csv"); while (1)
{
int64 now, then;
float ticks = cvGetTickFrequency()*1.0e6; then = cvGetTickCount();
// get frm from CAM
frm = cvQueryFrame(capture); if (!frm) break;
// flip frame
//cvFlip(frm, frm, 1);
cvShowImage("Webcam", frm); // resize 0.5 to increase speed cvResize(frm, frm_resize, 1); // Background Subtraction
background_subtraction(frm_resize, frm_bg_subtraction, frm_bg); //cvShowImage("Background Subtraction", frm_bg_subtraction); // skin detection
skin_detection(frm_resize, frm_skin_detection);
//cvShowImage("Skin Detection", frm_skin_detection); // hand detection
cvAnd(frm_skin_detection, frm_bg_subtraction, frm_hand_detection); cvShowImage("Hand Detection", frm_hand_detection);
// HAND
hand_detection(frm_resize, hand, frm_hand_detection); //
//finger counting
fingers_counting(frm_hand_detection, hand, frm); //cvShowImage("Webcam draw", frm);
//cvShowImage("Draw hand", hand); //Draw if (num_fingers == 1) { cvLine(frm_draw, pt2_l, pt2_f, CV_RGB(255, 0, 0), 5); cvLine(frm_draw_binary, pt2_l, pt2_f, CV_RGB(255, 255, 255), 5); } if (num_fingers == 5) { cvSetZero(frm_draw); cvSetZero(frm_draw_binary); } cvCopy(frm_draw, frm, frm_draw_binary); pt2_l = pt2_f; //printf("pointpt2_l: %d\n", pt2_l); cvShowImage("End", frm); /*cvShowImage("End2", frm_draw); cvShowImage("End3", frm_draw_binary);*/ // now = cvGetTickCount();
float frame_time = (now - then) / ticks; float fps = 1 / frame_time;
if (c >= 0) break; } cvReleaseCapture(&capture); cvDestroyAllWindows(); myfile.close(); }
void background_subtraction(IplImage*src, IplImage*dst, IplImage*bg) {
// khai bao cac bien trung gian de su dung
IplImage* tmp1 = cvCreateImage(cvSize(src->width, src->height), 8, 1); IplImage* tmp2 = cvCreateImage(cvSize(src->width, src->height), 8, 1); IplImage* tmp3 = cvCreateImage(cvSize(src->width, src->height), 8, 1); //Background Subtraction cvCvtColor(src, tmp1, CV_RGB2GRAY); //cvShowImage("Background Subtraction 1", tmp1); cvAbsDiff(tmp1, bg, tmp2); cvSmooth(tmp2, tmp2, CV_GAUSSIAN, 15); //cvShowImage("Background Subtraction 2", tmp2); cvThreshold(tmp2, tmp3, 30, 255, CV_THRESH_BINARY); //cvShowImage("Background Subtraction 3", tmp3); cvErode(tmp3, dst, cvCreateStructuringElementEx(3, 3, 0, 0, CV_SHAPE_ELLIPSE, 0)); //cvShowImage("Background Subtraction 4", dst); cvDilate(dst, dst, cvCreateStructuringElementEx(5, 5, 0, 0, CV_SHAPE_ELLIPSE, 0)); //cvShowImage("Background Subtraction 5", dst); }
void skin_detection(IplImage*src, IplImage*dst) {
// khai bao thong so trackbar dieu chinh nguong tmp1 cvNamedWindow(window0);
cvCreateTrackbar("MinY", window0, &minY, 255); cvCreateTrackbar("MaxY", window0, &maxY, 255); cvCreateTrackbar("MinU", window0, &minU, 140); cvCreateTrackbar("MaxU", window0, &maxU, 140); cvCreateTrackbar("MinV", window0, &minV, 140); cvCreateTrackbar("MaxV", window0, &maxV, 160); // khai bao bien trung gian
IplImage* tmp1 = cvCreateImage(cvSize(src->width, src->height), 8, 3); IplImage* tmp2 = cvCreateImage(cvSize(src->width, src->height), 8, 1); IplConvKernel* Element1;
IplConvKernel* Element2; //SkinColor
//cvShowImage("Skin Detection S0", src); cvCvtColor(src, tmp1, CV_RGB2YUV); //cvShowImage("Skin Detection S1", tmp1);
cvInRangeS(tmp1, cvScalar(minY, minU, minV), cvScalar(maxY, maxU, maxV), tmp2);
//cvShowImage("Skin Detection S2", tmp2);
Element1 = cvCreateStructuringElementEx(5, 5, 0, 0, CV_SHAPE_ELLIPSE, 0);
Element2 = cvCreateStructuringElementEx(7, 7, 0, 0, CV_SHAPE_ELLIPSE, 0); cvErode(tmp2, dst, Element1); //cvShowImage("Skin Detection S3", dst); cvDilate(dst, dst, Element2); //cvShowImage("Skin Detection S4", dst); }
void hand_detection(IplImage*src, IplImage*dst, IplImage*mask) {
cvErode(mask, mask, cvCreateStructuringElementEx(5, 5, 3, 3, CV_SHAPE_ELLIPSE, 0));
cvDilate(mask, mask, cvCreateStructuringElementEx(7, 7, 6, 6, CV_SHAPE_ELLIPSE, 0)); // BLOB LABELING CBlobLabeling blob; blob.SetParam(mask, 40); // area blob.DoLabeling(); // much time here
blob.BlobSmallSizeConstraint(50, 50); // size min blob.BlobBigSizeConstraint(200, 250); // size max int Hand_Blob_Index = 0;
//only select right most blob
for (int i = 0; i < blob.m_nBlobs; i++) {
if ((blob.m_recBlobs[Hand_Blob_Index].x < blob.m_recBlobs[i].x) && (blob.m_recBlobs[Hand_Blob_Index].y > blob.m_recBlobs[i].y)) { Hand_Blob_Index = i; } } // cut image