OpenCV实战(30)——OpenCV与机器学习的碰撞

news2024/11/25 7:34:33

OpenCV实战(30)——OpenCV与机器学习的碰撞

    • 0. 前言
    • 1. 机器学习简介
    • 2. 基于局部二值模式的最近邻人脸识别
    • 3. 图像表示与人脸识别
    • 4. 完整代码
    • 小结
    • 系列链接

0. 前言

随着人工智能的发展,许多机器学习算法开始用于解决机器视觉问题。机器学习是一个广泛的研究领域,包含许多重要的概念,本节我们将介绍一些主要的机器学习技术,并介绍如何使用 OpenCV 在计算机视觉系统中应用这些技术。

1. 机器学习简介

机器学习的核心是开发可以自行学习如何对数据输入进行处理的计算机系统。机器学习系统无需明确的显式编程,而是根据数据样本自动训练和学习,一旦系统成功完成训练,则训练后的系统可以对新的没有见过的数据输出正确的结果。
机器学习可以用于解决多种类型的问题,但在本节,我们重点是分类问题。通常,为了构建一个可以识别特定类别实例的分类器,必须使用大量带标签的样本来训练分类器。在二分类问题中,样本数据集由表示要学习的类实例的正样本和由不属于感兴趣类实例的负样本组成。从样本数据中,系统将学习能够预测输入实例正确类别的决策函数。
在计算机视觉中,数据样本可以是图像或视频片段。因此,首先需要以一种统一的方式描述每个图像的内容,一种简单的表示是将图像缩放至固定大小,将缩放后的像素的逐行连接形成一个向量,然后将其用作机器学习算法的训练样本。本节中我们将学习不同的图像表示方法,并构建一个经典的人脸识别模型。

2. 基于局部二值模式的最近邻人脸识别

我们首先介绍最近邻分类 (nearest neighbor classification) 以及局部二值模式 (Local Binary Pattern, LBP) 特征,局部二值模式是一种流行的图像表示方法,以独特的方式对图像的纹理图案和轮廓进行编码。
我们将使用以上两种技术解决人脸识别问题。人脸识别一个非常具有挑战性的问题,在过去的 20 年中一直是流行的研究对象,本节中,我们将介绍在 OpenCV 中实现的人脸识别解决方案。
OpenCV 库提供了许多通用 cv::face::FaceRecognizer 类的子类实现的人脸识别方法。在本节中,我们将学习 cv::face::LBPHFaceRecognizer 类,它是一种基于简单但通常有效的分类方法,即最近邻分类器。此外,它使用的图像表示是根据 LBP 特征构建的,这是一种流行的表征图像模式的方式。

(1) 为了创建 cv::face::LBPHFaceRecognizer 类的实例,调用其静态 create 方法:

    cv::Ptr<cv::face::FaceRecognizer> recognizer =
        cv::face::LBPHFaceRecognizer::create(1, // LBP 模式半径
                                        8,      // 要考虑的邻居像素数
                                        8, 8,   // 单元格尺寸
                                        200.);  // 到最近邻居的最小距离

(2) cv::face::LBPHFaceRecognizer 类的前两个参数用于描述要使用的 LBP 特征,然后向识别器提供输入参考人脸图像。输入参考图像需要提供两个向量:一个包含人脸图像,另一个包含相关的标签,标签是整数值,用于标识特定的人物。通过向识别器输入要识别的人的不同图像来训练识别器,输入图像越具有代表性,识别正确人物的机会就越大。在示例中,我们仅提供两个参考人物的两张图像,train 方法是要调用的方法:

    // 参考图像矢量及其标签
    std::vector<cv::Mat> referenceImages;
    std::vector<int> labels;
    // 打开参考图像
    referenceImages.push_back(cv::imread("p0_1.png", cv::IMREAD_GRAYSCALE));
    labels.push_back(0); // person 0
    referenceImages.push_back(cv::imread("p0_2.png", cv::IMREAD_GRAYSCALE));
    labels.push_back(0); // person 0
    referenceImages.push_back(cv::imread("p1_1.png", cv::IMREAD_GRAYSCALE));
    labels.push_back(1); // person 1
    referenceImages.push_back(cv::imread("p1_2.png", cv::IMREAD_GRAYSCALE));
    labels.push_back(1); // person 1
    // 通过计算 LBPH 来训练分类器
    recognizer->train(referenceImages, labels);

