【光电智造】给OpenCV初学者的礼物——OpenCV人脸检测入门教程

今日光电 2024-11-25 18:01

 今日光电 

     有人说,20世纪是电的世纪,21世纪是光的世纪;知光解电,再小的个体都可以被赋能。追光逐电,光赢未来...欢迎来到今日光电!




----追光逐电 光赢未来----


前言

OpenCV是一种经常被用到的计算机视觉库。然而,它的文档是只用英文发布的。这对习惯中文阅读的国内计算机爱好者来说并不是太友好,特别是对那些还没受过高等教育但对计算机科学抱有美好向往的普通大众。


OpenCV各版本间的使用方法并不是完全统一的。翻译工作与官方数据手册的发布不可避免的会有滞后性,虽然本文作者已经尽可能选取目前最新版本的文档了,但是这些内容也不可避免的会有一天变成过时的糟粕。所以提醒初学者们,尽信书不如无书。


很荣幸能成为大家学习OpenCV的领路人。作者祝大家都能在学习的过程中找人生到真正的意义。


本文的参考文档见https://docs.opencv.org/4.0.1/d1/dfb/intro.html





图像的基本操作





对于人类来说,图像可以解构为画面结构、色彩和非常丰富的意象。你可以把它解构为各种色块或线条,然后用故事性的语言把这幅图像存在脑海里。


反过来,对于计算机来说,这个过程就要机械得多了,计算机只认识组成一副图像的那一个个像素。为了存储这一个个像素,需要像素的坐标和色彩信息。OpenCV以一种叫Mat的结构存储图像,你可以把它理解为一种数据结构,结构上是下图这样的一个表格,表格的行列分布代表像素的行列。



在上图的各个像素中,颜色又用三原色来表示,即BGR(蓝绿红)。这些BGR数据依次排列如下图。其中,三原色里的每个颜色元素的值域是0-255(即8位),因此每个像素具有8x3位(即24位真彩色)。



我们来建立一个Mat数据M,存储一个最简单的2x2像素的图像,且每个像素为蓝色(0,0,255),可以这样写。要注意的是,目前最新的OpenCV 4.0.1已经不支持C了,代码文件需要按C++写


Mat M(2,2, CV_8UC3, Scalar(0,0,255));//新建2x2像素图像

imshow("image", M);//显示M的图像


当然,大多数时候,我们是不会这样傻傻地新建一个图像的。我们可以直接从一个jpg文件里面读取图像,我将把读取图片文件的方法教给大家。


举个例子,把一个叫Lena的照片放进一个叫M的Mat结构,可以像下面这么写。另外,我们还演示了怎么把一个彩色图像以灰度图的方式读取。


Mat M = imread("lena.jpg");//图像来自图片文件

imshow("image", M);//显示M的图像

Mat img = imread("lena.jpg", IMREAD_GRAYSCALE);//以灰度形式读取图片文件

imshow("grayimage", img);//显示img的图像(它是灰度图)


反过来,一个Mat数据,也可以写入文件,这样就把图片给存起来了。举个例子,读取Lena的照片文件,然后存到一个叫out的文件里。


imwrite("image.jpg", M);//显示M的图像

Mat img = imread("lena.jpg", IMREAD_GRAYSCALE);//以灰度形式读取图片文件

imwrite("grayimage.jpg", img);//显示img的图像(它是灰度图)





一个人脸检测的例子





这个例子,需要引用3个OpenCV的头文件:


#include "opencv2/objdetect.hpp"

#include "opencv2/highgui.hpp"

#include "opencv2/imgproc.hpp"


当然,也可以一步到位,把所有的OpenCv库都引了


#include "opencv2/opencv.hpp"


应用iostream库,并使用命名空间来减少代码里面文字的输入量


#include

using namespace std;

using namespace cv;


定义两个分类器,分别用来检测脸和眼睛


CascadeClassifier face_cascade;

CascadeClassifier eyes_cascade;


