当前位置:   article > 正文

C++视觉开发 四.手势识别_c++动作识别

c++动作识别

本章记录传统手势识别,在表示0-5六个数值时的识别问题。例如识别剪刀石头布,手势,以及其表示的动作。在识别时将手势中的凹陷区域称为凸缺陷,其个数作为识别的重要依据。

需要注意,在凸缺陷个数为0时,无法识别个数,需要引入凸包的概念,后面会讲到。实际过程中,算法获取凸缺陷时,会获取到细小的凸缺陷,需要将细小的凸缺陷屏蔽。

一.理论基础

1.凸包

凸包(Convex Hull)是(物体最外层)给定点集的最小凸多边形或多面体,包含所有点,并确保多边形内部任意两点的连线也在内部。换句话说,凸包指的是完全包含原有轮廓,并且仅由轮廓上的点构成的多边形。在凸包内可以想象成用橡皮筋围住一组钉在板上的钉子,橡皮筋绷紧后包围的区域即为凸包,任意三个点构成的面向内部的角的角度都小于180°。凸包在计算几何、计算机视觉、图形学等领域有广泛应用。

凸包示意图 

重要函数:

(1)cv::convexHull 计算凸包

功能:计算二维点集的凸包,该函数通常用于图像处理和计算几何中,以确定形状的边界。

函数语法:

  1. void cv::convexHull(
  2. InputArray points,
  3. OutputArray hull,
  4. bool clockwise=false,
  5. bool returnPoints=true
  6. );
参数含义
points输入的二维点集,可以是 std::vector<cv::Point>cv::Mat 类型。
hull

输出的凸包结果,类型与输入点集相同

如果 returnPoints 为 true,则返回凸包上的点;

如果为 false,则返回凸包点集的索引。

clockwise

指定输出的凸包点的顺序。

如果为 true,则按顺时针方向排序;

否则按逆时针方向排序。默认值为 false

returnPoints

指定输出结果的类型。

如果为 true,则返回凸包上的点;

如果为 false,则返回凸包点集的索引。默认值为 true

示例代码:

  1. vector<vector<cv::Point>> contours;
  2. cv::findContours(binary, contours, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);
  3. vector<cv::Point> hull;
  4. cv::convexHull(contours[0], hull);

(2)cv::polylines 绘制多边形

功能:可以用来绘制一组点连接形成的多边形轮廓。

函数语法:

  1. void cv::polylines(
  2. InputOutputArray image,
  3. InputArrayOfArrays hull,
  4. bool isClosed,
  5. const Scalar& color,
  6. int thickness=1,
  7. int lineType=LINE_8,
  8. int shift=0
  9. );
参数含义
image输入输出参数,表示要绘制的图像。
pts输入参数,表示一个或多个点集的数组,每个点集表示一条多边形线。
isClosed

输入参数,表示多边形是否封闭

如果为 true,则绘制一个封闭的多边形;否则绘制一条开放的多边形线。

color输入参数,表示线条的颜色。
thickness(可选)输入参数,表示线条的粗细,默认为 1。
lineType(可选)输入参数,表示线条的类型 LINE_8, LINE_4等。
shift(可选)输入参数,表示点坐标的小数点位数,默认为 0。

(3)应用示例:绘制图像的凸包

  1. #include <opencv2/opencv.hpp>
  2. #include <iostream>
  3. int main() {
  4. // 读取并绘制原始图像
  5. cv::Mat o = cv::imread("hand.bmp");
  6. if(o.empty()) {
  7. std::cerr << "Could not open or find the image!" << std::endl;
  8. return -1;
  9. }
  10. cv::imshow("original", o);
  11. // 提取轮廓
  12. cv::Mat gray;
  13. cv::cvtColor(o, gray, cv::COLOR_BGR2GRAY);
  14. cv::Mat binary;
  15. cv::threshold(gray, binary, 127, 255, cv::THRESH_BINARY);
  16. std::vector<std::vector<cv::Point>> contours;
  17. std::vector<cv::Vec4i> hierarchy;
  18. cv::findContours(binary, contours, hierarchy, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE);
  19. // 寻找凸包,得到凸包的角点
  20. std::vector<cv::Point> hull;
  21. cv::convexHull(contours[0], hull);
  22. // 绘制凸包
  23. cv::polylines(o, hull, true, cv::Scalar(0, 255, 0), 2);
  24. // 输出凸包的角点
  25. for (const auto& point : hull) {
  26. std::cout << point << std::endl;
  27. }
  28. // 显示凸包
  29. cv::imshow("result", o);
  30. cv::waitKey(0);
  31. cv::destroyAllWindows();
  32. return 0;
  33. }

2.凸缺陷