(3) 使用的图片如下图所示,第一行是编号为 0 的人物图片,第二行是编号为 1 的人物图片:

人物图片

(4) 参考图像的质量也很重要。此外,我们可以对其执行标准化,即将主要面部特征置于标准化位置。例如,鼻尖位于图像的中间,两只眼睛水平对齐在特定的图像位置,可以使用这种方法自动标准化面部图像的面部特征检测方法。通过提供一个输入图像,模型会预测人脸图像对应的标签:

    // 预测图像标签
    recognizer->predict(inputImage,     // 人脸图像
                        predictedLabel, // 图像的预测标签
                        confidence);    // 预测的置信度

输入图像如下图所示:

输入图像

识别器不仅会返回预测的标签,还会返回相应的置信度分数。在 cv::face::LBPHFaceRecognizer 类中,置信度用于衡量所识别人脸和原模型的差距,该值越低,识别器对其预测的置信度就越高。

3. 图像表示与人脸识别

为了理解本节中介绍的人脸识别方法,接下来,我们将解释它的两个主要组成部分:图像表示和分类方法。
cv::face::LBPHFaceRecognizer 算法利用了 LBP 特性,这是一种描述图像中存在的图像模式的方式。它是一种局部表示,通过对邻域中发现的图像强度模式进行编码,将每个像素转换为二进制表示。为了实现这个目标,需要应用以下规则;将局部像素与其选定的每个相邻像素进行比较,如果其值大于其邻居的值,则将相应位置的值置为 0,否则将其置为 1。最常见的情况下,需要将每个像素与其 8 个直接相邻像素进行比较,从而生成 8 位模式。例如,假设我们有以下局部模式:
[ 87 98 17 21 26 89 19 24 90 ] \left[ \begin{array}{ccc} 87&98&17\\ 21&26&89\\ 19&24&90\\\end{array}\right] 872119982624178990
应用上述规则会生成以下二进制值:
[ 1 1 0 0 1 0 0 1 ] \left[ \begin{array}{ccc} 1&1&0\\ 0&&1\\ 0&0&1\\\end{array}\right] 10010011
取左上角像素作为初始位置并顺时针移动,中心像素被 11011000 的二进制序列替换。通过循环图像的所有像素以生成所有像素相应的 LBP 字节,可以生成完整的 8LBP 图像:

// 计算灰度图像点局部二值特征
void lbp(const cv::Mat &image, cv::Mat &result) {
    assert(image.channels() == 1);      // 输入图像必须为灰度图像
    result.create(image.size(), CV_8U); // 内存分配
    for (int j = 1; j<image.rows - 1; j++) {                    // 循环所有行 (除了第一行和最后一行)
        const uchar* previous = image.ptr<const uchar>(j - 1);  // 上一行
        const uchar* current = image.ptr<const uchar>(j);       // 当前行
        const uchar* next = image.ptr<const uchar>(j + 1);      // 下一行
        uchar* output = result.ptr<uchar>(j);                   // 输出行
        for (int i = 1; i<image.cols - 1; i++) {
            // 局部二值特征
            *output = previous[i - 1] > current[i] ? 1 : 0;
            *output |= previous[i] > current[i] ? 2 : 0;
            *output |= previous[i + 1] > current[i] ? 4 : 0;
            *output |= current[i - 1] > current[i] ? 8 : 0;
            *output |= current[i + 1] > current[i] ? 16 : 0;
            *output |= next[i - 1] > current[i] ? 32 : 0;
            *output |= next[i] > current[i] ? 64 : 0;
            *output |= next[i + 1] > current[i] ? 128 : 0;
            output++;   // 下一像素
        }
    }
    // 将未处理像素置为零
    result.row(0).setTo(cv::Scalar(0));
    result.row(result.rows - 1).setTo(cv::Scalar(0));
    result.col(0).setTo(cv::Scalar(0));
    result.col(result.cols - 1).setTo(cv::Scalar(0));
}