加载已经训练好的分类器,其中face_cascade_name和eyes_cascade_name是xml格式的分类器文件名。(OpenCV的Github里有已经训练好的模型,可以免费下载)


face_cascade.load( face_cascade_name );

eyes_cascade.load( eyes_cascade_name );


定义一个检测人脸并显示检测结果的函数,函数的输入变量是Mat图像。


void detectAndDisplay( Mat frame )


输入的Mat变量frame是彩色的,色彩信息对检测没什么用。所以我们把它转成灰度图,并标准化。


Mat frame_gray;

cvtColor( frame, frame_gray, COLOR_BGR2GRAY );//转灰度

equalizeHist( frame_gray, frame_gray );//标准化


一张图里有可能会检测到多个人脸,我们可以把检测结果用一个叫faces的向量来存储。用分类器进行多尺度检测的函数叫detectMultiScale,这个函数有两个变量,第一个是输入的图像frame_gray,第二个是输出的结果faces。


std::vector faces;//准备要存入人脸检测结果的向量

face_cascade.detectMultiScale( frame_gray, faces );//检测


由于faces是把检测结果存成了一个向量,所以我们可以用size()函数得到这个向量的长度。下面这个代码循环遍历了每个结果,计算了每张脸的位置。


for ( size_t i = 0; i < faces.size(); i++ )

{

Point center( faces[i].x + faces[i].width/2, faces[i].y + faces[i].height/2 );//获得每张脸部中心的xy像素坐标

ellipse( frame, center, Size( faces[i].width/2, faces[i].height/2 ), 0, 0, 360, Scalar( 255, 0, 255 ), 4 );//给每个脸画个圈圈框起来

}


上面这个循环里,还可以把检测到个每个脸的区域单独提取出来,再检测一下眼睛。然后嵌入一个用圈圈框出眼睛的循环。这一步,依然使用detectMultiScale函数进行检测。


Mat faceROI = frame_gray( faces[i] );//提取脸部区域

std::vector eyes;//准备要存入眼睛检测结果的向量

eyes_cascade.detectMultiScale( faceROI, eyes );//检测眼睛

for ( size_t j = 0; j < eyes.size(); j++ )

{

Point eye_center( faces[i].x + eyes[j].x + eyes[j].width/2, faces[i].y + eyes[j].y + eyes[j].height/2 );//获得每个眼睛的中心

int radius = cvRound( (eyes[j].width + eyes[j].height)*0.25 );

circle( frame, eye_center, radius, Scalar( 255, 0, 0 ), 4 );//把每个眼睛用圈圈框起来

}


接下来,我们把上面的内容整合一下,这个人脸检测并显示检测结果的函数


detectAndDisplay可以写成这样

void detectAndDisplay( Mat frame )

{

Mat frame_gray;

cvtColor( frame, frame_gray, COLOR_BGR2GRAY );

equalizeHist( frame_gray, frame_gray );

//-- Detect faces

std::vector faces;

face_cascade.detectMultiScale( frame_gray, faces );

for ( size_t i = 0; i < faces.size(); i++ )

{

Point center( faces[i].x + faces[i].width/2, faces[i].y + faces[i].height/2 );

ellipse( frame, center, Size( faces[i].width/2, faces[i].height/2 ), 0, 0, 360, Scalar( 255, 0, 255 ), 4 );

Mat faceROI = frame_gray( faces[i] );

//-- In each face, detect eyes

std::vector eyes;

eyes_cascade.detectMultiScale( faceROI, eyes );

for ( size_t j = 0; j < eyes.size(); j++ )

{

Point eye_center( faces[i].x + eyes[j].x + eyes[j].width/2, faces[i].y + eyes[j].y + eyes[j].height/2 );

int radius = cvRound( (eyes[j].width + eyes[j].height)*0.25 );

circle( frame, eye_center, radius, Scalar( 255, 0, 0 ), 4 );

}

}

//-- Show what you got

imshow( "Capture - Face detection", frame );

}


