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

news2024/11/14 18:00:38

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

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

一.理论基础

1.凸包

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

凸包示意图 

重要函数:

(1)cv::convexHull 计算凸包

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

函数语法:

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

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

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

如果为 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);

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

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

函数语法:

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。

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

#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;
}

2.凸缺陷

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

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


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

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

函数语法:

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

示例代码:

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

(2)cv::line 绘制线条

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

函数语法:

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

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

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

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

(3)cv::circle 绘制圆

功能:在图像 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

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

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

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

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

#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;
}

 结果如图:

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

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

#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;
}

3.凸缺陷与凸包面积比

当有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.识别流程

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

2.具体步骤

(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;
}

结果如图:

三.剪刀石头布

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

代码示例: 

#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;
}

运行结果:
 

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

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

2.剪刀石头布识别

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

#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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1907911.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何解决模型的灾难性遗忘问题?清华大学提出新方法

获取本文论文原文PDF&#xff0c;请在公众号【AI论文解读】留言&#xff1a;论文解读 探索连续学习中的新方法 在人工智能领域&#xff0c;尤其是在语言模型&#xff08;LM&#xff09;的发展过程中&#xff0c;连续学习&#xff08;CL&#xff09;始终是一个挑战。传统的学习…

【HTML入门】第四课 - 换行、分割横线和html的注释

这一小节&#xff0c;我们继续说HTML的入门知识&#xff0c;包括换行、横线分割以及注释&#xff08;html的注释&#xff09;。 目录 1 换行 2 分割横线 3 html注释 1 换行 html中分为块元素和行内元素。这一小节呢&#xff0c;先不说这些元素们&#xff0c;我们先说一下换…

贝叶斯估计(1):期末大乱炖

写在前面&#xff01; 1 先验分布和后验分布 三种信息&#xff1a;总体信息、样本信息、先验信息 总体信息&#xff1a;“总体是正态分布”&#xff1b;样本信息&#xff1a;总体抽取的样本提供的信息&#xff0c;是最新鲜的信息&#xff1b;先验信息&#xff1a;在抽样之前就…

从OpenAI停服看中国市场:国产替代崛起的机遇与挑战

一、OpenAI 停服事件背景 OpenAI 自 2020 年推出 GPT-3 以来&#xff0c;在全球范围内引起了极大的反响。其强大的自然语言处理能力使其成为许多企业和开发者的首选工具。然而&#xff0c;2024 年 6 月 25 日&#xff0c;许多中国用户收到了一封来自 OpenAI 的邮件&#xff0c…

c++之命名空间详解(namespace)

引例 在学习之前我们首先了来看这样一个情形: 在c语言下&#xff0c;我们写了两个头文件&#xff1a;链表和顺序表的。我们会定义一个type(typedef int type)方便改变数据类型&#xff08;比如将int改成char&#xff09;&#xff0c;来做到整体代换。 但是我们两个头文件里面…

精益生产培训公司:从混乱到高效,只需一步!

大家有没有觉得工作中的琐事总是让你忙得团团转&#xff0c;却总是达不到预期的效果&#xff1f;其实&#xff0c;很多人都遇到过类似的困扰。今天张驰咨询想跟你们分享一个能彻底改变这种情况的方法——精益生产。其实它并不复杂&#xff0c;而是非常实用和高效&#xff01; …

Xilinx FPGA DDR4 接口的 PCB 准则

目录 1. 简介 1.1 FPGA-MIG 与 DDR4 介绍 1.2 DDR4 信号介绍 1.2.1 Clock Signals 1.2.2 Address and Command Signals 1.2.3 Address and Command Signals 1.2.4 Data Signals 1.2.5 Other Signals 2. 通用存储器布线准则 3. Xilinx FPGA-MIG 的 PCB 准则 3.1 引脚…

通过高德地图 JS API实现单击鼠标进行标注

效果图: 核心代码: <template><a-modal title="选择地图所在位置" :width="width" :visible="visible" @ok="handleOk" @cancel="handleCancel" cancelText="关闭"><div class="location-…

java —— JSP 技术

一、JSP &#xff08;一&#xff09;前言 1、.jsp 与 .html 一样属于前端内容&#xff0c;创建在 WebContent 之下&#xff1b; 2、嵌套的 java 语句放置在<% %>里面&#xff1b; 3、嵌套 java 语句的三种语法&#xff1a; ① 脚本&#xff1a;<% java 代码 %>…