循环体将每个像素与其八个相邻像素进行比较,并分配位值:

原始图像
最后将得到一张 LBP 图像,可以将其显示为灰度图像:

LBP 图像

cv::face::LBPHFaceRecognizer 类中,create 方法的前两个参数通过大小(即以像素为单位的半径)和维度(即沿圆的像素数,可能应用插值)指定要考虑的邻域。生成 LBP 图像后,将图像划分为网格。网格的大小通过 create 方法的第三个参数指定。
对于结果网格中的每个块,构建 LBP 值的直方图。通过将所有这些直方图的 bin 计数连接成一长向量,获得了全局图像表示。使用 8×8 网格,计算出的 256bin 直方图集,形成一个 16384 维向量。
cv::face::LBPHFaceRecognizer 类的 train 方法为提供的每个参考图像生成一个长向量。然后,每个人脸图像都可以看作是高维空间中的一个点。当使用 predict 方法将新图像传递给识别器时,会找到与该图像最近的参考点。因此,与该点相关联的标签是预测标签,置信度值是计算出的距离。通常还会存在另一种情况;如果输入点的最近邻居离它太远,那么这可能意味着这个点实际上不属于任何参考类。我们可以通过 cv::face::LBPHFaceRecognizer 类的 create 方法的第四个参数指定究竟多远的距离才会被视为异常值。
通过将不同的类绘制在表示空间生成不同的点云时,可以观察到此方法的有效性。该方法的另一个优势在于隐式地处理多个类,因为它只是从最近的邻居中得到预测的类别。其缺点在于较高的计算成本,在可能由海量样本点组成的庞大空间中找到最近邻居可能大量时间,且存储所有这些样本点的空间成本也很高。

4. 完整代码

完整代码 recognizeFace.cpp 如下所示:

#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/face.hpp>

// 计算灰度图像点局部二值特征
void lbp(const cv::Mat &image, cv::Mat &result) {
    assert(image.channels() == 1);      // 输入图像必须为灰度图像
    result.create(image.size(), CV_8U); // 内存分配
    for (int j = 1; j<image.rows - 1; j++) {                    // 循环所有行 (除了第一行和最后一行)
        const uchar* previous = image.ptr<const uchar>(j - 1);  // 上一行
        const uchar* current = image.ptr<const uchar>(j);       // 当前行
        const uchar* next = image.ptr<const uchar>(j + 1);      // 下一行
        uchar* output = result.ptr<uchar>(j);                   // 输出行
        for (int i = 1; i<image.cols - 1; i++) {
            // 局部二值特征
            *output = previous[i - 1] > current[i] ? 1 : 0;
            *output |= previous[i] > current[i] ? 2 : 0;
            *output |= previous[i + 1] > current[i] ? 4 : 0;
            *output |= current[i - 1] > current[i] ? 8 : 0;
            *output |= current[i + 1] > current[i] ? 16 : 0;
            *output |= next[i - 1] > current[i] ? 32 : 0;
            *output |= next[i] > current[i] ? 64 : 0;
            *output |= next[i + 1] > current[i] ? 128 : 0;
            output++;   // 下一像素
        }
    }
    // 将未处理像素置为零
    result.row(0).setTo(cv::Scalar(0));
    result.row(result.rows - 1).setTo(cv::Scalar(0));
    result.col(0).setTo(cv::Scalar(0));
    result.col(result.cols - 1).setTo(cv::Scalar(0));
}