实际中,循环读摄像头并用这个detectAndDisplay函数进行检测的代码可以这么写。


Mat frame;//存放摄像头捕获图像的frame变量,它是个Mat数据

while ( capture.read(frame) )//循环把摄像头图像放入frame变量

{

detectAndDisplay( frame );//检测人脸并显示结果

}


最后,我们来写一下主函数,把上面那个摄像头读取和人脸检测功能加上。


int main( int argc, const char** argv )

{

CommandLineParser parser(argc, argv,

"{help h||}"

"{face_cascade|../../data/haarcascades/haarcascade_frontalface_alt.xml|Path to face cascade.}"

"{eyes_cascade|../../data/haarcascades/haarcascade_eye_tree_eyeglasses.xml|Path to eyes cascade.}"

"{camera|0|Camera device number.}");

parser.about( "\nThis program demonstrates using the cv::CascadeClassifier class to detect objects (Face + eyes) in a video stream.\n"

"You can use Haar or LBP features.\n\n" );

parser.printMessage();

String face_cascade_name = parser.get("face_cascade");

String eyes_cascade_name = parser.get("eyes_cascade");

//-- 1. Load the cascades

if( !face_cascade.load( face_cascade_name ) )

{

cout << "--(!)Error loading face cascade\n";

return -1;

};

if( !eyes_cascade.load( eyes_cascade_name ) )

{

cout << "--(!)Error loading eyes cascade\n";

return -1;

};

int camera_device = parser.get("camera");

VideoCapture capture;

//-- 2. Read the video stream

capture.open( camera_device );

if ( ! capture.isOpened() )

{

cout << "--(!)Error opening video capture\n";

return -1;

}

Mat frame;

while ( capture.read(frame) )

{

if( frame.empty() )

{

cout << "--(!) No captured frame -- Break!\n";

break;

}

//-- 3. Apply the classifier to the frame

detectAndDisplay( frame );

if( waitKey(10) == 27 )

{

break; // escape

}

}

return 0;

}


人脸检测完整的代码可去下方下载


https://github.com/opencv/opencv/blob/master/samples/cpp/tutorial_code/objectDetection/objectDetection.cpp


已经训练好的分类器可去下方下载


https://github.com/opencv/opencv/tree/master/data/haarcascades


来源:深度学习冲鸭


申明:感谢原创作者的辛勤付出。本号转载的文章均会在文中注明,若遇到版权问题请联系我们处理。


 

----与智者为伍 为创新赋能----


【说明】欢迎企业和个人洽谈合作,投稿发文。欢迎联系我们
诚招运营合伙人 ,对新媒体感兴趣,对光电产业和行业感兴趣。非常有意者通过以下方式联我们!条件待遇面谈
投稿丨合作丨咨询

联系邮箱:uestcwxd@126.com

QQ:493826566