白嫖A100活动来啦,书生·浦语大模型全链路开源体系

扫码参加即可获得&#xff1a; 第一节 书生浦语大模型全链路开源体系 书生浦语大模型的开源历程。 从模型到应用的典型流程 书生浦语的开源体系&#xff0c;包含从数据、预训练、微调、部署、评测、应用等环节

一手洞悉泰国slot线上游戏投放本土网盟CPI计费广告优势

一手洞悉泰国slot线上游戏投放本土网盟CPI计费广告优势 ​在泰国这个拥有独特文化背景和审美观念的国家&#xff0c;Slots游戏以其丰富的玩法和刺激的体验迅速赢得了玩家们的喜爱。然而&#xff0c;要在竞争激烈的市场中脱颖而出&#xff0c;有效的推广策略显得尤为重要。本土…

消防认证-防火窗

一、消防认证 消防认证是指消防产品符合国家相关技术要求和标准&#xff0c;且通过了国家认证认可监督管理委员会审批&#xff0c;获得消防认证资质的认证机构颁发的证书&#xff0c;消防产品具有完好的防火功能&#xff0c;是住房和城乡建设领域验收的重要指标。 二、认证依据…

C++入门基础篇(1)

欢迎大家来到海盗猫鸥的博客—— 断更许久&#xff0c;让我们继续好好学习吧&#xff01; 目录 1.namespace命名空间 命名空间的存在价值&#xff1a; 命名空间的定义&#xff1a; 命名空间的使用&#xff1a; 2.C输入输出函数 使用&#xff1a; 3.缺省参数 4.函数重载…

静脉分割YOLOV8-SEG

静脉分割&#xff0c;YOLOV8*SEG资源-CSDN文库 首先使用YOLOV8-SEG训练&#xff0c;得到PT模型&#xff0c;然后转换成ONNX&#xff0c;OPENCV的DNN调用&#xff0c;从而摆脱PYTORCH依赖&#xff0c;支持C,PYTHON,ANDROID调用

Spring AOP源码篇二之 代理工厂ProxyFactory学习

了解AspectJ表达式以及PointCut、Advice、Advisor后&#xff0c;继续学习Spring AOP代理工厂 AspectJ表达式参考&#xff1a;Spring AOP之AspectJ表达式-CSDN博客 PointCut、Advice、Advisor参考&#xff1a;Spring AOP源码篇一之 PointCut、Advice、Advisor学习-CSDN博客 简单…

昇思13天

ResNet50迁移学习 ResNet50迁移学习总结 背景介绍 在实际应用场景中&#xff0c;由于训练数据集不足&#xff0c;很少有人会从头开始训练整个网络。普遍做法是使用在大数据集上预训练得到的模型&#xff0c;然后将该模型的权重参数用于特定任务中。本章使用迁移学习方法对Im…

2.5 C#视觉程序开发实例1----IO_Manager实现切换程序

2.5 C#视觉程序开发实例1----IO_Manager实现切换程序 1 IO_Manager中输入实现 1.0 IO_Manager中输入部分引脚定义 // 设定index 目的是为了今后可以配置这些参数、 // 输入引脚定义 private int index_trig0 0; // trig index private int index_cst 7; //cst index priva…

简单介绍 Dagger2 的入门使用

依赖注入 在介绍 Dagger2 这个之前&#xff0c;必须先解释一下什么是依赖注入&#xff0c;因为这个库就是用来做依赖注入的。所以这里先简单用一句话来介绍一下依赖注入&#xff1a; 依赖注入是一种设计模式&#xff0c;它允许对象在运行时注入其依赖项。而不是在编译时确定&a…

学习数据库2

在数据库中创建一个表student&#xff0c;用于存储学生信息 查看建表结果 向student表中添加一条新记录 记录中id字段的值为1&#xff0c;name字段的值为"monkey"&#xff0c;grade字段的值为98.5 并查看结果 向student表中添加多条新记录 2,"bob"…

Gradle基础:从入门到掌握

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 在现代软件开发中&#xff0c;自动化构建工具是提高效率和管理依赖的重要手段。而Gradle作为一种灵活且强大的构…