赞
踩
本章记录传统手势识别,在表示0-5六个数值时的识别问题。例如识别剪刀石头布,手势,以及其表示的动作。在识别时将手势中的凹陷区域称为凸缺陷,其个数作为识别的重要依据。
需要注意,在凸缺陷个数为0时,无法识别个数,需要引入凸包的概念,后面会讲到。实际过程中,算法获取凸缺陷时,会获取到细小的凸缺陷,需要将细小的凸缺陷屏蔽。
凸包(Convex Hull)是(物体最外层)给定点集的最小凸多边形或多面体,包含所有点,并确保多边形内部任意两点的连线也在内部。换句话说,凸包指的是完全包含原有轮廓,并且仅由轮廓上的点构成的多边形。在凸包内可以想象成用橡皮筋围住一组钉在板上的钉子,橡皮筋绷紧后包围的区域即为凸包,任意三个点构成的面向内部的角的角度都小于180°。凸包在计算几何、计算机视觉、图形学等领域有广泛应用。
凸包示意图
重要函数:
功能:计算二维点集的凸包,该函数通常用于图像处理和计算几何中,以确定形状的边界。
函数语法:
- void cv::convexHull(
- InputArray points,
- OutputArray hull,
- bool clockwise=false,
- bool returnPoints=true
- );
points | 输入的二维点集,可以是 std::vector<cv::Point> 或 cv::Mat 类型。 |
hull | 输出的凸包结果,类型与输入点集相同。 如果 如果为 false,则返回凸包点集的索引。 |
clockwise | 指定输出的凸包点的顺序。 如果为 true,则按顺时针方向排序; 否则按逆时针方向排序。默认值为 false。 |
returnPoints | 指定输出结果的类型。 如果为 true,则返回凸包上的点; 如果为 false,则返回凸包点集的索引。默认值为 true。 |
示例代码:
- vector<vector<cv::Point>> contours;
- cv::findContours(binary, contours, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);
-
- vector<cv::Point> hull;
- cv::convexHull(contours[0], hull);
功能:可以用来绘制一组点连接形成的多边形轮廓。
函数语法:
- void cv::polylines(
- InputOutputArray image,
- InputArrayOfArrays hull,
- bool isClosed,
- const Scalar& color,
- int thickness=1,
- int lineType=LINE_8,
- int shift=0
- );
image | 输入输出参数,表示要绘制的图像。 |
pts | 输入参数,表示一个或多个点集的数组,每个点集表示一条多边形线。 |
isClosed | 输入参数,表示多边形是否封闭。 如果为 true,则绘制一个封闭的多边形;否则绘制一条开放的多边形线。 |
color | 输入参数,表示线条的颜色。 |
thickness | (可选)输入参数,表示线条的粗细,默认为 1。 |
lineType | (可选)输入参数,表示线条的类型 LINE_8, LINE_4等。 |
shift | (可选)输入参数,表示点坐标的小数点位数,默认为 0。 |
- #include <opencv2/opencv.hpp>
- #include <iostream>
-
- int main() {
- // 读取并绘制原始图像
- cv::Mat o = cv::imread("hand.bmp");
- if(o.empty()) {
- std::cerr << "Could not open or find the image!" << std::endl;
- return -1;
- }
- cv::imshow("original", o);
-
- // 提取轮廓
- cv::Mat gray;
- cv::cvtColor(o, gray, cv::COLOR_BGR2GRAY);
- cv::Mat binary;
- cv::threshold(gray, binary, 127, 255, cv::THRESH_BINARY);
- std::vector<std::vector<cv::Point>> contours;
- std::vector<cv::Vec4i> hierarchy;
- cv::findContours(binary, contours, hierarchy, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE);
-
- // 寻找凸包,得到凸包的角点
- std::vector<cv::Point> hull;
- cv::convexHull(contours[0], hull);
-
- // 绘制凸包
- cv::polylines(o, hull, true, cv::Scalar(0, 255, 0), 2);
-
- // 输出凸包的角点
- for (const auto& point : hull) {
- std::cout << point << std::endl;
- }
-
- // 显示凸包
- cv::imshow("result", o);
- cv::waitKey(0);
- cv::destroyAllWindows();
-
- return 0;
- }
概念:凸缺陷(Convexity Defects)是指形状的凸包与形状之间的区域。这些缺陷表示在形状的边界上向内凹陷的部分。具体来说,凸缺陷是形状的轮廓与其凸包之间的点,这些点与凸包形成的线段是形状的凹陷部分。
通常情况下,使用如下四个特征值来表示凸缺陷:
1.起点:该特征值用于说明当前凸缺陷的起点位置。需要注意的是,起点值用轮廓索引表示。也就是说,起点一定是轮廓中的一个点,并且用其在轮廓中的序号来表示。例如,点A是凸缺陷1的起点。
2.终点:该特征值用于说明当前凸缺陷的终点位置。该值也是使用轮廓索引表示的。例如,图中的点B是凸缺陷1的终点。
3.轮廓上距离凸包最远的点:例如,点C是凸缺陷1中的轮廓上距离凸包最远的点。
4.最远点到凸包的近似距离:例如,距离D是凸缺陷1中的最远点到凸包的近似距离。
功能:计算输入轮廓与其凸包之间的凸缺陷,返回每个凸缺陷的起点、终点、最远点和深度。
函数语法:
- void cv::convexityDefects(
- InputArray contour,
- InputArray convexhull,
- OutputArray convexityDefects);
contour | 输入的轮廓点集。 |
convexhull | 输入的凸包点的索引。 |
convexityDefects | 输出的凸缺陷。 |
示例代码:
- // 计算凸缺陷
- std::vector<cv::Vec4i> defects;
- cv::convexityDefects(contours[0], hull, defects);
功能:在图像 img
上绘制一条从 pt1
到 pt2
的线条,线条的颜色、粗细和类型可以由相应的参数控制。
函数语法:
- void cv::line(
- Mat& img,
- Point pt1,
- Point pt2,
- const Scalar& color,
- int thickness=1,
- int lineType=LINE_8,
- int shift=0
- );
img | 输入/输出图像,在图像上绘制线条。 |
pt1 | 线条的起点,类型为 cv::Point 。 |
pt2 | 线条的终点,类型为 cv::Point 。 |
color | 线条的颜色,类型为 例如, |
thickness | (可选)线条的粗细,默认为 1。 |
lineType | (可选)线条的类型。 |
shift | 点坐标的小数位数,默认为 0。 |
功能:在图像 img
上绘制一个以 center
为圆心、半径为 radius
的圆,圆的颜色、线条粗细和类型可以由相应的参数控制。
函数语法:
- void cv::circle(
- Mat& img,
- Point center,
- int radius,
- const Scalar& color,
- int thickness=1,
- int lineType=LINE_8,
- int shift=0
- );
img | 输入输出图像,在图像上绘制圆。 |
center | 圆心的坐标,类型为 cv::Point。 |
radius | 圆的半径,类型为 int。 |
color | 圆的颜色,类型为 例如, |
thickness | 圆的线条粗细。如果为负值,如 -1 ,则绘制填充的圆。 |
lineType | 线条的类型。 |
shift | 点坐标的小数位数,默认为 0。 |
- #include <opencv2/opencv.hpp>
- #include <iostream>
-
- int main() {
- // 读取图像
- cv::Mat img = cv::imread("hand.bmp");
- if (img.empty()) {
- std::cerr << "Could not open or find the image!" << std::endl;
- return -1;
- }
- cv::imshow("original", img);
-
- // 转换为灰度图像
- cv::Mat gray;
- cv::cvtColor(img, gray, cv::COLOR_BGR2GRAY);
-
- // 二值化
- cv::Mat binary;
- cv::threshold(gray, binary, 127, 255, cv::THRESH_BINARY);
-
- // 查找轮廓
- std::vector<std::vector<cv::Point>> contours;
- std::vector<cv::Vec4i> hierarchy;
- cv::findContours(binary, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);
-
- // 计算凸包
- std::vector<int> hull;
- cv::convexHull(contours[0], hull, false);
-
- // 计算凸缺陷
- std::vector<cv::Vec4i> defects;
- cv::convexityDefects(contours[0], hull, defects);
-
- std::cout << "defects=\n";
- for (const auto& defect : defects) {
- std::cout << defect << std::endl;
- }
-
- // 绘制凸缺陷
- for (size_t i = 0; i < defects.size(); i++) {
- int s = defects[i][0]; // 起点
- int e = defects[i][1]; // 终点
- int f = defects[i][2]; // 远点
- // int d = defects[i][3]; // 距离(这里未使用)
-
- cv::Point start = contours[0][s];
- cv::Point end = contours[0][e];
- cv::Point far = contours[0][f];
-
- cv::line(img, start, end, cv::Scalar(0, 0, 255), 2);
- cv::circle(img, far, 5, cv::Scalar(255, 0, 0), -1);
- }
-
- // 显示结果
- cv::imshow("result", img);
- cv::waitKey(0);
- cv::destroyAllWindows();
-
- return 0;
- }
结果如图:
需要对每一个轮廓分别计算凸包和凸缺陷,并将结果绘制在同一张图像上。
- #include <opencv2/opencv.hpp>
- #include <iostream>
- #include <vector>
-
- void processImage(cv::Mat& img) {
- // 转换为灰度图像
- cv::Mat gray;
- cv::cvtColor(img, gray, cv::COLOR_BGR2GRAY);
-
- // 二值化
- cv::Mat binary;
- cv::threshold(gray, binary, 127, 255, cv::THRESH_BINARY);
-
- // 查找轮廓
- std::vector<std::vector<cv::Point>> contours;
- std::vector<cv::Vec4i> hierarchy;
- cv::findContours(binary, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);
-
- // 遍历每个轮廓
- for (size_t i = 0; i < contours.size(); i++) {
- // 计算凸包
- std::vector<int> hull;
- cv::convexHull(contours[i], hull, false);
-
- // 计算凸缺陷
- std::vector<cv::Vec4i> defects;
- cv::convexityDefects(contours[i], hull, defects);
-
- // 绘制凸包
- std::vector<cv::Point> hullPoints;
- for (size_t j = 0; j < hull.size(); j++) {
- hullPoints.push_back(contours[i][hull[j]]);
- }
- cv::polylines(img, hullPoints, true, cv::Scalar(0, 255, 0), 2);
-
- // 绘制凸缺陷
- for (size_t j = 0; j < defects.size(); j++) {
- int s = defects[j][0]; // 起点
- int e = defects[j][1]; // 终点
- int f = defects[j][2]; // 远点
-
- cv::Point start = contours[i][s];
- cv::Point end = contours[i][e];
- cv::Point far = contours[i][f];
-
- cv::line(img, start, end, cv::Scalar(0, 0, 255), 2);
- cv::circle(img, far, 5, cv::Scalar(255, 0, 0), -1);
- }
- }
- }
-
- int main() {
- // 读取图像
- cv::Mat img = cv::imread("hand.bmp");
- if (img.empty()) {
- std::cerr << "Could not open or find the image!" << std::endl;
- return -1;
- }
- cv::imshow("original", img);
-
- // 处理图像
- processImage(img);
-
- // 显示结果
- cv::imshow("result", img);
- cv::waitKey(0);
- cv::destroyAllWindows();
-
- return 0;
- }
当有0个凸缺陷时,手势既可能是1也可能是0,所以我们做以下判断:
凸包面积=凸缺陷面积+轮廓面积。
数值0的手势:轮廓/凸包面积> 0.9。
数值1的手势:轮廓/凸包面积≤ 0.9。
应用示例:
- #include<iostream>
- #include<opencv2/opencv.hpp>
- using namespace std;
-
- void reg(cv::Mat& img) {
- cv::Mat gray;
- cv::cvtColor(img, gray, cv::COLOR_BGR2GRAY);
- cv::Mat binary;
- cv::threshold(gray, binary, 127, 255, cv::THRESH_BINARY + cv::THRESH_OTSU);
- vector < vector<cv::Point>> contours;
- cv::findContours(binary, contours, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);
-
- //找到最大轮廓
- /*auto max_contour = std::max_element(contours.begin(), contours.end(), [](const std::vector<cv::Point>& a, const std::vector<cv::Point>& b) {
- return cv::contourArea(a) < cv::contourArea(b);
- });*/
- double max_area = 0;
- vector<cv::Point> max_contours;
- for (int i = 0; i < contours.size(); ++i) {
- double area = cv::contourArea(contours[i]);
- if (area > max_area) {
- max_area = area;
- max_contours = contours[i];
- }
- }
- cout << "Max contour area:" << max_area << endl;
- vector<cv::Point> hull;
- cv::convexHull(max_contours, hull);
- double areahull = cv::contourArea(hull);
- // 通常情况下,手势0,轮廓和凸包大致相等,该值大于0.9.
- // 手势1,轮廓要比凸包小一些,该值小于等于0.9
- double arearatio = max_area / areahull;
- string result;
- if (arearatio > 0.9) {
- result = "fist:0";
- }
- else {
- result = "finger:1";
- }
- cv::Point org(0, 80);
- int font = cv::FONT_HERSHEY_SIMPLEX;
- double fontScale = 2;
- cv::Scalar color(0, 0, 255);
- int thickness = 3;
- cv::putText(img, result, org, font, fontScale, color, thickness);
-
-
- };
- int main() {
- cv::Mat img1 = cv::imread("zero.jpg");
- cv::Mat img2 = cv::imread("one.jpg");
- if (img1.empty() || img2.empty()) {
- cerr << "error" << endl;
- return -1;
- }
- reg(img1);
- reg(img2);
- cv::imshow("zero", img1);
- cv::imshow("one", img2);
- cv::waitKey();
- cv::destroyAllWindows();
-
- return 0;
- }
上面为基本流程图,下面介绍具体步骤。
(1)获取图像
读取摄像头,划定识别区域,仅在区域里识别手势。
- cv::Mat frame;
- cap.read(frame);
- if (frame.empty()) break;
-
- cv::flip(frame, frame, 1);
-
- // 设定一个固定区域作为识别区域
- cv::Rect roi_rect(400, 10, 200, 200);
- cv::Mat roi = frame(roi_rect);
- 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空间可以更有效地检测皮肤。
- // 在hsv色彩空间内检测出皮肤
- cv::Mat hsv;
- cv::cvtColor(roi, hsv, cv::COLOR_BGR2HSV);
- cv::Scalar lower_skin(0, 28, 70);
- cv::Scalar upper_skin(20, 255, 255);
- cv::Mat mask;
- cv::inRange(hsv, lower_skin, upper_skin, mask);
(3)图像预处理
去除噪声,高斯滤波
- // 预处理
- cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(2, 2));
- cv::dilate(mask, mask, kernel, cv::Point(-1, -1), 4);
- cv::GaussianBlur(mask, mask, cv::Size(5, 5), 100);
(4)获取轮廓
- // 找出轮廓
- vector<vector<cv::Point>> contours;
- vector<cv::Vec4i> hierarchy;
- cv::findContours(mask, contours, hierarchy, cv::RETR_TREE,
- cv::CHAIN_APPROX_SIMPLE);
-
- double max_area = 0;
- vector<cv::Point> max_contour;
- for (int i = 0; i < contours.size(); ++i) {
- double area = cv::contourArea(contours[i]);
- if (area > max_area) {
- max_area = area;
- max_contour = contours[i];
- }
- }
(5)获取凸包
- vector<cv::Point> hull;
- cv::convexHull(max_contour, hull);
- double areahull = cv::contourArea(hull);
(6)轮廓与凸包面积比
double arearatio = max_area / areahull;
(7)获取凸缺陷
- // 获取凸缺陷
- vector<int> hull_indices;
- cv::convexHull(max_contour, hull_indices, false);
- vector<cv::Vec4i> defects;
- cv::convexityDefects(max_contour, hull_indices, defects);
(8)计算并绘制有效凸缺陷
- int n = 0; // 凹凸点个数初始值为0
-
- // 遍历凸缺陷,判断是否为指间凸缺陷
- for (size_t i = 0; i < defects.size(); i++) {
- int s = defects[i][0]; // 起点
- int e = defects[i][1]; // 终点
- int f = defects[i][2]; // 远点
-
- cv::Point start = (max_contour)[s];
- cv::Point end = (max_contour)[e];
- cv::Point far = (max_contour)[f];
-
- double a = cv::norm(end - start);
- double b = cv::norm(far - start);
- double c = cv::norm(end - far);
-
- // 计算手指之间的角度
- double angle = acos((b * b + c * c - a * a) / (2 * b * c)) * 57;
- if (angle <= 90 && defects[i][3] > 20) {
- n++;
- cv::circle(roi, far, 3, cv::Scalar(255, 0, 0), -1); // 用蓝色绘制最远点
- }
-
- // 绘制手势的凸包
- cv::line(roi, start, end, cv::Scalar(0, 255, 0), 2);
- }
(9)使用凸缺陷识别手势
- // 通过凸缺陷个数及面积比判断识别结果
- string result;
- if (n == 0) {
- if (arearatio > 0.9) {
- result = "0";
- }
- else {
- result = "1";
- }
- }
- else if (n == 1) {
- result = "2";
- }
- else if (n == 2) {
- result = "3";
- }
- else if (n == 3) {
- result = "4";
- }
- else if (n == 4) {
- result = "5";
- }
(10)显示结果
- // 显示识别结果
- cv::putText(frame, result, cv::Point(400, 80), cv::FONT_HERSHEY_SIMPLEX, 2,
- cv::Scalar(0, 0, 255), 3);
- cv::imshow("frame", frame);
-
- if (cv::waitKey(25) == 27) { // 键盘Esc键退出
- break;
- }
- }
-
- cv::destroyAllWindows();
- cap.release();
完整代码:
- #include <opencv2/opencv.hpp>
- #include <iostream>
- #include <cmath>
- #include <vector>
-
- using namespace std;
-
- int main() {
- cv::VideoCapture cap(0, cv::CAP_DSHOW);
- if (!cap.isOpened()) {
- cerr << "Error opening video stream" << endl;
- return -1;
- }
-
- while (cap.isOpened()) {
- cv::Mat frame;
- cap.read(frame);
- if (frame.empty()) break;
-
- cv::flip(frame, frame, 1);
-
- // 设定一个固定区域作为识别区域
- cv::Rect roi_rect(400, 10, 200, 200);
- cv::Mat roi = frame(roi_rect);
- cv::rectangle(frame, roi_rect, cv::Scalar(0, 0, 255), 0);
-
- // 在hsv色彩空间内检测出皮肤
- cv::Mat hsv;
- cv::cvtColor(roi, hsv, cv::COLOR_BGR2HSV);
- cv::Scalar lower_skin(0, 28, 70);
- cv::Scalar upper_skin(20, 255, 255);
- cv::Mat mask;
- cv::inRange(hsv, lower_skin, upper_skin, mask);
-
- // 预处理
- cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(2, 2));
- cv::dilate(mask, mask, kernel, cv::Point(-1, -1), 4);
- cv::GaussianBlur(mask, mask, cv::Size(5, 5), 100);
-
- // 找出轮廓
- vector<vector<cv::Point>> contours;
- vector<cv::Vec4i> hierarchy;
- cv::findContours(mask, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);
-
-
- // 找到最大轮廓
- /*auto max_contour = max_element(contours.begin(), contours.end(), [](const vector<cv::Point>& a, const vector<cv::Point>& b) {
- return cv::contourArea(a) < cv::contourArea(b);
- });
- double areacnt = cv::contourArea(*max_contour);*/
- double max_area = 0;
- vector<cv::Point> max_contour;
- for (int i = 0; i < contours.size(); ++i) {
- double area = cv::contourArea(contours[i]);
- if (area > max_area) {
- max_area = area;
- max_contour = contours[i];
- }
- }
-
- // 获取轮廓的凸包
- vector<cv::Point> hull;
- cv::convexHull(max_contour, hull);
- double areahull = cv::contourArea(hull);
-
- // 获取轮廓面积、凸包的面积比
- double arearatio = max_area / areahull;
-
- // 获取凸缺陷
- vector<int> hull_indices;
- cv::convexHull(max_contour, hull_indices, false);
- vector<cv::Vec4i> defects;
- cv::convexityDefects(max_contour, hull_indices, defects);
-
- int n = 0; // 凹凸点个数初始值为0
-
- // 遍历凸缺陷,判断是否为指间凸缺陷
- for (size_t i = 0; i < defects.size(); i++) {
- int s = defects[i][0]; // 起点
- int e = defects[i][1]; // 终点
- int f = defects[i][2]; // 远点
-
- cv::Point start = (max_contour)[s];
- cv::Point end = (max_contour)[e];
- cv::Point far = (max_contour)[f];
-
- double a = cv::norm(end - start);
- double b = cv::norm(far - start);
- double c = cv::norm(end - far);
-
- // 计算手指之间的角度
- double angle = acos((b * b + c * c - a * a) / (2 * b * c)) * 57;
- if (angle <= 90 && defects[i][3] > 20) {
- n++;
- cv::circle(roi, far, 3, cv::Scalar(255, 0, 0), -1); // 用蓝色绘制最远点
- }
-
- // 绘制手势的凸包
- cv::line(roi, start, end, cv::Scalar(0, 255, 0), 2);
- }
-
- // 通过凸缺陷个数及面积比判断识别结果
- string result;
- if (n == 0) {
- if (arearatio > 0.9) {
- result = "0";
- }
- else {
- result = "1";
- }
- }
- else if (n == 1) {
- result = "2";
- }
- else if (n == 2) {
- result = "3";
- }
- else if (n == 3) {
- result = "4";
- }
- else if (n == 4) {
- result = "5";
- }
-
- // 显示识别结果
- cv::putText(frame, result, cv::Point(400, 80), cv::FONT_HERSHEY_SIMPLEX, 2, cv::Scalar(0, 0, 255), 3);
- cv::imshow("frame", frame);
-
- if (cv::waitKey(25) == 27) { // 键盘Esc键退出
- break;
- }
- }
-
- cv::destroyAllWindows();
- cap.release();
- return 0;
- }
结果如图:
功能:用于比较两个形状相似度的函数。它通过计算两个对象的Hu矩来测量相似度。两个对象可以是轮廓,也可以是灰度图。
函数语法:
contour1 | 第一个轮廓或灰度图像 |
contour2 | 第二个轮廓或灰度图像 |
method | int类型,用于计算相似度的比较方法。常见的方法有:
|
parameter | 不使用时传入 0 |
代码示例:
- #include <opencv2/opencv.hpp>
- #include <iostream>
-
- using namespace std;
-
- int main() {
- // 读取图像
- cv::Mat o1 = cv::imread("o1.jpg");
- cv::Mat o2 = cv::imread("o2.jpg");
- cv::Mat o3 = cv::imread("o3.jpg");
-
- if (o1.empty() || o2.empty() || o3.empty()) {
- cerr << "Could not open or find the images!" << endl;
- return -1;
- }
-
- // 转换为灰度图像
- cv::Mat gray1, gray2, gray3;
- cv::cvtColor(o1, gray1, cv::COLOR_BGR2GRAY);
- cv::cvtColor(o2, gray2, cv::COLOR_BGR2GRAY);
- cv::cvtColor(o3, gray3, cv::COLOR_BGR2GRAY);
-
- // 二值化
- cv::Mat binary1, binary2, binary3;
- cv::threshold(gray1, binary1, 127, 255, cv::THRESH_BINARY);
- cv::threshold(gray2, binary2, 127, 255, cv::THRESH_BINARY);
- cv::threshold(gray3, binary3, 127, 255, cv::THRESH_BINARY);
-
- // 查找轮廓
- vector<vector<cv::Point>> contours1, contours2, contours3;
- vector<cv::Vec4i> hierarchy;
- cv::findContours(binary1, contours1, hierarchy, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE);
- cv::findContours(binary2, contours2, hierarchy, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE);
- cv::findContours(binary3, contours3, hierarchy, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE);
-
- if (contours1.empty() || contours2.empty() || contours3.empty()) {
- cerr << "Could not find contours in the images!" << endl;
- return -1;
- }
-
- // 获取第一个轮廓
- vector<cv::Point> cnt1 = contours1[0];
- vector<cv::Point> cnt2 = contours2[0];
- vector<cv::Point> cnt3 = contours3[0];
-
- // 形状匹配
- double ret0 = cv::matchShapes(cnt1, cnt1, 1, 0.0);
- double ret1 = cv::matchShapes(cnt1, cnt2, 1, 0.0);
- double ret2 = cv::matchShapes(cnt1, cnt3, 1, 0.0);
-
- // 输出结果
- cout << "o1.shape = " << o1.size() << endl;
- cout << "o2.shape = " << o2.size() << endl;
- cout << "o3.shape = " << o3.size() << endl;
- cout << "相同图像(cnt1,cnt1)的matchShape = " << ret0 << endl;
- cout << "相似图像(cnt1,cnt2)的matchShape = " << ret1 << endl;
- cout << "不相似图像(cnt1,cnt3)的matchShape = " << ret2 << endl;
-
- // 显示图像
- cv::imshow("original1", o1);
- cv::imshow("original2", o2);
- cv::imshow("original3", o3);
- cv::waitKey(0);
- cv::destroyAllWindows();
-
- return 0;
- }
运行结果:
需要注意,选取轮廓作参数时,仅从原始图像中选取了部分轮廓参与匹配。
而使用灰度图作为参数时,函数使用了更多特征参与匹配,所以结果不一样。
- #include <opencv2/opencv.hpp>
- #include <iostream>
- #include <vector>
- #include <algorithm>
-
- using namespace std;
-
- string reg(const cv::Mat& x) {
- cv::Mat o1 = cv::imread("paper.jpg");
- cv::Mat o2 = cv::imread("rock.jpg");
- cv::Mat o3 = cv::imread("scissors.jpg");
-
- if (o1.empty() || o2.empty() || o3.empty()) {
- cerr << "Could not open or find the images!" << endl;
- return "";
- }
-
- cv::Mat gray1, gray2, gray3, xgray;
- cv::cvtColor(o1, gray1, cv::COLOR_BGR2GRAY);
- cv::cvtColor(o2, gray2, cv::COLOR_BGR2GRAY);
- cv::cvtColor(o3, gray3, cv::COLOR_BGR2GRAY);
- cv::cvtColor(x, xgray, cv::COLOR_BGR2GRAY);
-
- cv::Mat binary1, binary2, binary3, xbinary;
- cv::threshold(gray1, binary1, 127, 255, cv::THRESH_BINARY);
- cv::threshold(gray2, binary2, 127, 255, cv::THRESH_BINARY);
- cv::threshold(gray3, binary3, 127, 255, cv::THRESH_BINARY);
- cv::threshold(xgray, xbinary, 127, 255, cv::THRESH_BINARY);
-
- vector<vector<cv::Point>> contours1, contours2, contours3, xcontours;
- vector<cv::Vec4i> hierarchy;
- cv::findContours(binary1, contours1, hierarchy, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE);
- cv::findContours(binary2, contours2, hierarchy, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE);
- cv::findContours(binary3, contours3, hierarchy, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE);
- cv::findContours(xbinary, xcontours, hierarchy, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE);
-
- if (contours1.empty() || contours2.empty() || contours3.empty() || xcontours.empty()) {
- cerr << "Could not find contours in one or more images!" << endl;
- return "";
- }
-
- vector<cv::Point> cnt1 = contours1[0];
- vector<cv::Point> cnt2 = contours2[0];
- vector<cv::Point> cnt3 = contours3[0];
- vector<cv::Point> cntx = xcontours[0];
-
- vector<double> ret;
- ret.push_back(cv::matchShapes(cntx, cnt1, cv::CONTOURS_MATCH_I1, 0.0));
- ret.push_back(cv::matchShapes(cntx, cnt2, cv::CONTOURS_MATCH_I1, 0.0));
- ret.push_back(cv::matchShapes(cntx, cnt3, cv::CONTOURS_MATCH_I1, 0.0));
-
- int max_index = min_element(ret.begin(), ret.end()) - ret.begin();
-
- string result;
- if (max_index == 0) {
- result = "paper";
- } else if (max_index == 1) {
- result = "rock";
- } else {
- result = "scissors";
- }
-
- return result;
- }
-
- int main() {
- cv::Mat t1 = cv::imread("test1.jpg");
- cv::Mat t2 = cv::imread("test2.jpg");
- cv::Mat t3 = cv::imread("test3.jpg");
-
- if (t1.empty() || t2.empty() || t3.empty()) {
- cerr << "Could not open or find the test images!" << endl;
- return -1;
- }
-
- // 输出识别结果
- cout << reg(t1) << endl;
- cout << reg(t2) << endl;
- cout << reg(t3) << endl;
-
- // 显示处理结果
- cv::Point org(0, 60);
- int font = cv::FONT_HERSHEY_SIMPLEX;
- double fontScale = 2;
- cv::Scalar color(255, 255, 255);
- int thickness = 3;
- cv::putText(t1, reg(t1), org, font, fontScale, color, thickness);
- cv::putText(t2, reg(t2), org, font, fontScale, color, thickness);
- cv::putText(t3, reg(t3), org, font, fontScale, color, thickness);
- cv::imshow("test1", t1);
- cv::imshow("test2", t2);
- cv::imshow("test3", t3);
- cv::waitKey(0);
- cv::destroyAllWindows();
-
- return 0;
- }
- #include <opencv2/opencv.hpp>
- #include <iostream>
- #include <cmath>
-
- int main() {
- cv::VideoCapture cap(0, cv::CAP_DSHOW);
- if (!cap.isOpened()) {
- std::cerr << "Error: Could not open camera." << std::endl;
- return -1;
- }
-
- while (true) {
- cv::Mat frame;
- cap >> frame;
- if (frame.empty()) {
- std::cerr << "Error: Could not read frame." << std::endl;
- break;
- }
-
- cv::flip(frame, frame, 1);
-
- cv::Rect roi_rect(400, 10, 200, 200);
- cv::Mat roi = frame(roi_rect);
- cv::rectangle(frame, roi_rect, cv::Scalar(0, 0, 255), 2);
-
- cv::Mat hsv;
- cv::cvtColor(roi, hsv, cv::COLOR_BGR2HSV);
- cv::Scalar lower_skin(0, 28, 70);
- cv::Scalar upper_skin(20, 255, 255);
- cv::Mat mask;
- cv::inRange(hsv, lower_skin, upper_skin, mask);
-
- cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(2, 2));
- cv::dilate(mask, mask, kernel, cv::Point(-1, -1), 4);
- cv::GaussianBlur(mask, mask, cv::Size(5, 5), 100);
-
- std::vector<std::vector<cv::Point>> contours;
- cv::findContours(mask, contours, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);
- if (contours.empty()) continue;
-
- auto cnt = *std::max_element(contours.begin(), contours.end(), [](const std::vector<cv::Point>& a, const std::vector<cv::Point>& b) {
- return cv::contourArea(a) < cv::contourArea(b);
- });
- double areacnt = cv::contourArea(cnt);
-
- std::vector<cv::Point> hull;
- cv::convexHull(cnt, hull);
- double areahull = cv::contourArea(hull);
- double arearatio = areacnt / areahull;
-
- std::vector<int> hull_indices;
- cv::convexHull(cnt, hull_indices, false);
- std::vector<cv::Vec4i> defects;
- cv::convexityDefects(cnt, hull_indices, defects);
-
- int n = 0;
- for (const auto& defect : defects) {
- cv::Point start = cnt[defect[0]];
- cv::Point end = cnt[defect[1]];
- cv::Point far = cnt[defect[2]];
- double a = cv::norm(end - start);
- double b = cv::norm(far - start);
- double c = cv::norm(end - far);
- double angle = std::acos((b * b + c * c - a * a) / (2 * b * c)) * 57;
- if (angle <= 90 && defect[3] > 20) {
- n++;
- cv::circle(roi, far, 3, cv::Scalar(255, 0, 0), -1);
- }
- cv::line(roi, start, end, cv::Scalar(0, 255, 0), 2);
- }
-
- std::string result;
- if (n == 0) {
- result = (arearatio > 0.9) ? "Rock" : "Invalid";
- } else if (n == 1 || n == 2) {
- result = "Scissors";
- } else if (n == 4) {
- result = "Paper";
- } else {
- result = "Invalid";
- }
-
- cv::putText(frame, result, cv::Point(400, 80), cv::FONT_HERSHEY_SIMPLEX, 2, cv::Scalar(0, 0, 255), 3);
- cv::imshow("frame", frame);
- if (cv::waitKey(25) == 27) break; // Exit on ESC key
- }
-
- cv::destroyAllWindows();
- cap.release();
- return 0;
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。