概念:凸缺陷(Convexity Defects)是指形状的凸包与形状之间的区域。这些缺陷表示在形状的边界上向内凹陷的部分。具体来说,凸缺陷是形状的轮廓与其凸包之间的点,这些点与凸包形成的线段是形状的凹陷部分。

通常情况下,使用如下四个特征值来表示凸缺陷:
1.起点:该特征值用于说明当前凸缺陷的起点位置。需要注意的是,起点值用轮廓索引表示。也就是说,起点一定是轮廓中的一个点,并且用其在轮廓中的序号来表示。例如,点A是凸缺陷1的起点。
2.终点:该特征值用于说明当前凸缺陷的终点位置。该值也是使用轮廓索引表示的。例如,图中的点B是凸缺陷1的终点。
3.轮廓上距离凸包最远的点:例如,点C是凸缺陷1中的轮廓上距离凸包最远的点。
4.最远点到凸包的近似距离:例如,距离D是凸缺陷1中的最远点到凸包的近似距离。


(1)cv::convexityDefects 计算凸缺陷

功能:计算输入轮廓与其凸包之间的凸缺陷,返回每个凸缺陷的起点、终点、最远点和深度

函数语法:

  1. void cv::convexityDefects(
  2. InputArray contour,
  3. InputArray convexhull,
  4. OutputArray convexityDefects);
参数含义
contour输入的轮廓点集。
convexhull输入的凸包点的索引。
convexityDefects输出的凸缺陷。

示例代码:

  1. // 计算凸缺陷
  2. std::vector<cv::Vec4i> defects;
  3. cv::convexityDefects(contours[0], hull, defects);

(2)cv::line 绘制线条

功能:在图像 img 上绘制一条从 pt1pt2 的线条,线条的颜色、粗细和类型可以由相应的参数控制。

函数语法:

  1. void cv::line(
  2. Mat& img,
  3. Point pt1,
  4. Point pt2,
  5. const Scalar& color,
  6. int thickness=1,
  7. int lineType=LINE_8,
  8. int shift=0
  9. );
参数含义
img输入/输出图像,在图像上绘制线条。
pt1线条的起点,类型为 cv::Point
pt2线条的终点,类型为 cv::Point
color

线条的颜色,类型为 cv::Scalar。

例如,cv::Scalar(0, 0, 255) 表示红色。

thickness(可选)线条的粗细,默认为 1。
lineType(可选)线条的类型。
shift点坐标的小数位数,默认为 0。

(3)cv::circle 绘制圆

功能:在图像 img 上绘制一个以 center 为圆心、半径为 radius 的圆,圆的颜色、线条粗细和类型可以由相应的参数控制。

函数语法:

  1. void cv::circle(
  2. Mat& img,
  3. Point center,
  4. int radius,
  5. const Scalar& color,
  6. int thickness=1,
  7. int lineType=LINE_8,
  8. int shift=0
  9. );

参数含义
img输入输出图像,在图像上绘制圆。
center圆心的坐标,类型为 cv::Point
radius圆的半径,类型为 int
color

圆的颜色,类型为 cv::Scalar

例如,cv::Scalar(255, 0, 0) 表示蓝色。

thickness圆的线条粗细。如果为负值,如 -1,则绘制填充的圆。
lineType线条的类型。
shift点坐标的小数位数,默认为 0。

(4)应用示例1:一个图像里有单独轮廓

  1. #include <opencv2/opencv.hpp>
  2. #include <iostream>
  3. int main() {
  4. // 读取图像
  5. cv::Mat img = cv::imread("hand.bmp");
  6. if (img.empty()) {
  7. std::cerr << "Could not open or find the image!" << std::endl;
  8. return -1;
  9. }
  10. cv::imshow("original", img);
  11. // 转换为灰度图像
  12. cv::Mat gray;
  13. cv::cvtColor(img, gray, cv::COLOR_BGR2GRAY);
  14. // 二值化
  15. cv::Mat binary;
  16. cv::threshold(gray, binary, 127, 255, cv::THRESH_BINARY);
  17. // 查找轮廓
  18. std::vector<std::vector<cv::Point>> contours;
  19. std::vector<cv::Vec4i> hierarchy;
  20. cv::findContours(binary, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);
  21. // 计算凸包
  22. std::vector<int> hull;
  23. cv::convexHull(contours[0], hull, false);
  24. // 计算凸缺陷
  25. std::vector<cv::Vec4i> defects;
  26. cv::convexityDefects(contours[0], hull, defects);
  27. std::cout << "defects=\n";
  28. for (const auto& defect : defects) {
  29. std::cout << defect << std::endl;
  30. }
  31. // 绘制凸缺陷
  32. for (size_t i = 0; i < defects.size(); i++) {
  33. int s = defects[i][0]; // 起点
  34. int e = defects[i][1]; // 终点
  35. int f = defects[i][2]; // 远点
  36. // int d = defects[i][3]; // 距离(这里未使用)
  37. cv::Point start = contours[0][s];
  38. cv::Point end = contours[0][e];
  39. cv::Point far = contours[0][f];
  40. cv::line(img, start, end, cv::Scalar(0, 0, 255), 2);
  41. cv::circle(img, far, 5, cv::Scalar(255, 0, 0), -1);
  42. }
  43. // 显示结果
  44. cv::imshow("result", img);
  45. cv::waitKey(0);
  46. cv::destroyAllWindows();
  47. return 0;
  48. }

 结果如图:

(5)应用示例2: 一个图像里有很多轮廓

需要对每一个轮廓分别计算凸包和凸缺陷,并将结果绘制在同一张图像上。

  1. #include <opencv2/opencv.hpp>
  2. #include <iostream>
  3. #include <vector>
  4. void processImage(cv::Mat& img) {
  5. // 转换为灰度图像
  6. cv::Mat gray;
  7. cv::cvtColor(img, gray, cv::COLOR_BGR2GRAY);
  8. // 二值化
  9. cv::Mat binary;
  10. cv::threshold(gray, binary, 127, 255, cv::THRESH_BINARY);
  11. // 查找轮廓
  12. std::vector<std::vector<cv::Point>> contours;
  13. std::vector<cv::Vec4i> hierarchy;
  14. cv::findContours(binary, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);
  15. // 遍历每个轮廓
  16. for (size_t i = 0; i < contours.size(); i++) {
  17. // 计算凸包
  18. std::vector<int> hull;
  19. cv::convexHull(contours[i], hull, false);
  20. // 计算凸缺陷
  21. std::vector<cv::Vec4i> defects;
  22. cv::convexityDefects(contours[i], hull, defects);
  23. // 绘制凸包
  24. std::vector<cv::Point> hullPoints;
  25. for (size_t j = 0; j < hull.size(); j++) {
  26. hullPoints.push_back(contours[i][hull[j]]);
  27. }
  28. cv::polylines(img, hullPoints, true, cv::Scalar(0, 255, 0), 2);
  29. // 绘制凸缺陷
  30. for (size_t j = 0; j < defects.size(); j++) {
  31. int s = defects[j][0]; // 起点
  32. int e = defects[j][1]; // 终点
  33. int f = defects[j][2]; // 远点
  34. cv::Point start = contours[i][s];
  35. cv::Point end = contours[i][e];
  36. cv::Point far = contours[i][f];
  37. cv::line(img, start, end, cv::Scalar(0, 0, 255), 2);
  38. cv::circle(img, far, 5, cv::Scalar(255, 0, 0), -1);
  39. }
  40. }
  41. }
  42. int main() {
  43. // 读取图像
  44. cv::Mat img = cv::imread("hand.bmp");
  45. if (img.empty()) {
  46. std::cerr << "Could not open or find the image!" << std::endl;
  47. return -1;
  48. }
  49. cv::imshow("original", img);
  50. // 处理图像
  51. processImage(img);
  52. // 显示结果
  53. cv::imshow("result", img);
  54. cv::waitKey(0);
  55. cv::destroyAllWindows();
  56. return 0;
  57. }

3.凸缺陷与凸包面积比

当有0个凸缺陷时,手势既可能是1也可能是0,所以我们做以下判断:

凸包面积=凸缺陷面积+轮廓面积。

数值0的手势:轮廓/凸包面积> 0.9。

数值1的手势:轮廓/凸包面积≤ 0.9。

应用示例:

  1. #include<iostream>
  2. #include<opencv2/opencv.hpp>
  3. using namespace std;
  4. void reg(cv::Mat& img) {
  5. cv::Mat gray;
  6. cv::cvtColor(img, gray, cv::COLOR_BGR2GRAY);
  7. cv::Mat binary;
  8. cv::threshold(gray, binary, 127, 255, cv::THRESH_BINARY + cv::THRESH_OTSU);
  9. vector < vector<cv::Point>> contours;
  10. cv::findContours(binary, contours, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);
  11. //找到最大轮廓
  12. /*auto max_contour = std::max_element(contours.begin(), contours.end(), [](const std::vector<cv::Point>& a, const std::vector<cv::Point>& b) {
  13. return cv::contourArea(a) < cv::contourArea(b);
  14. });*/
  15. double max_area = 0;
  16. vector<cv::Point> max_contours;
  17. for (int i = 0; i < contours.size(); ++i) {
  18. double area = cv::contourArea(contours[i]);
  19. if (area > max_area) {
  20. max_area = area;
  21. max_contours = contours[i];
  22. }
  23. }
  24. cout << "Max contour area:" << max_area << endl;
  25. vector<cv::Point> hull;
  26. cv::convexHull(max_contours, hull);
  27. double areahull = cv::contourArea(hull);
  28. // 通常情况下,手势0,轮廓和凸包大致相等,该值大于0.9.
  29. // 手势1,轮廓要比凸包小一些,该值小于等于0.9
  30. double arearatio = max_area / areahull;
  31. string result;
  32. if (arearatio > 0.9) {
  33. result = "fist:0";
  34. }
  35. else {
  36. result = "finger:1";
  37. }
  38. cv::Point org(0, 80);
  39. int font = cv::FONT_HERSHEY_SIMPLEX;
  40. double fontScale = 2;
  41. cv::Scalar color(0, 0, 255);
  42. int thickness = 3;
  43. cv::putText(img, result, org, font, fontScale, color, thickness);
  44. };
  45. int main() {
  46. cv::Mat img1 = cv::imread("zero.jpg");
  47. cv::Mat img2 = cv::imread("one.jpg");
  48. if (img1.empty() || img2.empty()) {
  49. cerr << "error" << endl;
  50. return -1;
  51. }
  52. reg(img1);
  53. reg(img2);
  54. cv::imshow("zero", img1);
  55. cv::imshow("one", img2);
  56. cv::waitKey();
  57. cv::destroyAllWindows();
  58. return 0;
  59. }