int main(){
    cv::Mat image = imread("test_img.png", cv::IMREAD_GRAYSCALE);
    cv::imshow("Original image", image);
    cv::Mat lbpImage;
    lbp(image, lbpImage);
    cv::imshow("LBP image", lbpImage);
    cv::Ptr<cv::face::FaceRecognizer> recognizer =
        cv::face::LBPHFaceRecognizer::create(1, // LBP 模式半径
                                        8,      // 要考虑的邻居像素数
                                        8, 8,   // 单元格尺寸
                                        200.);  // 到最近邻居的最小距离
    // 参考图像矢量及其标签
    std::vector<cv::Mat> referenceImages;
    std::vector<int> labels;
    // 打开参考图像
    referenceImages.push_back(cv::imread("p0_1.png", cv::IMREAD_GRAYSCALE));
    labels.push_back(0); // person 0
    referenceImages.push_back(cv::imread("p0_2.png", cv::IMREAD_GRAYSCALE));
    labels.push_back(0); // person 0
    referenceImages.push_back(cv::imread("p1_1.png", cv::IMREAD_GRAYSCALE));
    labels.push_back(1); // person 1
    referenceImages.push_back(cv::imread("p1_2.png", cv::IMREAD_GRAYSCALE));
    labels.push_back(1); // person 1
    // 4 个正样本
    cv::Mat faceImages(2 * referenceImages[0].rows, 2 * referenceImages[0].cols, CV_8U);
    for (int i = 0; i < 2; i++)
        for (int j = 0; j < 2; j++) { 
            referenceImages[i * 2 + j].copyTo(faceImages(cv::Rect(j*referenceImages[i * 2 + j].cols, i*referenceImages[i * 2 + j].rows, referenceImages[i * 2 + j].cols, referenceImages[i * 2 + j].rows)));
        }
    cv::resize(faceImages, faceImages, cv::Size(), 0.5, 0.5);
    cv::imshow("Reference faces", faceImages);
    // 通过计算 LBPH 来训练分类器
    recognizer->train(referenceImages, labels);
    int predictedLabel = -1;
    double confidence = 0.0;
    // 提取人脸图像
    cv::Mat inputImage;
    cv::resize(image(cv::Rect(300, 75, 150, 150)), inputImage, cv::Size(256, 256));
    cv::imshow("Input image", inputImage);
    // 预测图像标签
    recognizer->predict(inputImage,     // 人脸图像
                        predictedLabel, // 图像的预测标签
                        confidence);    // 预测的置信度
    std::cout << "Image label= " << predictedLabel << " (" << confidence << ")" << std::endl;
    cv::waitKey();
}

小结

机器学习是人工智能的子集,它为计算机以及其它具有计算能力的系统提供自动预测或决策的能力,诸如虚拟助理、车牌识别系统、智能推荐系统等机器学习应用程序给我们的日常生活带来了便捷的体验。本节中,我们介绍了如何在 OpenCV 计算机视觉应用程序中机器学习算法,并以人脸识别为例体验了人工智能的强大之处。

系列链接

OpenCV实战(1)——OpenCV与图像处理基础
OpenCV实战(2)——OpenCV核心数据结构
OpenCV实战(3)——图像感兴趣区域
OpenCV实战(4)——像素操作
OpenCV实战(5)——图像运算详解
OpenCV实战(6)——OpenCV策略设计模式
OpenCV实战(7)——OpenCV色彩空间转换
OpenCV实战(8)——直方图详解
OpenCV实战(9)——基于反向投影直方图检测图像内容
OpenCV实战(10)——积分图像详解
OpenCV实战(11)——形态学变换详解
OpenCV实战(12)——图像滤波详解
OpenCV实战(13)——高通滤波器及其应用
OpenCV实战(14)——图像线条提取
OpenCV实战(15)——轮廓检测详解
OpenCV实战(16)——角点检测详解
OpenCV实战(17)——FAST特征点检测
OpenCV实战(18)——特征匹配
OpenCV实战(19)——特征描述符
OpenCV实战(20)——图像投影关系
OpenCV实战(21)——基于随机样本一致匹配图像
OpenCV实战(22)——单应性及其应用
OpenCV实战(23)——相机标定
OpenCV实战(24)——相机姿态估计
OpenCV实战(25)——3D场景重建
OpenCV实战(26)——视频序列处理
OpenCV实战(27)——追踪视频中的特征点
OpenCV实战(28)——光流估计
OpenCV实战(29)——视频对象追踪

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

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

相关文章

