• [问题求助] 问下论坛大佬们,人脸的侧脸识别不是很精确如何解决?
    业务里需要对视频里出现的人的面部打马赛克,但是用了几个模型效果都不太理想(OpenCV自带的、CNN模型,开源的面部检测模式都测试了)。 正脸,一般的侧脸可以完美识别。 但是低头、半侧脸这种就识别不到,华为云有没有人脸的模型SDK或者在线API,我去试试? 或者有没有推荐的开源面部模型和算法推荐推荐。
  • 浅谈OpenCV的八种目标跟踪算法
    1、GOTURN TrackerGoturn是一种基于深度学习的对象跟踪算法。最初的实现是在Caffe,目前已经移植到OpenCV跟踪API。Goturn是一种基于深度学习的跟踪算法,是回归网络的一般对象跟踪的缩写。大多数跟踪算法都是在线训练的。换句话说,跟踪算法学习运行时跟踪的对象的外观。因此,许多实时追踪器依赖于在线学习算法,这通常比基于深度学习的解决方案快得多。Goturn改变了我们将深度学习应用于跟踪问题的方式,通过离线方式学习对象的运动。Goturn模型接受了数千个视频序列的训练,不需要在运行时执行任何学习。与其他基于深度学习的追踪器相比,Goturn速度更快。它在caffe的gpu上以100fps的速度运行,在opencv cpu上以20fps的速度运行。尽管跟踪器是通用的,但理论上,通过将传输集与特定类型的对象进行偏移,可以在特定对象(例如行人)上获得更好的结果。局限性:神经网络体现的优势,往往就是它的劣势。神经网络依赖于训练集中样本所能代表的场景种类,对于不存在的场景,就会存在问题。如在实际使用中,希望跟踪手掌,把手掌移到脸上时,跟踪器锁定在脸上,并不会在手掌上。而跟踪脸,并用手堵遮住脸,但追踪器能够跟踪通过遮挡的脸,这说明训练集存在大量的手掌遮脸的场景。2、MIL Tracker 密尔跟踪器这个跟踪器的概念与上面描述的BOOSTING Tracker相似。最大的区别是,它不只是将对象的当前位置视为一个正示例,还会在当前位置周围的一个小邻域中查找,以生成几个潜在的正示例。你可能认为这是一个坏主意,因为在这些“积极”的例子中,大多数的对象都不是中心。这就是多实例学习(mil)来拯救的地方。在mil中,您不指定正负示例,而是指定正负“bags”。正面的图像收集并非都是正面的例子。一个正面的bag包含了以对象当前位置为中心的区域,以及它周围的一个小邻域中的区域。即使被跟踪对象的当前位置不准确,当来自当前位置附近的样本放入正袋中时,很有可能该袋至少包含一个图像,并且该对象很好地居中。优点:性能不错。它不会像助推跟踪器那样漂移,并且在部分遮挡下也能正常工作。缺点:失败率较高。3、BOOSTING Tracker助推跟踪器该跟踪器基于ADaboost的在线版本,ADaboost是基于HAAR级联的人脸检测器内部使用的算法。这个分类器需要在运行时用对象的正负示例进行培训。以用户(或其他对象检测算法)提供的初始边界框为对象的正例,边界框外的许多图像部位作为背景。给定一个新的帧,分类器在前一个位置附近的每个像素上运行,并记录分类器的得分。对象的新位置是得分最大的位置。缺点:速度较慢,并且表现不好,跟踪失败后,不能及时呈现错误报告。4、KCF跟踪器 KCF代表kernelized correlation filters。这个追踪器建立在前两个追踪器中提出的想法之上。该跟踪器利用了这样一个事实:在MIL跟踪器中使用的多个正样本具有较大的重叠区域。这些重叠的数据导致了一些很好的数学特性,这些特性被跟踪器利用,从而使跟踪速度更快、更准确。优点:准确度和速度都比MIL跟踪器好,它报告跟踪故障比BOOSTING和MIL这两个追踪算法好。缺点:无法从完全遮挡中恢复。5、TLD跟踪器 TLD代表跟踪、学习和检测。顾名思义,这个跟踪器将长期跟踪任务分解为三个组件(短期)跟踪、学习和检测。在作者的论文中,“跟踪器跟踪对象从一帧到另一帧。探测器定位到目前为止观察到的所有外观,并在必要时纠正跟踪器。学习估计检测器的错误并更新它以避免将来出现这些错误。”这个跟踪器的输出有点跳跃。例如,如果您正在跟踪一个行人,并且场景中还有其他行人,则此跟踪器有时可以临时跟踪一个与您要跟踪的行人不同的行人。在积极的一面,这条轨迹似乎是在更大的比例、运动和遮挡上跟踪一个对象。如果你有一个隐藏在另一个物体后面的视频序列,这个跟踪器可能是个不错的选择。 优点:在多帧遮挡下效果最好。此外,跟踪最佳的超比例变化。 缺点:很多误报使它几乎不可用。6、MEDIANFLOW跟踪器在内部,这个跟踪器可以实时地跟踪物体的前后方向,并测量这两个轨迹之间的差异。最大限度地减少这种向前向后的误差,使他们能够可靠地检测跟踪故障,并在视频序列中选择可靠的轨迹。在测试中发现这个跟踪器在运动可预测和对象小的情况下工作得最好。与其他跟踪者不同的是,即使跟踪明显失败,跟踪者也知道跟踪何时失败。 优点:出色的跟踪故障报告。当运动是可预测的并且没有遮挡时,效果非常好。 缺点:大幅度运动跟踪,模型会失效。7、MOSSE 莫斯跟踪器最小平方误差输出和(mosse)使用自适应相关进行对象跟踪,当使用单帧进行初始化时,可产生稳定的相关滤波器。Mosse跟踪器对光照、比例、姿势和非刚性变形的变化具有鲁棒性。对于遮挡,跟踪器能够在对象重新出现时暂停并恢复到停止的位置。优点:速度快。缺点:准确率不如CSRT和KCF高。8、CSRT跟踪器在具有信道和空间可靠性的鉴别相关滤波器(DCF-CSR)中,我们使用空间可靠性图从帧中调整滤波器支持到所选区域的一部分进行跟踪。这样可以确保选定区域的放大和定位,并改进对非矩形区域或对象的跟踪。它只使用两个标准功能(HoGs and Colornames)。它也在相对较低的fps(25 fps)下工作,但提供了更高的目标跟踪精度。优点:比KCR精度高。缺点:速度较慢。
  • [技术干货] OpenCV实战(2)——OpenCV核心数据结构-转载
    0. 前言cv::Mat 类是用于保存图像(以及其他矩阵数据)的数据结构,该数据结构是所有 OpenCV 类和函数的核心,这是 OpenCV 库的一个关键元素,用于处理图像和矩阵(从计算和数学的角度来看,图像本质上是一个矩阵),同时 cv::Mat 数据结构结合了优雅的内存管理机制,因此,使用起来十分高效。此数据结构在应用程序开发中广泛使用,因此再进一步学习前我们必须熟悉 cv::Mat 数据结构。1. cv::Mat 数据结构1.1 cv::Mat 简介cv::Mat 数据结构基本上由两部分组成:标头 (header) 和数据块 (data block)。标头包含与矩阵相关的所有信息(尺寸大小、通道数、数据类型等)。我们已经介绍了如何访问包含在 cv::Mat 标头中的一些属性,例如,通过使用 cols 、rows 或 channels 访问矩阵的列数、行数或通道数;数据块包含图像的所有像素值,标头中包含一个指向这个数据块的指针变量,即 data 属性。cv::Mat 数据结构的一个重要特性是内存块仅在明确请求时才被复制,大多数操作只是简单地复制 cv::Mat 标头,这样多个对象本质上同时指向同一个数据块,这种内存管理模型使应用程序更高效,同时可以避免内存泄漏。1.2 cv::Mat 属性接下来,我们通过编写测试程序 (mat.cpp) 来测试 cv::Mat 数据结构的不同属性。1. 首先,声明 opencv 头文件和 c++ i/o 流文件:#include#include#include2. 创建函数来生成新的灰度图像,其所有像素具有相同的默认值:cv::Mat function() {// 创建新图像并返回cv::Mat ima(500, 500, CV_8U, 50);return ima;}默认情况下, cv::Mat 对象在创建时的大小为零,但也可以指定初始大小:cv::Mat image1(240,320,CV_8U,100);1如果指定图像初始大小,就需要指定每个矩阵元素的类型,以上代码使用 CV_8U 类型,对应创建的图像像素为 1 字节 (8 位),字母 U 表示它是无符号的;还可以使用字母 S 声明有符号整数类型。对于彩色图像,需要指定三个通道 (CV_8UC3),也可以声明大小为 16 或 32 的整数(同样可以为有符号或无符号)图像,例如,CV_16SC3 表示 16 位有符号整数类型。除了整数类型,还可以使用 32 位或 64 位浮点数,例如,CV_32F 表示 32 位浮点数。3. 在主函数中,创建六个窗口来显示图像处理结果:cv::namedWindow("Image 1");cv::namedWindow("Image 2");cv::namedWindow("Image 3");cv::namedWindow("Image 4");cv::namedWindow("Image 5");cv::namedWindow("Image");4. 创建多个不同的图像矩阵(具有不同的尺寸、通道和默认值),并等待按键被按下:// 创建一个尺寸为 240x320 的图像,创建时直接定义像素值cv::Mat image1(240,320,CV_8U,100);cv::imshow("Image", image1); // 显示图像cv::waitKey(0);// 当尺寸或数据类型不同时,需要重新分配内存image1.create(200,200,CV_8U);image1 = 200;cv::imshow("Image", image1); // 显示图像cv::waitKey(0);// 创建一个由红色填充的图像,OpenCV 中色彩通道顺序为 BGRcv::Mat image2(240,320,CV_8UC3,cv::Scalar(0,0,255));// 也可以使用以下方法定义// cv::Mat image2(cv::Size(320,240),CV_8UC3);// image2= cv::Scalar(0,0,255);cv::imshow("Image", image2); // 显示图像cv::waitKey(0);图像(或矩阵)的每个元素可以由多个值组成(例如,彩色图像具有三个通道,因此每个坐标点都有三个像素值),因此,OpenCV 引入了一种简单的数据结构,用于将像素值传递给函数,即 cv::Scalar 结构体,一般用来保存一个值或者三个值。例如,要创建一个用红色像素初始化的彩色图像:cv::Mat image2(240,320,CV_8UC3,cv::Scalar(0,0,255));1同样,灰度图像的初始化也可以通过使用此结构完成 (cv::Scalar(100))。图像尺寸大小通常也需要作为参数传递给函数,我们已经知道 cols 和 rows 属性可用于获取 cv::Mat 实例的维度。尺寸大小信息也可以通过 cv::Size 结构提供,它只包含矩阵的高度和宽度,size() 方法可以获取当前矩阵大小。cv::Size 结构是许多必须指定矩阵大小的方法中常用的格式。例如,使用以下方式创建图像:cv::Mat image2(cv::Size(320,240),CV_8UC3);15. 使用 imread 函数读取图像并将其复制到另一个图像矩阵:// 读取图像cv::Mat image3 = cv::imread("1.png");// 令 image4 和 image1 指向同一个数据块 image3cv::Mat image4(image3);image1 = image3;// image2 和 image5 是 image3 的副本image3.copyTo(image2);cv::Mat image5 = image3.clone();6. 对复制后的图像应用图像转换函数 (cv::flip()),显示创建的所有图像,然后等待按键:// 水平翻转图像cv::flip(image3,image3,1);// 检查图像cv::imshow("Image 3", image3);cv::imshow("Image 1", image1);cv::imshow("Image 2", image2);cv::imshow("Image 4", image4);cv::imshow("Image 5", image5);cv::waitKey(0);7. 使用创建的函数来生成一个新的灰色图像:// 使用 function 函数创建灰度图像cv::Mat gray = function();cv::imshow("Image", gray); // 显示图像cv::waitKey(0);8. 加载一个彩色图像,在加载过程中将其转换为灰度图像,然后,将其值转换为浮点矩阵:// 将图像读取为灰度图像image1= cv::imread("1.png", cv::IMREAD_GRAYSCALE);// 将图像转换为值在 [0, 1] 区间内的浮点图像image1.convertTo(image2,CV_32F,1/255.0,0.0);cv::imshow("Image", image2); // 显示图像1.3 完整代码示例完整代码 (mat.cpp) 如下所示:#include#include#includecv::Mat function() {// 创建新图像并返回cv::Mat ima(500,500,CV_8U,50);return ima;}int main() {// 创建一个尺寸为 240x320 的图像,创建时直接定义像素值cv::Mat image1(240,320,CV_8U,100);cv::imshow("Image", image1); // 显示图像cv::waitKey(0);// 当尺寸或数据类型不同时,需要重新分配内存image1.create(200,200,CV_8U);image1 = 200;cv::imshow("Image", image1); // 显示图像cv::waitKey(0);// 创建一个由红色填充的图像,OpenCV 中色彩通道顺序为 BGRcv::Mat image2(240,320,CV_8UC3,cv::Scalar(0,0,255));// 也可以使用以下方法定义// cv::Mat image2(cv::Size(320,240),CV_8UC3);// image2= cv::Scalar(0,0,255);cv::imshow("Image", image2); // 显示图像cv::waitKey(0);// 读取图像cv::Mat image3 = cv::imread("1.png");// 令 image4 和 image1 指向同一个数据块 image3cv::Mat image4(image3);image1 = image3;// image2 和 image5 是 image3 的副本image3.copyTo(image2);cv::Mat image5 = image3.clone();// 水平翻转图像cv::flip(image3,image3,1);// 检查图像cv::imshow("Image 3", image3);cv::imshow("Image 1", image1);cv::imshow("Image 2", image2);cv::imshow("Image 4", image4);cv::imshow("Image 5", image5);cv::waitKey(0);// 使用 function 函数创建灰度图像cv::Mat gray = function();cv::imshow("Image", gray); // 显示图像cv::waitKey(0);// 将图像读取为灰度图像image1= cv::imread("1.png", cv::IMREAD_GRAYSCALE);// 将图像转换为值在 [0, 1] 区间内的浮点图像image1.convertTo(image2,CV_32F,1/255.0,0.0);cv::imshow("Image", image2); // 显示图像// 使用 cv::Matx 创建一个 3x3 的双精度 (double-precision) 矩阵cv::Matx33d matrix(3.0, 2.0, 1.0,2.0, 1.0, 3.0,1.0, 2.0, 3.0);// 使用 cv::Matx 创建一个 3x1 的双精度矩阵 (向量)cv::Matx31d vector(5.0, 1.0, 3.0);// 矩阵相乘cv::Matx31d result = matrix*vector;std::cout << result;cv::waitKey(0);return 0;}编译后,运行此程序查看生成图像(生成的纯色图像未列出):$ g++ mat.cpp -o mat `pkg-config --cflags --libs opencv4`$ ./mat122. 探索 cv::Mat 数据结构2.1 cv::Mat 对象的创建图像的数据块可以使用 create() 方法分配或重新分配。当图像先前已被分配时,它的旧内容首先被释放,出于效率考虑,如果新分配的尺寸大小和类型与现有的尺寸大小和类型匹配,则不会执行新的内存分配:image1.create(200,200,CV_8U);1当没有引用指向给定的 cv::Mat 对象时,分配的内存会自动释放,这样可以避免在 C++ 中经常与动态内存分配相关的常见内存泄漏问题,这是 OpenCV 中的一个关键机制,通过让 cv::Mat 类实现引用计数和浅复制来实现。因此,当一个图像分配给另一个图像时,图像数据(即像素)不会被复制,两个图像都将指向同一个内存块,按值传递或按值返回的图像同样也是如此。保留引用计数,以便仅当对图像的所有引用都被销毁或分配给另一个图像时才会释放内存:cv::Mat image4(image3);image1= image3;应用于以上图像 (image3、image4 和 image1) 之一的任何变换也将影响其他图像;如果希望创建图像内容的深层副本,需要使用 copyTo() 方法,在这种情况下,将在目标图像上调用 create() 方法;另一种生成图像副本的方法是 clone() 方法,它创建一个相同的新图像:image3.copyTo(image2);cv::Mat image5= image3.clone();如果需要将一个图像复制到另一个不一定具有相同数据类型的图像中,则必须使用 convertTo() 方法:image1.convertTo(image2,CV_32F,1/255.0,0.0);1以上代码中,源图像 image3 被复制到浮点图像 image2 中。convertTo() 方法包括两个可选参数——比例因子和偏移量。需要注意的是,两个图像的通道数必须相同。cv::Mat 对象的分配模型还允许我们安全地编写返回图像的函数(或类方法):cv::Mat function() {// 创建新图像并返回cv::Mat ima(500,500,CV_8U,50);return ima;}可以从主函数 main() 中调用这个函数 function():cv::Mat gray= function();1如果我们调用函数 function(),那么变量 gray 将保存由函数 function() 创建的图像,而无需分配额外的内存。事实上,从 funtion() 返回的 cv::Mat 实例只是 ima 图像的浅拷贝。当 ima 局部变量超出范围时,该变量被释放,但由于关联的引用计数器指示其内部图像数据正在被另一个实例(即变量 gray )引用,因此不会释放其内存块。需要注意的是,在创建类实例时,不要返回图像的类属性。接下来,我们介绍一个容易出错的实现示例:class ErrorExample {// 图像属性cv::Mat ima;public:ErrorExample(): ima(240, 320, CV_8U, cv::Scalar(100)){}cv::Mat method() {return ima;}}在以上代码中,如果一个函数调用这个类的方法,它会获得一个图像属性的浅拷贝。如果以后这个副本被修改,类属性也会被修改,这会影响类的后续行为(反之亦然),为了避免这类错误,我们应该返回属性的副本。2.2 OpenCV 输入和输出数组查看 OpenCV 文档,可以看到许多方法和函数都接受 cv::InputArray 类型的参数作为输入。该类型是一个简单的代理类,引入此类型用于概括 OpenCV 中数组的概念,从而避免重复实现具有不同输入参数类型的相同方法或函数的多个版本,这意味着可以通过提供 cv::Mat 对象或其他兼容类型作为参数,该类只是一个接口,所以不应该在代码中显式地声明它。cv::InputArray 也可以使用 std::vector 类构造,这意味着这类对象可以用作 OpenCV 方法和函数的输入。其他兼容类型包括 cv::Scalar 和 cv::Vec;相应的,还存在一个 cv::OutputArray 代理类,用于指定某些方法或函数返回的数组。小结cv::Mat 数据结构是 OpenCV 中最基础也是最核心的数据结构,通过此数据结构构成了 OpenCV 图像处理所用的复杂类和函数。本节中,我们介绍了 cv::Mat 结构的基本用法,包括如何创建空白/非空白图像、修改图像数据类型以及复制图像内容等操作,并且深入探索了创建 cv::Mat 数据结构时的内存分配方式,最后介绍了 OpenCV 输入和输出数组的两个代理类,包括 cv::InputArray 和 cv::OutputArray。————————————————版权声明:本文为CSDN博主「盼小辉丶」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/LOVEmy134611/article/details/126692582
  • [技术干货]   OpenMV&&stm32通信 -转载
    目录: 1.开篇之言 2.简单介绍 3.主要代码 4.结篇之语 ------------------------------------------------------------------------------------------- 1.开篇之言 (不用看这,都是废话) ……由于新型冠状病毒的影响,不能开学,也不能回学校准备比赛。又学校推迟一周在网上开课,无聊中开启我人生中第一篇博客,由于第一次写博客,错误之处多多指教。也以此为第一篇博客来记录自己的学习和加深印象,也当做笔记啦!!!!!! ……半年前的大一暑假协会组织我们留校培训,这是目前最充实的一个暑假,两个月的暑假在家待了4天 5夜,在路上待了两天,其余都是在学校度过余假啦!!!!!! ……在培训二十天的时候,学长给布置任务练手,让做个 捡羽毛球机器人 当时刚学完51,下面准备学stm32,就决定用stm32作为主控来完成任务。 ……当天晚上买了是他们32学习板,我买的是洋桃一号开发板,当时买回来大概十天学了是stm32大部分,然后我们放了一星期假,我提前回来一天开始着手做任务,从OpenMV开始开启下半暑假旅程。 ……当然十天学stm32比较水,后来开学又看了正点原子的视频,正点原子和杜洋老师的stm32区别是有的,个人感觉正点原子比杜洋老师讲的细,不过杜洋老师的学着比正点原子好学点,好上手一些,但是推荐用正点原子比较好,毕竟是32界的大佬,讲的也挺好,资料也多。  2.简单介绍 这里我主要写一点关于OpenMV与stm32通信的,识别羽毛球需要OpenMV,然后把数据转给stm32 1.OpenMV使用Python编程,用的语法较少,简单了解一下即可,OpenMV底层是用C语言写好的函数,用户只要用Python调用就可以啦,我这是通过形状识别的圆形识别来检测羽毛球的位置(只传输在画面中的x,y坐标)。 2.stm32使用串口中断来接受数据。 3.主控是洋桃一号开发板,由于是半年前做的,现在在家没有任何实物,所以里面接线图,实物图几乎都没有,请见谅。 4.另外说句题外话,想学Python的小伙伴们可以考虑风变科技的Python小课,我是前几天看卓大大推荐的8.9元的体验课,于是体验了一下,教学方式采用文字交互式学习,挺有趣,我是今天刚闯完体验课的4关,由于后面因为比赛也没时间,所以打算以后再学吧!!!!  3.主要代码 OpenMV的所有代码,这里识别摄像头画面中最大的圆的中心坐标(x,y)  import sensor, image, time, math from pyb import UART import json import ustruct  sensor.reset() sensor.set_pixformat(sensor.RGB565) # grayscale is faster sensor.set_framesize(sensor.QQVGA) sensor.skip_frames(time = 2000) clock = time.clock()  uart = UART(3,115200)   #定义串口3变量 uart.init(115200, bits=8, parity=None, stop=1) # init with given parameters  def find_max(circles):    #定义寻找球面积最大的函数     max_size=0     for circle in circles:         if circle.magnitude() > max_size:             max_circle=circle             max_size = circle.magnitude()     return max_circle  def sending_data(cx,cy):    #发送函数(我这里没用的,还是留着吧)     global uart;     #frame=[0x2C,18,cx%0xff,int(cx/0xff),cy%0xff,int(cy/0xff),0x5B];     #data = bytearray(frame)     data = ustruct.pack("<bbhhb",                                  0x2C,                                           0x12,                                           int(cx),                                        int(cy),                                        0x5B)     uart.write(data);     def recive_data():   #接收函数     global uart     if uart.any():         tmp_data = uart.readline();         print(tmp_data)  while(True):     clock.tick()     img = sensor.snapshot()      circles = img.find_circles(threshold = 2500, x_margin = 10, y_margin = 10, r_margin = 10,             r_min = 2, r_max = 100, r_step = 2);     cx=0;cy=0;     if circles:         #如果找到了目标球         max_c = find_max(circles);         #在圆周围画一个圆         #img.draw_circle(circle.x(), circle.y(), circle.r(), color = (255, 0, 0))         #用圆标记出目标颜色区域         img.draw_cross(max_c[0], max_c[1]) # cx, cy         img.draw_cross(160, 120) # 在中心点画标记         #在目标颜色区域的中心画十字形标记         cx=max_c[0];         cy=max_c[1];         img.draw_line((80,60,cx,cy), color=(127));         #img.draw_string(160,120, "(%d, %d)"%(160,120), color=(127));         img.draw_string(cx, cy, "(%d, %d)"%(cx,cy), color=(127));              sending_data(cx,cy); #发送点位坐标         recive_data(); stm32里部分代码,只是串口部分的,用的PA.9和PA.10  usart.c void USART1_Init(void){ //串口1初始化并启动     //GPIO端口设置     GPIO_InitTypeDef GPIO_InitStructure; //串口端口配置结构体变量     USART_InitTypeDef USART_InitStructure; //串口参数配置结构体变量     NVIC_InitTypeDef NVIC_InitStructure;//串口中断配置结构体变量               RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);    //打开串口复用时钟     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);   //打开PC端口时钟      //USART1_TX   PA.9     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//设定IO口的输出速度为50MHz     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;    //复用推挽输出     GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化GPIOA.10     //USART1_RX      PA.10     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; //PA.10     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入     GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化GPIOA.10       //Usart1 NVIC 配置     NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0 ;//抢占优先级0     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;        //子优先级2     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;            //IRQ通道使能     NVIC_Init(&NVIC_InitStructure);    //根据指定的参数初始化VIC寄存器      //USART 初始化设置     USART_InitStructure.USART_BaudRate = 115200;//串口波特率为115200     USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式     USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位     USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位     USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制     USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;    //收发模式     USART_Init(USART1, &USART_InitStructure); //初始化串口1      USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启ENABLE     USART_Cmd(USART1, ENABLE);   //使能串口1      //如下语句解决第1个字节无法正确发送出去的问题     USART_ClearFlag(USART1, USART_FLAG_TC);       //清串口1发送标志 }  //串口1中断处理函数 void USART1_IRQHandler(void)               //串口1全局中断服务函数 {     u8 temp;     if( USART_GetITStatus(USART1,USART_IT_RXNE)!=RESET )     {         USART_ClearITPendingBit(USART1,USART_IT_RXNE);//清除中断标志         temp = USART_ReceiveData(USART1);         Openmv_Receive_Data(temp);//openmv数据处理函数     } }  u8 RxCounter1 = 0;//接受OpenMV数据里用到的 u8 RxFlag1 = 0;//主函数中用的,可不用在意 u8 RxBuffer1[18];//接受OpenMV数据里用到的数组 u16 posX = 0,posY = 0;//x,y坐标  //接收OpenMV传过来的数据 void Openmv_Receive_Data(int16_t data) {     static u8 state = 0;         if(state==0&&data==0x2C)     {         state=1;         RxBuffer1[RxCounter1++]=data;     }     else if(state==1&&data==18)     {         state=2;         RxBuffer1[RxCounter1++]=data;     }     else if(state==2)     {         RxBuffer1[RxCounter1++]=data;         if(RxCounter1>19||data == 0x5B) state=3;    //最后字符是openmv[19]     }     else if(state==3)        //state == 3  检测是否接受到结束标志     {         if(RxBuffer1[RxCounter1-1] == 0x5B)         {                state = 0; //            RxFlag1 = 1;             USART_ITConfig(USART1,USART_IT_RXNE,DISABLE);//关闭DTSABLE中断              posX = RxBuffer1[3]<<8 | RxBuffer1[2];   //这时x,y坐标已经赋值给posX和posY             posY = RxBuffer1[5]<<8 | RxBuffer1[4];              OLED_DISPLAY_8x16(2,6*8,posX/100+0x30);  //x,y坐标显示OLED上方便观看             OLED_DISPLAY_8x16(2,7*8,posX0/10+0x30);             OLED_DISPLAY_8x16(2,8*8,posX+0x30);             OLED_DISPLAY_8x16(4,6*8,posY/100+0x30);             OLED_DISPLAY_8x16(4,7*8,posY0/10+0x30);             OLED_DISPLAY_8x16(4,8*8,posY+0x30);              if(posX > 105 && posX < 125)  //下面几行是我用到的,可不用看             {                     RxFlag1 = 1;                    //                posX = 0;    //                posY = 0;             }  //            RxFlag1 = 0;                RxCounter1 = 0; //            posX = 0; posY = 0;  //上面几行是我用到的,可不用看             USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);开启ENABLE中断            }            else   //wrong thing            {                state = 0;             RxCounter1=0;             }     }         else    //wrong thing     {         state = 0;         RxCounter1=0;     } usart.h extern u16 USART1_RX_STA;                 //接受状态标记      //Openmv_Receive_Data()函数用到的全局变量 extern u8 RxCounter1;      //接受OpenMV数据里用到的 extern u8 RxFlag1;          //主函数中用的,可不用在意 extern u8 RxBuffer1[18];   //接受OpenMV数据里用到的数组  extern u16 posX;  //OpenMV图像中羽毛球中心x坐标  可不用在意 extern u16 posY;  //OpenMV图像中羽毛球中心y坐标  //函数声明 void USART1_Init(void);//串口1初始化并启动 void Openmv_Receive_Data(int16_t data);//接收OpenMV传过来的数据 4.结篇之语 ……好了,到此第一篇博客就结束,里面可能有有些小错误,不过在我这是可以捡羽毛球的,只不过因为一些原因,成功率不高,不过OpenMV与stm32通信还是可以的,希望能帮助到你一点点就很开心了。 ……第一篇博客(2020.2.18.22:00----2020.2.19.1:30)。 ……里面废话多点,可以忽略。 ……最后希望疫情早日过去,我想回学校啦,啊哈哈哈哈哈。 ……中国加油,武汉加油。 ———————————————— 版权声明:本文为CSDN博主「苦逼的虚拟」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qq_45417468/article/details/104384547 
  • [技术干货] 基于opencv的人脸检测-转载
    引言这篇文章,让你用短短的16行代码基于opencv检测一张图片上的人脸,很有意思,快快学起来吧!一、代码先将代码直接粘在下面:import cv2 as cvdef face_detect_demo(img):img = cv.resize(img, dsize=(800, 800))gary = cv.cvtColor(img, cv.COLOR_BGR2GRAY)face_detect = cv.CascadeClassifier("D:/opencv/sources/data/haarcascades/haarcascade_frontalface_default.xml")face = face_detect.detectMultiScale(gary, 1.004, 28, 0, (40, 40), (50, 50))for x, y, w, h in face:cv.rectangle(img, (x, y), (x + w, y + h), color=(0, 0, 225), thickness=4)cv.imshow("result", img)# img.save("result.jpg") # 保存图片cv.imwrite(r"final_result.jpg", img)img = cv.imread("photo.jpg")face_detect_demo(img) # 检测单个图片while True:if ord("q") == cv.waitKey(1):breakcv.destroyAllWindows()二、代码解读首先导入我们所需要的必要包opencv,对应指令为import cv2 as cv,这里的as就相当于给这个模块起了个别名。如果读者opencv还没有安装的话,可以在终端里面pip install opencv,还不行的话可以看看这个教程:Opencv安装小白教程首先,我们定义一个函数face_detect_demo,这个函数有一个参数img,即我们要检测的图片,这是一个形参。调用cv里面的函数resize,改变图片的大小。resize第一个参数是要处理的图片,第二个参数是你想要改成图片的大小,这里的(800,800)就是改成800*800的这样一张图片。处理的结果再返回给img,当然,如果你喜欢的话也可以另外设一个变量,传进去。接下来更改图片颜色,直接调用cvtColor函数,同样,第一个参数是要处理的图片,第二个参数是要更改的颜色类型,这里的颜色类型有很多种,我这里设置的是COLOR_BGR2GRAY类型。前两步,你可以理解为将图片标准化,即传入不同大小,不同颜色的图片可以转化成同一大小,同一颜色类型的图片。这样的操作好处在于后面调参数更容易。重头戏来了!,这是opencv官方已经训练好的一个人脸检测模型,直接调用就可以,非常方便!文章最后有文件和代码提取链接,也有一些其他训练好的模型,如果感兴趣的话你也可以试一试其他模型。接着,我们检测图片中的人脸,face_detect.detectMultiScale,就是调用模型里面的detectMultiScale。第一个参数gray是要处理的图片,第二个参数是,第三个参数28意思是检测的时候,30次都在同一个位置检测到人脸;这个值高,模型就需要反复验证。第四个参数是。第五个参数是最小矩形框大小,第六个参数是最大矩形框大小。这六个参数需要自己去调,以求最佳匹配。detectMultiScale方法会直接用该模型检测图片,并返回多个x,y,w,h。(x,y)是左顶点的坐标,w是矩形框的宽度,h是矩形框的高度。for循环的功能就是在原来的图片上面绘制矩形框啦,对应的函数是cv.rectangle,这里面的参数很简单就不一一介绍了。cv.imshow进行展示,cv.imwrite进行保存,注意imwrite保存图片时,命名要加上.jpg函数到此结束,接下来是调用函数:首先读取图片,由于我把程序和图片放在一个文件夹,可以直接用名字去读,如果你没有放在一个文件家里面,要用绝对路径去读取。调用上面写好的函数去检验人脸最后做一个键盘的关联事件退出,,按下键盘q键会退出循环,程序结束。别忘了用cv.destroyAllWindows销毁所以窗口哦。四、一些思考我感觉这是一个很有意思的demo,虽然没有什么技术含量,没有数据获取,数据处理,数据训练,模型验证…但是,他能让我们看到cv的强大,如果计算机视觉没有opencv,很多简单的功能将变得复杂,当opencv+openmv,我们能做很多我们感兴趣的事。回到这个人脸检测里面去,为什么叫人脸检测不叫人脸识别呢?因为这个demo只能够知道图片里有几个人,至于他们到底分别是谁就没有办法知道了。如果加上神经网络又会怎么样呢?比如现在我有A这个人的大量图片,用神经网络进行训练,让计算机能够认出他。那么在一张合照里面,计算机能够很快认出哪个是A,并把他框选出来。那么到底有什么用呢?畅想未来:现在有很多重要犯罪嫌疑人逍遥法外,如果能将他的人脸数据训练出来的模型传到全国各地的摄像头中,无论在哪,只要摄像头检测到他的人脸就把地址信息发回警局,这样是不是就能即使抓捕他。儿童拐卖同样是一个社会热点,现在也有很多网站在帮助找被拐卖的儿童。父母主要是通过网站上上传的照片去猜测这到底是不是他的孩子。但是很多照片都是孩子被拐卖两三年甚至五年十年之后的照片了,就连是亲生父母都很难辨认。如果能用深度学习,强化学习,根据小时候的照片训练出来的模型去匹配网站上的照片,将匹配结果高的反馈给父母,这样父母和子女是不是更容易重逢呢?当然这只是我的猜想。人脸作为我们的生物密码给我们带来了不少便利,比如说手机一键解锁,支付宝一键支付,各种门禁刷脸进入。“刷脸”逐渐渗透到我们生活,我们的人脸数据不断传入互联网中,互联网的数据库越来越丰富,对应背后的产业链也越来越发达,面部识别并不是一项全新的技术,但人工智能和机器学习不断使面部识别变得更强大。文件链接:https://pan.baidu.com/s/1FV3NWT3Vayq3eKq2QsB3mg?pwd=tsvd提取码:tsvd————————————————版权声明:本文为CSDN博主「爱睡觉的咋」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/weixin_63866037/article/details/127341085
  • [技术干货] OpenCV实战(1)——OpenCV与图像处理-转载
     0. 前言 OpenCV 是一个的跨平台计算机视觉库,包含了 500 多个用于图像和视频分析的高效算法。本节将介绍 OpenCV 的基础知识,以及如何编译运行 OpenCV 程序,并将学习如何完成最基本的图像处理任务——读取、显示和保存图像。除此之外,鼠标事件和图形绘制也是 OpenCV 计算机视觉项目中常用的基本功能(例如图像标注场景,利用鼠标事件在图像中绘制目标对象的边界框),本节介绍了如何使用这两个重要的 OpenCV 功能。  1. OpenCV 基础 1.1 安装 OpenCV OpenCV 是一个开源库,可以用于开发在 Windows、Linux、Android 和 macOS 等平台上运行的计算机视觉应用程序。自 1999 年推出以来,它已成为计算机视觉领域广泛采用的主要开发工具。在 OpenCV 网站,根据所用计算机的不同平台( Unix/Windows 或 Android) 下载相对应的 OpenCV 包,关于不同平台 OpenCV 的安装方式可以参考官方指南或相关博客。  1.2 OpenCV 主要模块 从 OpenCV 2.2 版开始,OpenCV 库被分成了多个模块,这些模块是位于 lib 目录中的内置库文件,一些常用的模块如下:  core 模块包含 OpenCV 库的核心函数,主要包括基本数据结构和算术函数 imgproc 模块包含主要的图像处理函数 highgui 模块包含图像和视频读写函数以及一些用户界面函数 features2d 模块包含特征点检测器和描述符以及特征点匹配框架 calib3d 模块包含相机校准、视图几何估计和立体函数 video 模块包含运动估计、特征跟踪和前景提取函数和类 objdetect 模块包含人脸检测和人物检测等目标检测函数 OpenCV 还包括许多其他实用模块,例如机器学习函数 (ml)、计算几何算法 (flann)等;除此之外,还包括其他实现更高级函数的专用库,例如,用于计算摄影的 photo 和用于图像拼接算法的 stitching。 所有这些模块都有一个与之关联的头文件,因此,典型的 OpenCV C++ 代码首先应当声明引入所需的模块,例如,建议的声明样式类似于以下代码:  #include  #include  #include  有时,我们也可能会看到以以下命令开头的 OpenCV 代码,这是由于为了与旧定义兼容而使用了旧样式:  #include "cv.h" 1 1.3 使用 Qt 进行 OpenCV 开发 Qt 是作为开源项目开发的 C++ 应用程序的跨平台 IDE。它由两个独立的元素组成——一个名为 Qt Creator 的跨平台 IDE,以及一组 Qt 类和开发工具。使用 Qt 开发 C++ 应用程序有以下优点:  它是一个由 Qt 社区开发的开源计划,可以访问不同 Qt 组件的源代码 它是一个跨平台的 IDE,可以开发能够运行在不同操作系统上的应用程序,例如 Windows、Linux、macOS 等 它包含一个完整的跨平台 GUI 库,遵循面向对象和事件驱动模型 Qt 还包括多个跨平台库,可用于开发多媒体、数据库、多线程、Web 应用程序以及其他高级应用程序 使用 Qt 可以很方便的编译 OpenCV 库,因为它可以读取 CMake 文件。一旦安装了 OpenCV 和 CMake,只需从 Qt 的 File 菜单中选择 Open File or Project...,然后打开 OpenCV 的源目录下的 CMakeLists.txt 文件,然后在弹出窗口中单击 configure project:   创建一个 OpenCV 项目后,可以通过单击 Qt 菜单中的 Build Project 来构建该项目:   2. OpenCV 图像处理基础 一切准备就绪,接下来是运行第一个 OpenCV 应用程序的时候了。由于 OpenCV 的核心就是处理图像,因此我们首先学习如何执行图像应用程序所需的最基本操作,即从文件系统中加载输入图像、在窗口中显示图像、应用处理函数以及将输出图像存储在磁盘上。  2.1 加载、显示和保存图像 创建新文件 hello_opencv.cpp,包含头文件,声明将使用的类和函数。由于,本节我们只需要显示一个图像,所以需要声明图像数据结构的 core 头文件和包含所有图形界面功能的 highgui 头文件:  #include  #include  1 2 2.1.1 加载图像 在 mian 函数中首先声明一个保存图像的变量,定义一个 cv::Mat 类对象:  cv::Mat image; // 创建一个空图像 1 根据此定义将创建一个大小为 0 x 0 的图像,可以通过访问 cv::Mat size 属性来确认图像尺寸:  std::cout << "This image is " << image.rows << " x " << image.cols << std::endl;  1 接下来,调用 imread 函数将从文件中读取图像,对其进行解码并分配内存:  image = cv::imread("1.png"); // 读取图像 1 当使用 imread 打开图像而不指定完整路径时,将使用默认目录。当直接运行应用程序时,可执行文件显然位于该目录下;但是,如果直接从 IDE 运行应用程序,默认目录通常是包含项目文件的目录,因此,需要确保输入图像文件位于正确的目录中。 读取图像后就可以使用此图像了。但是,为了保证图像被正确读取(如果找不到文件、文件是否已损坏或不是可识别的格式,则会出现错误),应该首先使用 empty() 函数检查图像是否正确读取。如果没有分配图像数据,empty() 方法返回 true:  if (image.empty()) { // 异常处理     return 0;  } 2.1.2 显示图像 可以通过使用 highgui 模块中的函数来显示图像。首先需要声明用于显示图像的窗口,然后指定要在此窗口上显示的图像:  cv::namedWindow("Orginal Image"); cv::imshow("Orginal Image", image); 1 2 窗口由名称标识,我们也可以重复使用此窗口来显示另一个图像,或者可以创建多个具有不同名称的窗口。运行此应用程序时,可以看到一个图像窗口,如下所示:   加载图像后,通常需要对图像进行一些处理。OpenCV 提供了广泛的图像处理函数,例如,我们可以使用一个非常简单的 flip() 函数水平翻转图像。OpenCV 中的许多图像转换操作可以原地 (in-place) 执行,这意味着转换可以直接应用于输入图像,而不必创建新图像。翻转操作就属于原地操作:  cv::flip(image,image,1); // 原地操作 1 但是,我们也可以创建另一个矩阵来保存输出结果:  cv::Mat result; cv::flip(image, result, 1); // flip 函数中,正数表示水平翻转;0表示垂直翻转;负数表示同时进行水平和垂直翻转 1 2 在另一个窗口中显示图像翻转后的结果:  cv::namedWindow("Output Image"); cv::imshow("Output Image", result); 1 2 由于控制台窗口在 main 函数结束时就会终止,因此我们添加一个额外的 highgui 库函数以在结束程序之前等待用户按键操作:  cv::waitKey(0); 1 可以看到输出图像显示在一个不同的窗口中,如下图所示:   2.1.3 保存图像 最后,将处理后的图像保存在磁盘上,可以使用 highgui 库函数完成图像保存操作:  cv::imwrite("output.png", result); // 保存处理结果 1 文件扩展名决定了将使用哪个编解码器来保存图像,常见的图像格式包括 BMP、JPG、TIFF 和 PNG 等。  2.1.4 完整代码 完整代码如下所示:  #include  #include  #include   int main(){     cv::Mat image; // 创建一个空图像     std::cout << "This image is " << image.rows << " x " << image.cols << std::endl;       image = cv::imread("1.png"); // 读取图像     if (image.empty()) { // 异常处理         std::cout << "Error reading image..." << std::endl;         return 0;      }      cv::namedWindow("Orginal Image");     cv::imshow("Orginal Image", image);     cv::waitKey(0);     cv::Mat result;     cv::flip(image, result, 1); // 正数表示水平翻转;0表示垂直翻转;负数表示同时进行水平和垂直翻转     cv::namedWindow("Output Image");     cv::imshow("Output Image", result);     cv::imwrite("output.png", result); // 保存处理结果     cv::waitKey(0);     return 0; } 2.2 OpenCV 命名空间 OpenCV 的 C++ API 中的所有类和函数都定义在 cv 命名空间中。可以通过两种方式访问这些类和函数。第一种方法是在 main 函数的定义之前添加以下声明:  using namespace cv; 1 第二种方法是,按照命名空间规范(即 cv:: )作为所有 OpenCV 类和函数名称的前缀,前缀的使用使 OpenCV 类和函数更容易识别。  2.3 cv::imread() 函数详解 highgui 模块包含一组用于可视化图像并与之交互的函数,使用 imread 函数加载图像时,还可以选择将其作为灰度图像读取,这对于某些需要灰度图像的计算机视觉算法而言是十分有用的。在读取图像时即时转换输入的彩色图像可以节省时间并最大限度地减少内存使用:  // 将输入图片读取为灰度图像 image= cv::imread("1.png", cv::IMREAD_GRAYSCALE); 1 2 使用以上代码可以得到一个由无符号字节 (C++ 中的 unsigned char) 组成的图像,OpenCV 用 CV_8U 定义的常量表示这种数据类型。 有时即使输入图像为灰度图像,也需要将图像读取为三通道彩色图像。这可以通过设定第二个参数为正数调用 imread 函数来实现:  // 将输入图像读取为三通道彩色图像 image= cv::imread("1.png", cv::IMREAD_COLOR); 1 2 此时,可以得到一个每个像素由三个字节组成的图像,在 OpenCV 中指定为 CV_8UC3,如果输入图像为灰度图像,则所有三个通道都将包含相同的值。最后,如果希望以保存时的格式读取图像,只需输入一个负值作为 imread 的第二个参数。可以使用 channels 方法检查图像中的通道数:  std::cout << "This image has " << image.channels() << " channel(s)"; 1 当使用 imshow 显示由整数组成的图像 (16 位无符号整数指定为 CV_16U,32 位有符号整数指定为 CV_32S) 时,该图像的像素值将首先除以 256,以尝试使其可显示为 256 种灰度值。同样,由浮点数组成的图像通过使用 0.0 (显示为黑色)和 1.0 (显示为白色)之间的可能值来显示。超出此定义范围的值以白色(对于高于 1.0 的值)或黑色(对于低于 0.0 的值)显示。  2.4 OpenCV 应用程序的编译执行 2.4.1 编译 OpenCV 应用程序 程序编写完成后,需要进行编译后才能执行,在大多数 IDE 中编写程序时,可以很方便的编译并执行,除此之外,我们还可以使用命令行编译并执行,我们主要介绍以下两种编译方法。  1. 方法一,通过g++命令进行编译得到可执行文件:  $ g++ hello_opencv.cpp -o hello_opencv `pkg-config --cflags --libs opencv` 1 在以上编译命令中,hello_opencv.cpp 是源文件,-o 选项用于指定编译后生成的输出文件 hello_opencv,pkg-config 具有以下用途:  检查库的版本号,避免链接错误版本的库文件 获得编译预处理参数,例如头文件位置等 获得链接参数,例如库及其依赖库的位置、文件名和其他链接参数 自动加入所依赖的其他库的位置 在安装 OpenCV 的安装链接库文件目录 lib 中包含一个 pkgconfig 目录,其中包含一个 opencv.pc 文件,该文件即为 pkg-config 下的 OpenCV 配置文件,使用 pkg-config 时,选项 –cflags 用来指定程序在编译时所需要头文件所在的目录,选项 –libs 则指定程序在链接时所需要的动态链接库的目录。  2. 方法二,通过 cmake 进行编译,编辑 CMakeLists.txt 文件,添加以下代码:  #指定需要的cmake的最低版本 cmake_minimum_required(VERSION 2.8) #创建工程 project(hello_opencv) #指定C++语言为C++ 11 set(CMAKE_CXX_FLAGS "-std=c++11") #查找OpenCV 安装路径 find_package(OpenCV REQUIRED) #引入OpenCV头文件路径 include_directories(${OpenCV_INCLUDE_DIRS}) #指定编译 hello_opencv.cpp 程序编译后生成可执行文件 hello_opencv add_executable(hello_opencv hello_opencv.cpp) #指定可执行文件 hello_opencv 链接OpenCV lib target_link_libraries(hello_opencv ${OpenCV_LIBS}) 文件中各行代码作用使用注释进行说明,编写完成后,执行以下代码进行编译:  $ mkdir build $ cd build $ cmake .. $ make  2.4.1 执行 OpenCV 应用程序 编译完成后,执行可执行程序:  $ ./hello_opencv 3. OpenCV 鼠标事件 highgui 模块包含一组丰富的函数,可用于与图像进行交互。使用这些函数,应用程序可以对鼠标或按键事件做出响应。 当鼠标位于所创建的图像窗口上时,通过定义回调函数,可以对鼠标进行编程以执行特定操作。回调函数是没有显式调用的函数,但它会被应用程序调用以响应特定事件(例如鼠标与图像窗口交互的事件) 。为了被应用程序识别,回调函数需要有一个特定的签名并且必须被注册。在鼠标事件处理程序的情况下,回调函数必须具有以下签名:  void onMouse(int event, int x, int y, int flags, void* param); 1 第一个参数 event 是一个整数,用于指定哪种类型的鼠标事件触发了对回调函数的调用;紧接着的两个参数 x 和 y 是事件发生时鼠标位置的像素坐标;最后一个参数用于以指向对象的指针的形式向函数发送一个额外的参数。可以通过以下方式在应用程序中注册此回调函数:  cv::setMouseCallback("Original Image", onMouse, reinterpret_cast(&image)); 其中,onMouse 函数与名为 Original Image 的图像窗口相关联,并且需要图像的地址作为额外参数传递给该函数。如果我们定义如下代码所示的 onMouse 回调函数,那么每次点击鼠标时,都会在控制台上显示相应像素的值(假设图像是灰度图像) :  void onMouse(int event, int x, int y, int flags, void* param){     cv::Mat *im = reinterpret_cast(param); switch (event){     case cv::EVENT_LBUTTONDOWN:         // 打印坐标为 (x, y) 处的像素坐标         std::cout << "at (" << x << "," << y << ") value is: " << static_cast(im->at(cv::Point(x, y))) << std::endl;         break;     } } 为了获得 (x,y) 处的像素值,我们使用了 cv::Mat 对象的 at() 方法,在之后的学习中会进行详细介绍,此处的重点在于介绍鼠标事件。鼠标事件回调函数可以接收的其他事件包括 cv::EVENT_MOUSE_MOVE、cv::EVENT_LBUTTONUP、cv::EVENT_RBUTTONDOWN 和 cv::EVENT_RBUTTONUP 等。 完整代码 ( mouse_event.cpp`) 如下所示:  #include  #include  #include  #include   void onMouse( int event, int x, int y, int flags, void* param)    {     cv::Mat *im= reinterpret_cast(param);     switch (event) {    // dispatch the event         case cv::EVENT_LBUTTONDOWN: // mouse button down event             // display pixel value at (x,y)             std::cout << "at (" << x << "," << y << ") value is: "                        << static_cast(im->at(cv::Point(x,y))) << std::endl;             break;     } }  int main() {     cv::Mat image; // 异常处理     std::cout << "This image is " << image.rows << " x "                << image.cols << std::endl;      // 将输入图像读取为灰度图像     image=  cv::imread("1.png", cv::IMREAD_GRAYSCALE);      if (image.empty()) {  // error handling         std::cout << "Error reading image..." << std::endl;         return 0;     }      std::cout << "This image is " << image.rows << " x "                << image.cols << std::endl;     std::cout << "This image has "                << image.channels() << " channel(s)" << std::endl;       // 创建窗口,显示图像     cv::namedWindow("Original Image");      cv::imshow("Original Image", image);       // 为图像设置鼠标回调函数     cv::setMouseCallback("Original Image", onMouse, reinterpret_cast(&image));     cv::waitKey(0); // 等待键盘事件     return 0; } 编译并执行程序,结果如下所示:  $ g++ mouse_event.cpp -o mouse_event `pkg-config --cflags --libs opencv` $ ./mouse_event 4. OpenCV 绘制图形 OpenCV highgui 模块还提供了一些在图像上绘制图形和书写文本的函数。基本形状绘制函数包括circle()、ellipse()、line() 和 rectangle() 等,以 circle() 函数为例,其他绘图函数的基本用法类似:  cv::circle(image, // 绘制的目标图像     cv::Point(155, 110),    // 圆心坐标     65,                     // 半径     0,                      // 颜色     3,                      // 线条粗细 ); cv::Point 结构常用于 OpenCV 方法和函数中来指定像素坐标,我们假设绘制是在灰度图像上完成的;因此我们使用单个整数 0 指定绘制颜色。在之后的学习中,我们会学习如何在使用 cv::Scalar 结构的彩色图像的情况下指定颜色值。我们也可以在图像上写入文本:  cv::putText(image,      // 绘制的目标图像     "This is a dog.",   // 文本     cv::Point(40, 200), // 文本位置     cv::FONT_HERSHEY_PLAIN, // 字体类型     2.0,                    // 缩放比例     255,                    // 文字颜色     2,                      // 文字粗细 ) 完整代码 (draw_on_image.cpp) 如下所示:  #include   #include  #include  #include   int main() {     cv::Mat image; // create an empty image     std::cout << "This image is " << image.rows << " x "                << image.cols << std::endl;      // 读取图像     image=  cv::imread("1.png", cv::IMREAD_GRAYSCALE);      // 创建图像窗口     cv::namedWindow("Drawing on an Image");     // 绘制圆形     cv::circle(image,              // 目标图像                 cv::Point(430,160), // 圆心坐标                150,                 // 半径                 0,                  // 颜色                3);                 // 线条粗细     // 绘制文本     cv::putText(image,                   // 目标图像                 "This is a person.",        // 文本                 cv::Point(280,350),       // 文本位置                 cv::FONT_HERSHEY_PLAIN,  // 字体类型                 2.0,                     // 缩放                 255,                     // 字体颜色                 2);                      // 字体粗细      cv::imshow("Drawing on an Image", image); // 显示图像     cv::waitKey(0); // 等待键盘响应     return 0; }  编译并执行代码,在测试图像上调用这两个函数可以得到以下结果:  $ g++ draw_on_image.cpp -o draw_on_image `pkg-config --cflags --libs opencv` $ ./draw_on_image 5. 使用 Qt 运行 OpenCV 应用程序 如果想要使用 Qt 来运行 OpenCV 应用程序,需要创建项目文件。例如,对于上一小节的示例,项目文件 (draw_on_image.pro) 如下所示:  # 库模板 TEMPLATE = app # 目标文件 TARGET = draw_on_image # 项目目录 INCLUDEPATH += . # 向qmake声明应用程序依赖于 widgets 模块 greaterThan(QT_MAJOR_VERSION,4): QT += widgets  # OpenCV 安装路径 unix:!mac {   INCLUDEPATH += /home/brainiac/Program/opencv4/include/opencv4   LIBS += -L/home/brainiac/Program/opencv4/lib -lopencv_core -lopencv_imgproc -lopencv_highgui -lopencv_imgcodecs }  WIN32 {   INCLUDEPATH += C:/OpenCV4.6.0/build/include/opencv4   LIBS += -lc:/OpenCV4.6.0/build/lib/ -lopencv_core -lopencv_imgproc -lopencv_highgui -lopencv_imgcodecs } # 源文件 SOURCES += draw_on_image.cpp  该文件声明了文件和库文件的位置,还列出了代码使用的库模块。确保使用与 Qt 正在使用的编译器兼容的库二进制文件。 使用 qmake 编译并执行应用程序,结果如下所示:  $ qmake -makefile $ make $ ./draw_on_image 小结 OpenCV 是一个跨平台计算机视觉和机器学习库,实现了图像处理和计算机视觉方面的很多通用算法。本文,首先介绍了 OpenCV 的基础知识,并介绍了如何在不同平台安装 OpenCV 库,同时演示了如何使用不同方式编写、编译和执行 OpenCV 应用程序。然后,我们还总结了 OpenCV 处理图像的基础函数和功能,包括读取、显示和保存图像,以及鼠标事件和图像绘制。 ———————————————— 版权声明:本文为CSDN博主「盼小辉丶」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/LOVEmy134611/article/details/127478248 
  • [API使用] mindspore::dataset::GetAffineTransform的输出与cv2的输出不同
    在使用C++进行推理时用到了函数mindspore::dataset::GetAffineTransform,但是输入相同的数据后,与Python的cv2中的同名函数cv2.getAffineTransform所输出的结果不同。C++ Ascend310端测试核心代码#include #include #include "include/dataset/lite_cv/lite_mat.h" #include "include/dataset/lite_cv/image_process.h" using mindspore::MSTensor; using mindspore::DataType; using mindspore::dataset::LiteMat; using mindspore::dataset::Point; using mindspore::dataset::Affine; using mindspore::dataset::GetAffineTransform; int main(int argc, char **argv) { float src[3][2] = {154.61, 90.4373, 154.61, 38.1699, 102.342, 38.1699,}; float dst[3][2] = {144, 192, 144, 48, 0, 48,}; mindspore::dataset::LiteMat trans; std::vector src_point = {Point(src[0][0], src[0][1]), Point(src[1][0], src[1][1]), Point(src[2][0], src[2][1])}; std::vector dst_point = {Point(dst[0][0], dst[0][1]), Point(dst[1][0], dst[1][1]), Point(dst[2][0], dst[2][1])}; int inv = 0; if (inv) mindspore::dataset::GetAffineTransform(dst_point, src_point, trans); else mindspore::dataset::GetAffineTransform(src_point, dst_point, trans); std::cout << "[D]trans: " << trans.width_ << ' ' << trans.height_ << std::endl; std::cout << *(float*)(trans.data_ptr_) << ' ' << *((float*)(trans.data_ptr_) + 1) << ' ' << *((float*)(trans.data_ptr_) + 2) << std::endl; std::cout << *((float*)(trans.data_ptr_ )+ 2 * 1 + 0) << ' ' << *((float*)(trans.data_ptr_ )+ 2 * 1 + 1) << ' ' << *((float*)(trans.data_ptr_) + 2 * 1 + 2) << std::endl; return 0; }程序运行环境为:Ascend310,Ubuntu18.04,g++8.1.0,mindspore1.8.0,mindspore-lite1.8.1 程序输出结果为:[D]trans: 3 2 2.80742e-15 2.09438 0 0 -0 -3.64823e-16Python cv2.getAffineTransform()import numpy as np import cv2 src = np.array([[154.6097, 90.43735], [154.6097, 38.16989], [102.34224, 38.16989]]) dst = np.array([[144., 192.], [144., 48.], [0., 48.]]) inv = 0 if inv: trans = cv2.getAffineTransform(np.float32(dst), np.float32(src)) else: trans = cv2.getAffineTransform(np.float32(src), np.float32(dst)) print(trans)程序运行环境为:CPU,Windows10,Python3.8.8,opencv4.6.0 程序输出结果为:[[ 2.75506043e+00 -0.00000000e+00 -2.81959054e+02] [ 9.19143827e-17 2.75506043e+00 -5.71603573e+01]]问题可以看到两者的输出完全不同。我想知道,这两个函数是否是对应关系,如果是的话,我的代码在哪里出了问题,如果不是的话,那么Mindspore在C++中是否有对应的API可以使用。
  • [技术干货] openCV实战项目--人脸考勤-转载
    人脸任务在计算机视觉领域中十分重要,本项目主要使用了两类技术:人脸检测+人脸识别。代码分为两部分内容:人脸注册 和 人脸识别人脸注册:将人脸特征存储进数据库,这里用feature.csv代替人脸识别:将人脸特征与CSV文件中人脸特征进行比较,如果成功匹配则写入考勤文件attendance.csv文章前半部分为一步步实现流程介绍,最后会有整理过后的完整项目代码。一、项目实现A. 注册:导入相关包import cv2import numpy as npimport dlibimport timeimport csv# from argparse import ArgumentParserfrom PIL import Image, ImageDraw, ImageFont设计注册功能注册过程我们需要完成的事:打开摄像头获取画面图片在图片中检测并获取人脸位置根据人脸位置获取68个关键点根据68个关键点生成特征描述符保存(优化)展示界面,加入注册时成功提示等1、基本步骤我们首先进行前三步:# 检测人脸,获取68个关键点,获取特征描述符def faceRegister(faceId=1, userName='default', interval=3, faceCount=3, resize_w=700, resize_h=400):'''faceId:人脸IDuserName: 人脸姓名faceCount: 采集该人脸图片的数量interval: 采集间隔'''cap = cv2.VideoCapture(0)# 人脸检测模型hog_face_detector = dlib.get_frontal_face_detector()# 关键点 检测模型shape_detector = dlib.shape_predictor('./weights/shape_predictor_68_face_landmarks.dat')# resnet模型face_descriptor_extractor = dlib.face_recognition_model_v1('./weights/dlib_face_recognition_resnet_model_v1.dat')while True:ret, frame = cap.read()# 镜像frame = cv2.flip(frame,1)# 转为灰度图frame_gray = cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)# 检测人脸detections = hog_face_detector(frame,1)for face in detections:# 人脸框坐标 左上和右下l, t, r, b = face.left(), face.top(), face.right(), face.bottom()# 获取68个关键点points = shape_detector(frame,face)# 绘制关键点for point in points.parts():cv2.circle(frame,(point.x,point.y),2,(0,255,0),1)# 绘制矩形框cv2.rectangle(frame,(l,t),(r,b),(0,255,0),2)cv2.imshow("face",frame)if cv2.waitKey(10) & 0xFF == ord('q'):breakcap.release()cv2.destroyAllWindowsfaceRegister()此时一张帅脸如下:2、描述符的采集之后,我们根据参数,即faceCount 和 Interval 进行描述符的生成和采集。(这里我默认是faceCount=3,Interval=3,即每3秒采集一次,共3次)def faceRegister(faceId=1, userName='default', interval=3, faceCount=3, resize_w=700, resize_h=400):'''faceId:人脸IDuserName: 人脸姓名faceCount: 采集该人脸图片的数量interval: 采集间隔'''cap = cv2.VideoCapture(0)# 人脸检测模型hog_face_detector = dlib.get_frontal_face_detector()# 关键点 检测模型shape_detector = dlib.shape_predictor('./weights/shape_predictor_68_face_landmarks.dat')# resnet模型face_descriptor_extractor = dlib.face_recognition_model_v1('./weights/dlib_face_recognition_resnet_model_v1.dat')# 开始时间start_time = time.time()# 执行次数collect_times = 0while True:ret, frame = cap.read()# 镜像frame = cv2.flip(frame,1)# 转为灰度图frame_gray = cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)# 检测人脸detections = hog_face_detector(frame,1)for face in detections:# 人脸框坐标 左上和右下l, t, r, b = face.left(), face.top(), face.right(), face.bottom()# 获取68个关键点points = shape_detector(frame,face)# 绘制人脸关键点for point in points.parts():cv2.circle(frame, (point.x, point.y), 2, (0, 255, 0), 1)# 绘制矩形框cv2.rectangle(frame, (l, t), (r, b), (0, 255, 0), 2)# 采集:if collect_times < faceCount:# 获取当前时间now = time.time()# 时间限制if now - start_time > interval:# 获取特征描述符face_descriptor = face_descriptor_extractor.compute_face_descriptor(frame,points)# dlib格式转为数组face_descriptor = [f for f in face_descriptor]collect_times += 1start_time = nowprint("成功采集{}次".format(collect_times))else:# 时间间隔不到intervalprint("等待进行下一次采集")passelse:# 已经成功采集完3次了print("采集完毕")cap.release()cv2.destroyAllWindows()returncv2.imshow("face",frame)if cv2.waitKey(10) & 0xFF == ord('q'):breakcap.release()cv2.destroyAllWindows()faceRegister()等待进行下一次采集...成功采集1次等待进行下一次采集...成功采集2次等待进行下一次采集...成功采集3次采集完毕3、完整的注册最后就是写入csv文件这里加入了注册成功等的提示,且把一些变量放到了全局,因为后面人脸识别打卡时也会用到。# 加载人脸检测器hog_face_detector = dlib.get_frontal_face_detector()cnn_detector = dlib.cnn_face_detection_model_v1('./weights/mmod_human_face_detector.dat')haar_face_detector = cv2.CascadeClassifier('./weights/haarcascade_frontalface_default.xml')# 加载关键点检测器points_detector = dlib.shape_predictor('./weights/shape_predictor_68_face_landmarks.dat')# 加载resnet模型face_descriptor_extractor = dlib.face_recognition_model_v1('./weights/dlib_face_recognition_resnet_model_v1.dat')# 绘制中文def cv2AddChineseText(img, text, position, textColor=(0, 255, 0), textSize=30):if (isinstance(img, np.ndarray)): # 判断是否OpenCV图片类型img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))# 创建一个可以在给定图像上绘图的对象draw = ImageDraw.Draw(img)# 字体的格式fontStyle = ImageFont.truetype("./fonts/songti.ttc", textSize, encoding="utf-8")# 绘制文本draw.text(position, text, textColor, font=fontStyle)# 转换回OpenCV格式return cv2.cvtColor(np.asarray(img), cv2.COLOR_RGB2BGR)# 绘制左侧信息def drawLeftInfo(frame, fpsText, mode="Reg", detector='haar', person=1, count=1):# 帧率cv2.putText(frame, "FPS: " + str(round(fpsText, 2)), (30, 50), cv2.FONT_ITALIC, 0.8, (0, 255, 0), 2)# 模式:注册、识别cv2.putText(frame, "Mode: " + str(mode), (30, 80), cv2.FONT_ITALIC, 0.8, (0, 255, 0), 2)if mode == 'Recog':# 检测器cv2.putText(frame, "Detector: " + detector, (30, 110), cv2.FONT_ITALIC, 0.8, (0, 255, 0), 2)# 人数cv2.putText(frame, "Person: " + str(person), (30, 140), cv2.FONT_ITALIC, 0.8, (0, 255, 0), 2)# 总人数cv2.putText(frame, "Count: " + str(count), (30, 170), cv2.FONT_ITALIC, 0.8, (0, 255, 0), 2)# 注册人脸def faceRegiser(faceId=1, userName='default', interval=3, faceCount=3, resize_w=700, resize_h=400):# 计数count = 0# 开始注册时间startTime = time.time()# 视频时间frameTime = startTime# 控制显示打卡成功的时长show_time = (startTime - 10)# 打开文件f = open('./data/feature.csv', 'a', newline='')csv_writer = csv.writer(f)cap = cv2.VideoCapture(0)while True:ret, frame = cap.read()frame = cv2.resize(frame, (resize_w, resize_h))frame = cv2.flip(frame, 1)# 检测face_detetion = hog_face_detector(frame, 1)for face in face_detetion:# 识别68个关键点points = points_detector(frame, face)# 绘制人脸关键点for point in points.parts():cv2.circle(frame, (point.x, point.y), 2, (0, 255, 0), 1)# 绘制框框l, t, r, b = face.left(), face.top(), face.right(), face.bottom()cv2.rectangle(frame, (l, t), (r, b), (0, 255, 0), 2)now = time.time()if (now - show_time) < 0.5:frame = cv2AddChineseText(frame,"注册成功 {count}/{faceCount}".format(count=(count + 1), faceCount=faceCount),(l, b + 30), textColor=(255, 0, 255), textSize=30)# 检查次数if count < faceCount:# 检查时间if now - startTime > interval:# 特征描述符face_descriptor = face_descriptor_extractor.compute_face_descriptor(frame, points)face_descriptor = [f for f in face_descriptor]# 描述符增加进data文件line = [faceId, userName, face_descriptor]# 写入csv_writer.writerow(line)# 保存照片样本print('人脸注册成功 {count}/{faceCount},faceId:{faceId},userName:{userName}'.format(count=(count + 1),faceCount=faceCount,faceId=faceId,userName=userName))frame = cv2AddChineseText(frame,"注册成功 {count}/{faceCount}".format(count=(count + 1), faceCount=faceCount),(l, b + 30), textColor=(255, 0, 255), textSize=30)show_time = time.time()# 时间重置startTime = now# 次数加一count += 1else:print('人脸注册完毕')f.close()cap.release()cv2.destroyAllWindows()returnnow = time.time()fpsText = 1 / (now - frameTime)frameTime = now# 绘制drawLeftInfo(frame, fpsText, 'Register')cv2.imshow('Face Attendance Demo: Register', frame)if cv2.waitKey(10) & 0xFF == ord('q'):breakf.close()cap.release()cv2.destroyAllWindows()此时执行:faceRegiser(3,"用户B")人脸注册成功 1/3,faceId:3,userName:用户B人脸注册成功 2/3,faceId:3,userName:用户B人脸注册成功 3/3,faceId:3,userName:用户B人脸注册完毕其features文件:B. 识别、打卡识别步骤如下:打开摄像头获取画面根据画面中的图片获取里面的人脸特征描述符根据特征描述符将其与feature.csv文件里特征做距离判断获取ID、NAME考勤记录写入attendance.csv里这里与上面流程相似,不过是加了一个对比功能,距离小于阈值,则表示匹配成功。就加快速度不一步步来了,代码如下:# 刷新右侧考勤信息def updateRightInfo(frame, face_info_list, face_img_list):# 重新绘制逻辑:从列表中每隔3个取一批显示,新增人脸放在最前面# 如果有更新,重新绘制# 如果没有,定时往后移动left_x = 30left_y = 20resize_w = 80offset_y = 120index = 0frame_h = frame.shape[0]frame_w = frame.shape[1]for face in face_info_list[:3]:name = face[0]time = face[1]face_img = face_img_list[index]# print(face_img.shape)face_img = cv2.resize(face_img, (resize_w, resize_w))offset_y_value = offset_y * indexframe[(left_y + offset_y_value):(left_y + resize_w + offset_y_value), -(left_x + resize_w):-left_x] = face_imgcv2.putText(frame, name, ((frame_w - (left_x + resize_w)), (left_y + resize_w) + 15 + offset_y_value),cv2.FONT_ITALIC, 0.5, (0, 255, 0), 1)cv2.putText(frame, time, ((frame_w - (left_x + resize_w)), (left_y + resize_w) + 30 + offset_y_value),cv2.FONT_ITALIC, 0.5, (0, 255, 0), 1)index += 1return frame# 返回DLIB格式的facedef getDlibRect(detector='hog', face=None):l, t, r, b = None, None, None, Noneif detector == 'hog':l, t, r, b = face.left(), face.top(), face.right(), face.bottom()if detector == 'cnn':l = face.rect.left()t = face.rect.top()r = face.rect.right()b = face.rect.bottom()if detector == 'haar':l = face[0]t = face[1]r = face[0] + face[2]b = face[1] + face[3]nonnegative = lambda x: x if x >= 0 else 0return map(nonnegative, (l, t, r, b))# 获取CSV中信息def getFeatList():print('加载注册的人脸特征')feature_list = Nonelabel_list = []name_list = []# 加载保存的特征样本with open('./data/feature.csv', 'r') as f:csv_reader = csv.reader(f)for line in csv_reader:# 重新加载数据faceId = line[0]userName = line[1]face_descriptor = eval(line[2])label_list.append(faceId)name_list.append(userName)# 转为numpy格式face_descriptor = np.asarray(face_descriptor, dtype=np.float64)# 转为二维矩阵,拼接face_descriptor = np.reshape(face_descriptor, (1, -1))# 初始化if feature_list is None:feature_list = face_descriptorelse:# 拼接feature_list = np.concatenate((feature_list, face_descriptor), axis=0)print("特征加载完毕")return feature_list, label_list, name_list# 人脸识别def faceRecognize(detector='haar', threshold=0.5, write_video=False, resize_w=700, resize_h=400):# 视频时间frameTime = time.time()# 加载特征feature_list, label_list, name_list = getFeatList()face_time_dict = {}# 保存name,time人脸信息face_info_list = []# numpy格式人脸图像数据face_img_list = []# 侦测人数person_detect = 0# 统计人脸数face_count = 0# 控制显示打卡成功的时长show_time = (frameTime - 10)# 考勤记录f = open('./data/attendance.csv', 'a')csv_writer = csv.writer(f)cap = cv2.VideoCapture(0)# resize_w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))//2# resize_h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) //2videoWriter = cv2.VideoWriter('./record_video/out' + str(time.time()) + '.mp4', cv2.VideoWriter_fourcc(*'MP4V'), 15,(resize_w, resize_h))while True:ret, frame = cap.read()frame = cv2.resize(frame, (resize_w, resize_h))frame = cv2.flip(frame, 1)# 切换人脸检测器if detector == 'hog':face_detetion = hog_face_detector(frame, 1)if detector == 'cnn':face_detetion = cnn_detector(frame, 1)if detector == 'haar':frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)face_detetion = haar_face_detector.detectMultiScale(frame_gray, minNeighbors=7, minSize=(100, 100))person_detect = len(face_detetion)for face in face_detetion:l, t, r, b = getDlibRect(detector, face)face = dlib.rectangle(l, t, r, b)# 识别68个关键点points = points_detector(frame, face)cv2.rectangle(frame, (l, t), (r, b), (0, 255, 0), 2)# 人脸区域face_crop = frame[t:b, l:r]# 特征face_descriptor = face_descriptor_extractor.compute_face_descriptor(frame, points)face_descriptor = [f for f in face_descriptor]face_descriptor = np.asarray(face_descriptor, dtype=np.float64)# 计算距离distance = np.linalg.norm((face_descriptor - feature_list), axis=1)# 最小距离索引min_index = np.argmin(distance)# 最小距离min_distance = distance[min_index]predict_name = "Not recog"if min_distance < threshold:# 距离小于阈值,表示匹配predict_id = label_list[min_index]predict_name = name_list[min_index]# 判断是否新增记录:如果一个人距上次检测时间>3秒,或者换了一个人,将这条记录插入need_insert = Falsenow = time.time()if predict_name in face_time_dict:if (now - face_time_dict[predict_name]) > 3:# 刷新时间face_time_dict[predict_name] = nowneed_insert = Trueelse:# 还是上次人脸need_insert = Falseelse:# 新增数据记录face_time_dict[predict_name] = nowneed_insert = Trueif (now - show_time) < 1:frame = cv2AddChineseText(frame, "打卡成功", (l, b + 30), textColor=(0, 255, 0), textSize=40)if need_insert:# 连续显示打卡成功1sframe = cv2AddChineseText(frame, "打卡成功", (l, b + 30), textColor=(0, 255, 0), textSize=40)show_time = time.time()time_local = time.localtime(face_time_dict[predict_name])# 转换成新的时间格式(2016-05-05 20:28:54)face_time = time.strftime("%H:%M:%S", time_local)face_time_full = time.strftime("%Y-%m-%d %H:%M:%S", time_local)# 开始位置增加face_info_list.insert(0, [predict_name, face_time])face_img_list.insert(0, face_crop)# 写入考勤表line = [predict_id, predict_name, min_distance, face_time_full]csv_writer.writerow(line)face_count += 1# 绘制人脸点cv2.putText(frame, predict_name + " " + str(round(min_distance, 2)), (l, b + 30), cv2.FONT_ITALIC, 0.8,(0, 255, 0), 2)# 处理下一张脸now = time.time()fpsText = 1 / (now - frameTime)frameTime = now# 绘制drawLeftInfo(frame, fpsText, 'Recog', detector=detector, person=person_detect, count=face_count)# 舍弃face_img_list、face_info_list后部分,节约内存if len(face_info_list) > 10:face_info_list = face_info_list[:9]face_img_list = face_img_list[:9]frame = updateRightInfo(frame, face_info_list, face_img_list)if write_video:videoWriter.write(frame)cv2.imshow('Face Attendance Demo: Recognition', frame)if cv2.waitKey(10) & 0xFF == ord('q'):breakf.close()videoWriter.release()cap.release()cv2.destroyAllWindows()然后效果就和我们宿舍楼下差不多了~我年轻的时候,我大概比现在帅个几百倍吧,哎。二、总代码上文其实把登录和注册最后一部分代码放在一起就是了,这里就不再复制粘贴了,相关权重文件下载链接:opencv/data at master · opencv/opencv · GitHub懒得下载或者懒得找也可以私信我发你,看见或有时间回。当然本项目还有很多需要优化的地方,比如设置用户不能重复、考勤打卡每天只能一次、把csv改为链接成数据库等等,后续代码优化完成后就可以部署然后和室友**了。————————————————版权声明:本文为CSDN博主「老师我作业忘带了」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/suic009/article/details/127382811
  • [技术干货] Ubuntu20.04编译安装opencv3.4.12/opencv4.2.0
     系统信息:Ubuntu20.04 aarch64安装包下载:官网:cid:link_1​选择source下载压缩包其他途径:cid:link_0将压缩包传至服务器并解压(此处以zip文件为例)​​​​​​​unzip opencv-3.4.12.zip进入解压后的文件夹cd opencv-3.4.12创建build文件夹,并进入mkdir buildcd build进行文件编译及安装方法一:4.0以上版本使用方法二cmake -D CMAKE_BUILD_TYPE=Release -D CMAKE_INSTALL_PREFIX=/usr/local ..方法二:cmake -D WITH_TBB=ON -D WITH_EIGEN=ON -D OPENCV_GENERATE_PKGCONFIG=ON -D BUILD_DOCS=ON -D BUILD_TESTS=OFF -D BUILD_PERF_TESTS=OFF -D BUILD_EXAMPLES=OFF -D WITH_OPENCL=OFF -D WITH_CUDA=OFF -D BUILD_opencv_gpu=OFF -D BUILD_opencv_gpuarithm=OFF -D BUILD_opencv_gpubgsegm=O -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local ..编译安装:makemake install执行以下命令验证是否安装成功:opencv_version环境配置打开/etc/ld.so.conf 并在最后面添加include /usr/local/libvim /etc/ld.so.conf/usr/local 是opencv安装路径,也就是makefile中的指定安装路径添加环境变量vim /etc/bash.bashrc在文末添加如下内容:PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfigexport PKG_CONFIG_PATH初始化环境变量:source /etc/bash.bashrc验证是否安装成功执行以下命令验证:pkg-config opencv --modversionFAQ1.如果验证过程中出现如下报错:Package opencv was not found in the pkg-config search path.Perhaps you should add the directory containing `opencv.pc'to the PKG_CONFIG_PATH environment variableNo package 'opencv' found解决办法:确认config文件中是否存在opencv.pc这个文件,按照上面的操作,pc 文件应该在“/usr/local/lib/pkgconfig/”路径下,进入路径验证即可:可以看到,路径下有一个文件是“opencv4.pc”,因为这边安装的是opencv4.2.0,那么,直接修改文件名来让系统可以读到“opencv.pc”:执行命令:mv /usr/local/lib/pkgconfig/opencv4.pc /usr/local/lib/pkgconfig/opencv.pc执行验证命令得到版本号即可
  • [其他] 记Anaconda安装配置Python和Opencv
    # Anaconda安排配置Python和Opencvhttps://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/ 全√conda config –-add channels https://mirrors.ustc.edu.cn/anaconda/pkgs/main/conda config –-set show_channel_urls yes //设置搜索时显示通道conda config –-show channels创建名为opencv的新环境:conda create -n opencv python=3.7y# 安装opencv-python库:activate opencvpip install -i https://pypi.tuna.tsinghua.edu.cn/simple opencv-pythonpip install -i https://pypi.tuna.tsinghua.edu.cn/simple matplotlib# pip install opencv-python (如果只用主模块,使用这个命令安装)pip install opencv-contrib-python (如果需要用主模块和contrib模块,使用这个命令安装)# 报错ModuleNotFoundError: No module named 'cv2'用conda安装:conda install opencv# 或者在导入opencv前,写上:import syssys.path.append('/*/*/*/python-*/site-packages')
  • [其他] 图像处理之理解Homography matrix(单应性矩阵)
    转自公众号:小白学视觉图像处理之理解Homography matrix(单应性矩阵)单应性矩阵是投影几何中一个术语,本质上它是一个数学概念,但是在OpenCV中却是有几个函数与透视变换相关的函数,都用到了单应性矩阵的概念与知识。小编跟很多人一样,刚开始学习图像处理对单应性矩阵不是很了解,通过项目实践慢慢知道了一些这方面的知识和自己对它的理解,就跟大家分享一下。单应性矩阵概念这里说的单应性矩阵主要是指平面单应性矩阵,在三轴坐标中XYZ,Z=1这个有点类似于三维的齐次坐标。单应性矩阵主要用来解决两个问题,一是表述真实世界中一个平面与对应它图像的透视变换二是从通过透视变换实现图像从一种视图变换到另外一种视图首先看一下在三维空间中任意两个平面上图的中零点分别表示两个平面中任意两个点,a1、a2与b1、b2是这两点对应的两个方向上的线性向量。对于这两个平面直接的关系我们就可以通过这些点从而进一步确立两个平面直接的关系,而两个平面之间的关系用单应性矩阵来描述如下:这种关系被称为平面单应性。这个当中有一些数学知识推导,感兴趣的大家可以自己去看,我们最重要的是明白这个概念怎么来的。其次知道它的应用场景,下面我们就从应用层面和代码层面来说说单应性矩阵的应用。 - 用来解决拍照时候图像扭曲问题。这个在上一篇文章透视   变换中讲过,但是   当时没有说这个是单应性矩阵的应用。- 此外还两个计算机图形学的应用场景分布是纹理渲染与计算平面阴影。- 用来实现图像拼接时候解决对齐问题应用案例街拍的时候路两边有很多广告牌,如果在视频实时帧中获取到对应的广告牌位置,就可以获取广告牌的四个角坐标,然后通过准备好的内容,将广告牌内容替换,得到想要的虚拟广告牌效果,而这个过程中最重要的一步,可以通过计算单应性矩阵实现内容替换,演示效果如下: 时代广场的街拍看到左侧的广告牌不,我们准好了一张图像,准备替换它的内容,准备的图像如下:最终处理之后的效果如下:实现代码如下:#include #include #include using namespace cv;using namespace std;int main(int argc, char** argv) { // load images Mat src = imread("D:/vcprojects/images/times-square.jpg"); if (!src.data) { printf("could not load image..."); return -1; } // show images namedWindow("input image", CV_WINDOW_AUTOSIZE); imshow("input image", src); Mat replaceImg = imread("D:/vcprojects/images/kgirls.png"); imshow("adv content", replaceImg); // 定义两个平面上四个角坐标 vector src_corners(4); vector dst_corners(4); // 原图像平面四点坐标 src_corners[0] = Point(0, 0); src_corners[1] = Point(replaceImg.cols, 0); src_corners[2] = Point(0, replaceImg.rows); src_corners[3] = Point(replaceImg.cols, replaceImg.rows); // 目标平面四个角坐标 dst_corners[0] = Point(70, 131); dst_corners[1] = Point(168,216); dst_corners[2] = Point(21, 199); dst_corners[3] = Point(148, 267); // 计算单应性矩阵与透视变换 Mat h = findHomography(src_corners, dst_corners); Mat output_img; warpPerspective(replaceImg, output_img, h, src.size()); // create mask Mat m1 = Mat::zeros(replaceImg.size(), CV_8UC1); m1 = Scalar(255); Mat mask_output; warpPerspective(m1, mask_output, h, src.size()); imshow("Mask Result", mask_output); // use mask Mat result1; add(output_img, output_img, result1, mask_output); Mat result2; bitwise_not(mask_output, mask_output); add(src, result1, result2, mask_output); // put them together Mat result; add(result1, result2, result); imshow("Final Result", result); imwrite("D:/vcprojects/images/result.png", result); waitKey(0); return 0;}
  • [问题求助] 【Ascend 710】没有硬件画图的接口吗?
    【功能模块】         因为公司有需求要进行画框的,没有硬件支持的话,接几十路,然后都用 opencv 画框的话,那得降多少性能啊?         解码出来的 YUV 要转到 BGR,BGR 内存转到 CPU 上的内存?然后再用 opencv 画框?这不能接受吧。         哎,我一开始以为 ascend 跟 cuda 一样能编程呢。感觉就是个大号的海思啊。
  • [问题求助] 【 摄像机SDC产品】【opencv视频采集功能】RTSP帧率不稳定
    【功能模块】windows 10 pc直连网线连接摄像机,使用python和opencv,从RTSP流采集、显示和编码图像帧,有明显的卡顿和跳帧现象;并做了如下尝试:1、抓RTSP子码流2、修改华为摄像头参数:如帧率(25->20)、是否动态帧率(如果勾选,则去勾选),帧间隔(50->80),I帧间隔(50->25)问题依然存在型号、版本和配置如下摄像头编号场景型号版本配置1下轴瓦D2150-10-SIUSDC 9.0.0见下2气缸盖D2150-10-SIUSDC 9.0.0 SPC307见下3活塞轴瓦D2150-10-SIUSDC 9.0.0见下4openlab实验室X2391-10-TLSDC 9.0.0.SPC602见下摄像头配置(1&4):摄像头配置(2):摄像头配置(3):【操作步骤&问题现象】使用python和opencv采集摄像头图像帧:1、collect线程循环VideoCapture.read(),将获取的解码后的图像帧放入队列,sleep(10),单位毫秒2、encode线程循环读取队列,将图像帧显示并编码成JPEG,发送边侧进行AI推理【截图信息】【日志信息】(可选,上传日志内容或者附件)通过日志分析,发现帧率不稳定,存在很多跳秒,见附件csv_path.rar(encode_time列是encode线程每秒执行时间,单位:毫秒;collect_time列是collect线程每秒执行时间,单位:毫秒)。
  • [问题求助] Atlas 200 DK不能用opencv直接调用摄像头
    【功能模块】试了多次,Atlas 200 DK无法使用opencv直接调用摄像头,请问该如何解决?谢谢!【操作步骤&问题现象】1、2、【截图信息】【日志信息】(可选,上传日志内容或者附件)
  • [问题求助] Atlas 200 DK能用opencv直接调用树莓派摄像头么?
    【功能模块】Atlas 200 DK能否用opencv直接调用摄像头,如果不能有哪些利用opencv间接调用的方法,是否有相关的案例可以提供,谢谢!【操作步骤&问题现象】1、2、【截图信息】【日志信息】(可选,上传日志内容或者附件)
总条数:305 到第
上滑加载中