二.识别过程

1.识别流程

上面为基本流程图,下面介绍具体步骤。

2.具体步骤

(1)获取图像

读取摄像头,划定识别区域,仅在区域里识别手势。

  1. cv::Mat frame;
  2. cap.read(frame);
  3. if (frame.empty()) break;
  4. cv::flip(frame, frame, 1);
  5. // 设定一个固定区域作为识别区域
  6. cv::Rect roi_rect(400, 10, 200, 200);
  7. cv::Mat roi = frame(roi_rect);
  8. cv::rectangle(frame, roi_rect, cv::Scalar(0, 0, 255), 0);

(2)识别皮肤

本步骤的主要任务是色彩空间转换。将图像从BGR转化为HSV,以进行皮肤检测。

HSV 色彩空间的稳定性

色调(Hue, H):表示颜色的类型,例如红色、绿色等。

饱和度(Saturation, S):表示颜色的纯度。

明度(Value, V):表示颜色的亮度。

皮肤颜色在HSV色彩空间中的色调范围相对稳定,通常集中在一定的色调范围内。在HSV色彩空间中,色调(Hue)对于光照变化和阴影的影响较小,这使得在不同光照条件下,颜色的检测更加稳定。尽管皮肤颜色的亮度和饱和度可能会有所不同,但色调(Hue)变化较小,这使得使用HSV空间可以更有效地检测皮肤。

  1. // 在hsv色彩空间内检测出皮肤
  2. cv::Mat hsv;
  3. cv::cvtColor(roi, hsv, cv::COLOR_BGR2HSV);
  4. cv::Scalar lower_skin(0, 28, 70);
  5. cv::Scalar upper_skin(20, 255, 255);
  6. cv::Mat mask;
  7. cv::inRange(hsv, lower_skin, upper_skin, mask);

(3)图像预处理

去除噪声,高斯滤波

  1. // 预处理
  2. cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(2, 2));
  3. cv::dilate(mask, mask, kernel, cv::Point(-1, -1), 4);
  4. cv::GaussianBlur(mask, mask, cv::Size(5, 5), 100);

(4)获取轮廓

  1. // 找出轮廓
  2. vector<vector<cv::Point>> contours;
  3. vector<cv::Vec4i> hierarchy;
  4. cv::findContours(mask, contours, hierarchy, cv::RETR_TREE,
  5. cv::CHAIN_APPROX_SIMPLE);
  6. double max_area = 0;
  7. vector<cv::Point> max_contour;
  8. for (int i = 0; i < contours.size(); ++i) {
  9. double area = cv::contourArea(contours[i]);
  10. if (area > max_area) {
  11. max_area = area;
  12. max_contour = contours[i];
  13. }
  14. }

(5)获取凸包

  1. vector<cv::Point> hull;
  2. cv::convexHull(max_contour, hull);
  3. double areahull = cv::contourArea(hull);

(6)轮廓与凸包面积比

        double arearatio = max_area / areahull;

(7)获取凸缺陷

  1. // 获取凸缺陷
  2. vector<int> hull_indices;
  3. cv::convexHull(max_contour, hull_indices, false);
  4. vector<cv::Vec4i> defects;
  5. cv::convexityDefects(max_contour, hull_indices, defects);