评论
  •  光伏及击穿,都可视之为 复合的逆过程,但是,复合、光伏与击穿,不单是进程的方向相反,偏置状态也不一样,复合的工况,是正偏,光伏是零偏,击穿与漂移则是反偏,光伏的能源是外来的,而击穿消耗的是结区自身和电源的能量,漂移的载流子是 客席载流子,须借外延层才能引入,客席载流子 不受反偏PN结的空乏区阻碍,能漂不能漂,只取决于反偏PN结是否处于外延层的「射程」范围,而穿通的成因,则是因耗尽层的过度扩张,致使跟 端子、外延层或其他空乏区 碰触,当耗尽层融通,耐压 (反向阻断能力) 即告彻底丧失,
    MrCU204 2025-01-17 11:30 179浏览
  • 现在为止,我们已经完成了Purple Pi OH主板的串口调试和部分配件的连接,接下来,让我们趁热打铁,完成剩余配件的连接!注:配件连接前请断开主板所有供电,避免敏感电路损坏!1.1 耳机接口主板有一路OTMP 标准四节耳机座J6,具备进行音频输出及录音功能,接入耳机后声音将优先从耳机输出,如下图所示:1.21.2 相机接口MIPI CSI 接口如上图所示,支持OV5648 和OV8858 摄像头模组。接入摄像头模组后,使用系统相机软件打开相机拍照和录像,如下图所示:1.3 以太网接口主板有一路
    Industio_触觉智能 2025-01-20 11:04 150浏览
  • 临近春节,各方社交及应酬也变得多起来了,甚至一月份就排满了各式约见。有的是关系好的专业朋友的周末“恳谈会”,基本是关于2025年经济预判的话题,以及如何稳定工作等话题;但更多的预约是来自几个客户老板及副总裁们的见面,他们为今年的经济预判与企业发展焦虑而来。在聊天过程中,我发现今年的聊天有个很有意思的“点”,挺多人尤其关心我到底是怎么成长成现在的多领域风格的,还能掌握一些经济趋势的分析能力,到底学过哪些专业、在企业管过哪些具体事情?单单就这个一个月内,我就重复了数次“为什么”,再辅以我上次写的:《
    牛言喵语 2025-01-22 17:10 30浏览
  •  万万没想到!科幻电影中的人形机器人,正在一步步走进我们人类的日常生活中来了。1月17日,乐聚将第100台全尺寸人形机器人交付北汽越野车,再次吹响了人形机器人疯狂进厂打工的号角。无独有尔,银河通用机器人作为一家成立不到两年时间的创业公司,在短短一年多时间内推出革命性的第一代产品Galbot G1,这是一款轮式、双臂、身体可折叠的人形机器人,得到了美团战投、经纬创投、IDG资本等众多投资方的认可。作为一家成立仅仅只有两年多时间的企业,智元机器人也把机器人从梦想带进了现实。2024年8月1
    刘旷 2025-01-21 11:15 360浏览
  • Ubuntu20.04默认情况下为root账号自动登录,本文介绍如何取消root账号自动登录,改为通过输入账号密码登录,使用触觉智能EVB3568鸿蒙开发板演示,搭载瑞芯微RK3568,四核A55处理器,主频2.0Ghz,1T算力NPU;支持OpenHarmony5.0及Linux、Android等操作系统,接口丰富,开发评估快人一步!添加新账号1、使用adduser命令来添加新用户,用户名以industio为例,系统会提示设置密码以及其他信息,您可以根据需要填写或跳过,命令如下:root@id
    Industio_触觉智能 2025-01-17 14:14 121浏览
  • 数字隔离芯片是一种实现电气隔离功能的集成电路,在工业自动化、汽车电子、光伏储能与电力通信等领域的电气系统中发挥着至关重要的作用。其不仅可令高、低压系统之间相互独立,提高低压系统的抗干扰能力,同时还可确保高、低压系统之间的安全交互,使系统稳定工作,并避免操作者遭受来自高压系统的电击伤害。典型数字隔离芯片的简化原理图值得一提的是,数字隔离芯片历经多年发展,其应用范围已十分广泛,凡涉及到在高、低压系统之间进行信号传输的场景中基本都需要应用到此种芯片。那么,电气工程师在进行电路设计时到底该如何评估选择一
    华普微HOPERF 2025-01-20 16:50 73浏览
  • 随着消费者对汽车驾乘体验的要求不断攀升,汽车照明系统作为确保道路安全、提升驾驶体验以及实现车辆与环境交互的重要组成,日益受到业界的高度重视。近日,2024 DVN(上海)国际汽车照明研讨会圆满落幕。作为照明与传感创新的全球领导者,艾迈斯欧司朗受邀参与主题演讲,并现场展示了其多项前沿技术。本届研讨会汇聚来自全球各地400余名汽车、照明、光源及Tier 2供应商的专业人士及专家共聚一堂。在研讨会第一环节中,艾迈斯欧司朗系统解决方案工程副总裁 Joachim Reill以深厚的专业素养,主持该环节多位
    艾迈斯欧司朗 2025-01-16 20:51 195浏览
  • 2024年是很平淡的一年,能保住饭碗就是万幸了,公司业绩不好,跳槽又不敢跳,还有一个原因就是老板对我们这些员工还是很好的,碍于人情也不能在公司困难时去雪上加霜。在工作其间遇到的大问题没有,小问题还是有不少,这里就举一两个来说一下。第一个就是,先看下下面的这个封装,你能猜出它的引脚间距是多少吗?这种排线座比较常规的是0.6mm间距(即排线是0.3mm间距)的,而这个规格也是我们用得最多的,所以我们按惯性思维来看的话,就会认为这个座子就是0.6mm间距的,这样往往就不会去细看规格书了,所以这次的运气
    wuliangu 2025-01-21 00:15 181浏览
  • 日前,商务部等部门办公厅印发《手机、平板、智能手表(手环)购新补贴实施方案》明确,个人消费者购买手机、平板、智能手表(手环)3类数码产品(单件销售价格不超过6000元),可享受购新补贴。每人每类可补贴1件,每件补贴比例为减去生产、流通环节及移动运营商所有优惠后最终销售价格的15%,每件最高不超过500元。目前,京东已经做好了承接手机、平板等数码产品国补优惠的落地准备工作,未来随着各省市关于手机、平板等品类的国补开启,京东将第一时间率先上线,满足消费者的换新升级需求。为保障国补的真实有效发放,基于
    华尔街科技眼 2025-01-17 10:44 221浏览
  • 高速先生成员--黄刚这不马上就要过年了嘛,高速先生就不打算给大家上难度了,整一篇简单但很实用的文章给大伙瞧瞧好了。相信这个标题一出来,尤其对于PCB设计工程师来说,心就立马凉了半截。他们辛辛苦苦进行PCB的过孔设计,高速先生居然说设计多大的过孔他们不关心!另外估计这时候就跳出很多“挑刺”的粉丝了哈,因为翻看很多以往的文章,高速先生都表达了过孔孔径对高速性能的影响是很大的哦!咋滴,今天居然说孔径不关心了?别,别急哈,听高速先生在这篇文章中娓娓道来。首先还是要对各位设计工程师的设计表示肯定,毕竟像我
    一博科技 2025-01-21 16:17 95浏览
  •     IPC-2581是基于ODB++标准、结合PCB行业特点而指定的PCB加工文件规范。    IPC-2581旨在替代CAM350格式,成为PCB加工行业的新的工业规范。    有一些免费软件,可以查看(不可修改)IPC-2581数据文件。这些软件典型用途是工艺校核。    1. Vu2581        出品:Downstream     
    电子知识打边炉 2025-01-22 11:12 49浏览
  • 嘿,咱来聊聊RISC-V MCU技术哈。 这RISC-V MCU技术呢,简单来说就是基于一个叫RISC-V的指令集架构做出的微控制器技术。RISC-V这个啊,2010年的时候,是加州大学伯克利分校的研究团队弄出来的,目的就是想搞个新的、开放的指令集架构,能跟上现代计算的需要。到了2015年,专门成立了个RISC-V基金会,让这个架构更标准,也更好地推广开了。这几年啊,这个RISC-V的生态系统发展得可快了,好多公司和机构都加入了RISC-V International,还推出了不少RISC-V
    丙丁先生 2025-01-21 12:10 111浏览
  • 本文介绍瑞芯微开发板/主板Android配置APK默认开启性能模式方法,开启性能模式后,APK的CPU使用优先级会有所提高。触觉智能RK3562开发板演示,搭载4核A53处理器,主频高达2.0GHz;内置独立1Tops算力NPU,可应用于物联网网关、平板电脑、智能家居、教育电子、工业显示与控制等行业。源码修改修改源码根目录下文件device/rockchip/rk3562/package_performance.xml并添加以下内容,注意"+"号为添加内容,"com.tencent.mm"为AP
    Industio_触觉智能 2025-01-17 14:09 161浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