SoC 总线结构学习记录之系统存储总线(System Memory Bus)与私有设备总线

蜂鸟 E203 SOC总线结构&#xff1a;  蜂鸟 E203 内核 BIU 的系统存储接口 ICB 连接系统存储总线&#xff0c;通过其访问 SoC 中的若干存储组件&#xff0c;譬如 ROM&#xff0c;Flash 的只读区间等。  蜂鸟 E203 内核 BIU 的私有设备接口 ICB 连接私有设备总线&#xff0c…

Java 编程中的魔法之门:探索I/O流的奇妙世界

文章目录 什么是I/O流&#xff1f;I/O 流的层次结构1. 字节流&#xff08;Byte Streams&#xff09;2. 字符流&#xff08;Character Streams&#xff09;3. 缓冲流&#xff08;Buffered Streams&#xff09;4. 数据流&#xff08;Data Streams&#xff09;5. 对象流&#xff0…

STM32 FREERTOS osDelayUntil()异常

问题&#xff1a; 在使用osDelayUntil&#xff08;&#xff09;进行固定延时时发现不起作用&#xff0c;程序不能按照预期的延时进行执行&#xff08;比延时要快&#xff09;。 #define taskMBSysManage_Delay_TIME 1000 TickType_t xLastWakeTime; xLastWakeTime xTaskGe…

软考A计划-网络工程师-复习背熟-广域网和接入网技术和因特网

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列点击跳转>蓝桥系列 &#x1f449;关于作者 专注于Android/Unity和各种游…

高忆管理:庄家尾盘拉升的目的?

股市里的庄家是指一些出资者和基金司理&#xff0c;他们以高明的技巧和强大的实力操作股市。庄家尾盘拉升则是指在股市收盘前&#xff0c;庄家在商场进行一些操盘操作&#xff0c;然后推高股价。那么庄家尾盘拉升的目的是什么&#xff1f;在本文中&#xff0c;我们将从多个角度…

33、Flink之hive介绍与简单示例

Flink 系列文章 1、Flink 部署、概念介绍、source、transformation、sink使用示例、四大基石介绍和示例等系列综合文章链接 13、Flink 的table api与sql的基本概念、通用api介绍及入门示例 14、Flink 的table api与sql之数据类型: 内置数据类型以及它们的属性 15、Flink 的ta…

油耳的人适合戴哪种耳机,油耳戴什么耳机不会掉

长时间佩戴入耳式耳机导致油耳朵&#xff1f;甚至耳朵受伤。这似乎是一个不可避免的问题&#xff0c;但不同耳机对听力的伤害程度却不尽相同。然而&#xff0c;目前为止&#xff0c;骨传导耳机是唯一一种在相同音量下对听力损伤最低的选择。这种耳机通过骨骼传递声音&#xff0…

亮数据:以色列一家让人向往的互联网公司,很强

大家好&#xff0c;我是二哥呀&#xff01; 今天再给大家推荐一家小而美的互联网公司——亮数据&#xff0c;成立于 2014 年&#xff0c;总部设在以色列&#xff0c;全球目前约有 500 名员工&#xff0c;但却有超过 2000 个专利申报和计数。 我在他们的官网看到这样一段介绍&…

关于Echarts 绘制玫瑰图 (笔记)

目录 基于js文件绘图 基于vue3绘制玫瑰图 基于js文件绘图 // 定义一个配置对象 var option {// 图例设置legend: {top: bottom},// 工具栏设置toolbox: {show: true,feature: {mark: { show: true }, // 标记工具dataView: { show: true, readOnly: false }, // 数据视图工具r…

软件安全检测有哪些测试内容?怎么做

安全测试 信息安全检测是一个统称的概念&#xff0c;其概念的提出对于规范和明确信息安全日常工作具有重要作用。一般企业会委托第三方检测机构进行信息安全检测并且出具相关的软件安全检测报告。 信息安全检测依据是什么 根据国家标准、行业标准、地方标准或相关技术规范&a…

不会电脑分区?看这里,教你轻松学会!