(8)计算并绘制有效凸缺陷

  1. int n = 0; // 凹凸点个数初始值为0
  2. // 遍历凸缺陷,判断是否为指间凸缺陷
  3. for (size_t i = 0; i < defects.size(); i++) {
  4. int s = defects[i][0]; // 起点
  5. int e = defects[i][1]; // 终点
  6. int f = defects[i][2]; // 远点
  7. cv::Point start = (max_contour)[s];
  8. cv::Point end = (max_contour)[e];
  9. cv::Point far = (max_contour)[f];
  10. double a = cv::norm(end - start);
  11. double b = cv::norm(far - start);
  12. double c = cv::norm(end - far);
  13. // 计算手指之间的角度
  14. double angle = acos((b * b + c * c - a * a) / (2 * b * c)) * 57;
  15. if (angle <= 90 && defects[i][3] > 20) {
  16. n++;
  17. cv::circle(roi, far, 3, cv::Scalar(255, 0, 0), -1); // 用蓝色绘制最远点
  18. }
  19. // 绘制手势的凸包
  20. cv::line(roi, start, end, cv::Scalar(0, 255, 0), 2);
  21. }

(9)使用凸缺陷识别手势

  1. // 通过凸缺陷个数及面积比判断识别结果
  2. string result;
  3. if (n == 0) {
  4. if (arearatio > 0.9) {
  5. result = "0";
  6. }
  7. else {
  8. result = "1";
  9. }
  10. }
  11. else if (n == 1) {
  12. result = "2";
  13. }
  14. else if (n == 2) {
  15. result = "3";
  16. }
  17. else if (n == 3) {
  18. result = "4";
  19. }
  20. else if (n == 4) {
  21. result = "5";
  22. }

(10)显示结果

  1. // 显示识别结果
  2. cv::putText(frame, result, cv::Point(400, 80), cv::FONT_HERSHEY_SIMPLEX, 2,
  3. cv::Scalar(0, 0, 255), 3);
  4. cv::imshow("frame", frame);
  5. if (cv::waitKey(25) == 27) { // 键盘Esc键退出
  6. break;
  7. }
  8. }
  9. cv::destroyAllWindows();
  10. cap.release();

完整代码:

  1. #include <opencv2/opencv.hpp>
  2. #include <iostream>
  3. #include <cmath>
  4. #include <vector>
  5. using namespace std;
  6. int main() {
  7. cv::VideoCapture cap(0, cv::CAP_DSHOW);
  8. if (!cap.isOpened()) {
  9. cerr << "Error opening video stream" << endl;
  10. return -1;
  11. }
  12. while (cap.isOpened()) {
  13. cv::Mat frame;
  14. cap.read(frame);
  15. if (frame.empty()) break;
  16. cv::flip(frame, frame, 1);
  17. // 设定一个固定区域作为识别区域
  18. cv::Rect roi_rect(400, 10, 200, 200);
  19. cv::Mat roi = frame(roi_rect);
  20. cv::rectangle(frame, roi_rect, cv::Scalar(0, 0, 255), 0);
  21. // 在hsv色彩空间内检测出皮肤
  22. cv::Mat hsv;
  23. cv::cvtColor(roi, hsv, cv::COLOR_BGR2HSV);
  24. cv::Scalar lower_skin(0, 28, 70);
  25. cv::Scalar upper_skin(20, 255, 255);
  26. cv::Mat mask;
  27. cv::inRange(hsv, lower_skin, upper_skin, mask);
  28. // 预处理
  29. cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(2, 2));
  30. cv::dilate(mask, mask, kernel, cv::Point(-1, -1), 4);
  31. cv::GaussianBlur(mask, mask, cv::Size(5, 5), 100);
  32. // 找出轮廓
  33. vector<vector<cv::Point>> contours;
  34. vector<cv::Vec4i> hierarchy;
  35. cv::findContours(mask, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);
  36. // 找到最大轮廓
  37. /*auto max_contour = max_element(contours.begin(), contours.end(), [](const vector<cv::Point>& a, const vector<cv::Point>& b) {
  38. return cv::contourArea(a) < cv::contourArea(b);
  39. });
  40. double areacnt = cv::contourArea(*max_contour);*/
  41. double max_area = 0;
  42. vector<cv::Point> max_contour;
  43. for (int i = 0; i < contours.size(); ++i) {
  44. double area = cv::contourArea(contours[i]);
  45. if (area > max_area) {
  46. max_area = area;
  47. max_contour = contours[i];
  48. }
  49. }
  50. // 获取轮廓的凸包
  51. vector<cv::Point> hull;
  52. cv::convexHull(max_contour, hull);
  53. double areahull = cv::contourArea(hull);
  54. // 获取轮廓面积、凸包的面积比
  55. double arearatio = max_area / areahull;
  56. // 获取凸缺陷
  57. vector<int> hull_indices;
  58. cv::convexHull(max_contour, hull_indices, false);
  59. vector<cv::Vec4i> defects;
  60. cv::convexityDefects(max_contour, hull_indices, defects);
  61. int n = 0; // 凹凸点个数初始值为0
  62. // 遍历凸缺陷,判断是否为指间凸缺陷
  63. for (size_t i = 0; i < defects.size(); i++) {
  64. int s = defects[i][0]; // 起点
  65. int e = defects[i][1]; // 终点
  66. int f = defects[i][2]; // 远点
  67. cv::Point start = (max_contour)[s];
  68. cv::Point end = (max_contour)[e];
  69. cv::Point far = (max_contour)[f];
  70. double a = cv::norm(end - start);
  71. double b = cv::norm(far - start);
  72. double c = cv::norm(end - far);
  73. // 计算手指之间的角度
  74. double angle = acos((b * b + c * c - a * a) / (2 * b * c)) * 57;
  75. if (angle <= 90 && defects[i][3] > 20) {
  76. n++;
  77. cv::circle(roi, far, 3, cv::Scalar(255, 0, 0), -1); // 用蓝色绘制最远点
  78. }
  79. // 绘制手势的凸包
  80. cv::line(roi, start, end, cv::Scalar(0, 255, 0), 2);
  81. }
  82. // 通过凸缺陷个数及面积比判断识别结果
  83. string result;
  84. if (n == 0) {
  85. if (arearatio > 0.9) {
  86. result = "0";
  87. }
  88. else {
  89. result = "1";
  90. }
  91. }
  92. else if (n == 1) {
  93. result = "2";
  94. }
  95. else if (n == 2) {
  96. result = "3";
  97. }
  98. else if (n == 3) {
  99. result = "4";
  100. }
  101. else if (n == 4) {
  102. result = "5";
  103. }
  104. // 显示识别结果
  105. cv::putText(frame, result, cv::Point(400, 80), cv::FONT_HERSHEY_SIMPLEX, 2, cv::Scalar(0, 0, 255), 3);
  106. cv::imshow("frame", frame);
  107. if (cv::waitKey(25) == 27) { // 键盘Esc键退出
  108. break;
  109. }
  110. }
  111. cv::destroyAllWindows();
  112. cap.release();
  113. return 0;
  114. }

结果如图:

三.剪刀石头布

1.cv::matchShapes 形状匹配

功能:用于比较两个形状相似度的函数。它通过计算两个对象的Hu矩来测量相似度。两个对象可以是轮廓,也可以是灰度图。

函数语法:

参数含义
contour1第一个轮廓或灰度图像
contour2第二个轮廓或灰度图像
method

int类型,用于计算相似度的比较方法。常见的方法有:

cv::CONTOURS_MATCH_I1: I1 (Hu)距离。可直接写为1

cv::CONTOURS_MATCH_I2: I2 (Hu)距离。可直接写为2

cv::CONTOURS_MATCH_I3: I3 (Hu)距离。可直接写为3

parameter不使用时传入 0

代码示例: 

  1. #include <opencv2/opencv.hpp>
  2. #include <iostream>
  3. using namespace std;
  4. int main() {
  5. // 读取图像
  6. cv::Mat o1 = cv::imread("o1.jpg");
  7. cv::Mat o2 = cv::imread("o2.jpg");
  8. cv::Mat o3 = cv::imread("o3.jpg");
  9. if (o1.empty() || o2.empty() || o3.empty()) {
  10. cerr << "Could not open or find the images!" << endl;
  11. return -1;
  12. }
  13. // 转换为灰度图像
  14. cv::Mat gray1, gray2, gray3;
  15. cv::cvtColor(o1, gray1, cv::COLOR_BGR2GRAY);
  16. cv::cvtColor(o2, gray2, cv::COLOR_BGR2GRAY);
  17. cv::cvtColor(o3, gray3, cv::COLOR_BGR2GRAY);
  18. // 二值化
  19. cv::Mat binary1, binary2, binary3;
  20. cv::threshold(gray1, binary1, 127, 255, cv::THRESH_BINARY);
  21. cv::threshold(gray2, binary2, 127, 255, cv::THRESH_BINARY);
  22. cv::threshold(gray3, binary3, 127, 255, cv::THRESH_BINARY);
  23. // 查找轮廓
  24. vector<vector<cv::Point>> contours1, contours2, contours3;
  25. vector<cv::Vec4i> hierarchy;
  26. cv::findContours(binary1, contours1, hierarchy, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE);
  27. cv::findContours(binary2, contours2, hierarchy, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE);
  28. cv::findContours(binary3, contours3, hierarchy, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE);
  29. if (contours1.empty() || contours2.empty() || contours3.empty()) {
  30. cerr << "Could not find contours in the images!" << endl;
  31. return -1;
  32. }
  33. // 获取第一个轮廓
  34. vector<cv::Point> cnt1 = contours1[0];
  35. vector<cv::Point> cnt2 = contours2[0];
  36. vector<cv::Point> cnt3 = contours3[0];
  37. // 形状匹配
  38. double ret0 = cv::matchShapes(cnt1, cnt1, 1, 0.0);
  39. double ret1 = cv::matchShapes(cnt1, cnt2, 1, 0.0);
  40. double ret2 = cv::matchShapes(cnt1, cnt3, 1, 0.0);
  41. // 输出结果
  42. cout << "o1.shape = " << o1.size() << endl;
  43. cout << "o2.shape = " << o2.size() << endl;
  44. cout << "o3.shape = " << o3.size() << endl;
  45. cout << "相同图像(cnt1,cnt1)的matchShape = " << ret0 << endl;
  46. cout << "相似图像(cnt1,cnt2)的matchShape = " << ret1 << endl;
  47. cout << "不相似图像(cnt1,cnt3)的matchShape = " << ret2 << endl;
  48. // 显示图像
  49. cv::imshow("original1", o1);
  50. cv::imshow("original2", o2);
  51. cv::imshow("original3", o3);
  52. cv::waitKey(0);
  53. cv::destroyAllWindows();
  54. return 0;
  55. }