随着计算机技术的不断发展&#xff0c;电脑分区成为管理和优化硬盘空间的重要手段之一。它是对硬盘进行逻辑划分的过程&#xff0c;通过将硬盘分成不同的区域&#xff0c;提高数据访问效率&#xff0c;同时保护系统和用户数据的安全性。本文将为您介绍两种常用的电脑分区方法&a…

螺线管线圈的用途是什么

螺线管线圈是一种电子元器件&#xff0c;通常用于电感器和变压器。螺线管线圈可以是单层的或多层的&#xff0c;并且可以根据特定的电气参数进行设计。它们被广泛应用于电子设备和通信系统中&#xff0c;以满足各种应用的要求。 螺线管线圈主要用于电感器和变压器。电感器是一种…

科技云报道:AI+云计算共生共长,能否解锁下一个高增长空间?

科技云报道原创。 在过去近一年的时间里&#xff0c;AI大模型从最初的框架构建&#xff0c;逐步走到落地阶段。 然而&#xff0c;随着AI大模型深入到千行百业中&#xff0c;市场开始意识到通用大模型虽然功能强大&#xff0c;但似乎并不能完全满足不同企业的个性化需求。 大…

开源项目-内容管理系统

哈喽,大家好,今天给大家带来一个开源项目-内容管理系统。项目通过SpringBoot实现 主要功能有 - 内容:发帖、评论、帖子分类、分页、回帖统计、访问统计、表单验证 - 用户:权限、资料、头像、邮箱验证 - 管理:后台管理、统计图表、帖子/分类管理 注册 通过用户名,邮箱,…

实现基于栈的表达式求值计算器(难度4/10)

本作业主要考察&#xff1a;解释器模式的实现思想/栈结构在表达式求值方面的绝对优势 C数据结构与算法夯实基础作业列表 通过栈的应用&#xff0c;理解特定领域设计的关键作用&#xff0c;给大家眼前一亮的感觉。深刻理解计算机语言和人类语言完美结合的杰作。是作业中的上等…

长胜证券:华为“黑科技”点燃A股炒作激情

8月29日&#xff0c;在未举行相关发布会的情况下&#xff0c;华为新款手机Mate60Pro悄然上线开售&#xff0c;并在一小时内售罄。 金融出资报记者注意到&#xff0c;跟着商场对新机重视的继续发酵&#xff0c;其中的各种技能打破也愈加受到重视&#xff0c;其影响很快扩散到资…

前端(十五)——开源一个用react封装的图片预览组件

&#x1f475;博主&#xff1a;小猫娃来啦 &#x1f475;文章核心&#xff1a;开源一个react封装的图片预览组件 文章目录 组件开源代码下载地址运行效果展示实现思路使用思路和api实现的功能数据和入口部分代码展示 组件开源代码下载地址 Gitee&#xff1a;点此跳转下载 CSDN…

叉式移动机器人(AGV/AMR)解决方案

叉式移动机器人&#xff08;AGV/AMR&#xff09;是在叉车上加载各种导引技术&#xff0c;构建地图算法&#xff0c;辅以避障安全技术&#xff0c;实现叉车的无人化作业。 ▲ 叉式移动机器人无人化作业 ▲叉式移动机器人常见车型 叉式移动机器人在行业初期&#xff0c;各机构、…

Hugging Face--Transformers

pipeline 在这里插入图片描述 AutoClass AutoClass 是一个能够通过预训练模型的名称或路径自动查找其架构的快捷方式. 你只需要为你的任务选择合适的 AutoClass 和它关联的预处理类。 AutoTokenizer AutoModel 保存模型 自定义模型构建 Trainer - PyTorch优化训练循环 参考资…

概念解析 | 量子时代的灵感:探索量子感知技术

注1:本文系“概念解析”系列之一,致力于简洁清晰地解释、辨析复杂而专业的概念。本次辨析的概念是:量子感知技术。 量子时代的灵感:探索量子感知技术 量子感知技术是一个充满希望和挑战的新兴领域。在此,我们将深入探讨这个主题,概述其背景,解释其工作原理,讨论现有的…