运行结果:
 

需要注意,选取轮廓作参数时,仅从原始图像中选取了部分轮廓参与匹配。

而使用灰度图作为参数时,函数使用了更多特征参与匹配,所以结果不一样。

2.剪刀石头布识别

实现程序:图片的剪刀石头布识别

  1. #include <opencv2/opencv.hpp>
  2. #include <iostream>
  3. #include <vector>
  4. #include <algorithm>
  5. using namespace std;
  6. string reg(const cv::Mat& x) {
  7. cv::Mat o1 = cv::imread("paper.jpg");
  8. cv::Mat o2 = cv::imread("rock.jpg");
  9. cv::Mat o3 = cv::imread("scissors.jpg");
  10. if (o1.empty() || o2.empty() || o3.empty()) {
  11. cerr << "Could not open or find the images!" << endl;
  12. return "";
  13. }
  14. cv::Mat gray1, gray2, gray3, xgray;
  15. cv::cvtColor(o1, gray1, cv::COLOR_BGR2GRAY);
  16. cv::cvtColor(o2, gray2, cv::COLOR_BGR2GRAY);
  17. cv::cvtColor(o3, gray3, cv::COLOR_BGR2GRAY);
  18. cv::cvtColor(x, xgray, cv::COLOR_BGR2GRAY);
  19. cv::Mat binary1, binary2, binary3, xbinary;
  20. cv::threshold(gray1, binary1, 127, 255, cv::THRESH_BINARY);
  21. cv::threshold(gray2, binary2, 127, 255, cv::THRESH_BINARY);
  22. cv::threshold(gray3, binary3, 127, 255, cv::THRESH_BINARY);
  23. cv::threshold(xgray, xbinary, 127, 255, cv::THRESH_BINARY);
  24. vector<vector<cv::Point>> contours1, contours2, contours3, xcontours;
  25. vector<cv::Vec4i> hierarchy;
  26. cv::findContours(binary1, contours1, hierarchy, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE);
  27. cv::findContours(binary2, contours2, hierarchy, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE);
  28. cv::findContours(binary3, contours3, hierarchy, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE);
  29. cv::findContours(xbinary, xcontours, hierarchy, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE);
  30. if (contours1.empty() || contours2.empty() || contours3.empty() || xcontours.empty()) {
  31. cerr << "Could not find contours in one or more images!" << endl;
  32. return "";
  33. }
  34. vector<cv::Point> cnt1 = contours1[0];
  35. vector<cv::Point> cnt2 = contours2[0];
  36. vector<cv::Point> cnt3 = contours3[0];
  37. vector<cv::Point> cntx = xcontours[0];
  38. vector<double> ret;
  39. ret.push_back(cv::matchShapes(cntx, cnt1, cv::CONTOURS_MATCH_I1, 0.0));
  40. ret.push_back(cv::matchShapes(cntx, cnt2, cv::CONTOURS_MATCH_I1, 0.0));
  41. ret.push_back(cv::matchShapes(cntx, cnt3, cv::CONTOURS_MATCH_I1, 0.0));
  42. int max_index = min_element(ret.begin(), ret.end()) - ret.begin();
  43. string result;
  44. if (max_index == 0) {
  45. result = "paper";
  46. } else if (max_index == 1) {
  47. result = "rock";
  48. } else {
  49. result = "scissors";
  50. }
  51. return result;
  52. }
  53. int main() {
  54. cv::Mat t1 = cv::imread("test1.jpg");
  55. cv::Mat t2 = cv::imread("test2.jpg");
  56. cv::Mat t3 = cv::imread("test3.jpg");
  57. if (t1.empty() || t2.empty() || t3.empty()) {
  58. cerr << "Could not open or find the test images!" << endl;
  59. return -1;
  60. }
  61. // 输出识别结果
  62. cout << reg(t1) << endl;
  63. cout << reg(t2) << endl;
  64. cout << reg(t3) << endl;
  65. // 显示处理结果
  66. cv::Point org(0, 60);
  67. int font = cv::FONT_HERSHEY_SIMPLEX;
  68. double fontScale = 2;
  69. cv::Scalar color(255, 255, 255);
  70. int thickness = 3;
  71. cv::putText(t1, reg(t1), org, font, fontScale, color, thickness);
  72. cv::putText(t2, reg(t2), org, font, fontScale, color, thickness);
  73. cv::putText(t3, reg(t3), org, font, fontScale, color, thickness);
  74. cv::imshow("test1", t1);
  75. cv::imshow("test2", t2);
  76. cv::imshow("test3", t3);
  77. cv::waitKey(0);
  78. cv::destroyAllWindows();
  79. return 0;
  80. }

拓展:摄像头使用凸缺陷识别剪刀石头布

  1. #include <opencv2/opencv.hpp>
  2. #include <iostream>
  3. #include <cmath>
  4. int main() {
  5. cv::VideoCapture cap(0, cv::CAP_DSHOW);
  6. if (!cap.isOpened()) {
  7. std::cerr << "Error: Could not open camera." << std::endl;
  8. return -1;
  9. }
  10. while (true) {
  11. cv::Mat frame;
  12. cap >> frame;
  13. if (frame.empty()) {
  14. std::cerr << "Error: Could not read frame." << std::endl;
  15. break;
  16. }
  17. cv::flip(frame, frame, 1);
  18. cv::Rect roi_rect(400, 10, 200, 200);
  19. cv::Mat roi = frame(roi_rect);
  20. cv::rectangle(frame, roi_rect, cv::Scalar(0, 0, 255), 2);
  21. cv::Mat hsv;
  22. cv::cvtColor(roi, hsv, cv::COLOR_BGR2HSV);
  23. cv::Scalar lower_skin(0, 28, 70);
  24. cv::Scalar upper_skin(20, 255, 255);
  25. cv::Mat mask;
  26. cv::inRange(hsv, lower_skin, upper_skin, mask);
  27. cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(2, 2));
  28. cv::dilate(mask, mask, kernel, cv::Point(-1, -1), 4);
  29. cv::GaussianBlur(mask, mask, cv::Size(5, 5), 100);
  30. std::vector<std::vector<cv::Point>> contours;
  31. cv::findContours(mask, contours, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);
  32. if (contours.empty()) continue;
  33. auto cnt = *std::max_element(contours.begin(), contours.end(), [](const std::vector<cv::Point>& a, const std::vector<cv::Point>& b) {
  34. return cv::contourArea(a) < cv::contourArea(b);
  35. });
  36. double areacnt = cv::contourArea(cnt);
  37. std::vector<cv::Point> hull;
  38. cv::convexHull(cnt, hull);
  39. double areahull = cv::contourArea(hull);
  40. double arearatio = areacnt / areahull;
  41. std::vector<int> hull_indices;
  42. cv::convexHull(cnt, hull_indices, false);
  43. std::vector<cv::Vec4i> defects;
  44. cv::convexityDefects(cnt, hull_indices, defects);
  45. int n = 0;
  46. for (const auto& defect : defects) {
  47. cv::Point start = cnt[defect[0]];
  48. cv::Point end = cnt[defect[1]];
  49. cv::Point far = cnt[defect[2]];
  50. double a = cv::norm(end - start);
  51. double b = cv::norm(far - start);
  52. double c = cv::norm(end - far);
  53. double angle = std::acos((b * b + c * c - a * a) / (2 * b * c)) * 57;
  54. if (angle <= 90 && defect[3] > 20) {
  55. n++;
  56. cv::circle(roi, far, 3, cv::Scalar(255, 0, 0), -1);
  57. }
  58. cv::line(roi, start, end, cv::Scalar(0, 255, 0), 2);
  59. }
  60. std::string result;
  61. if (n == 0) {
  62. result = (arearatio > 0.9) ? "Rock" : "Invalid";
  63. } else if (n == 1 || n == 2) {
  64. result = "Scissors";
  65. } else if (n == 4) {
  66. result = "Paper";
  67. } else {
  68. result = "Invalid";
  69. }
  70. cv::putText(frame, result, cv::Point(400, 80), cv::FONT_HERSHEY_SIMPLEX, 2, cv::Scalar(0, 0, 255), 3);
  71. cv::imshow("frame", frame);
  72. if (cv::waitKey(25) == 27) break; // Exit on ESC key
  73. }
  74. cv::destroyAllWindows();
  75. cap.release();
  76. return 0;
  77. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/煮酒与君饮/article/detail/961664
推荐阅读
相关标签
  

闽ICP备14008679号