亚博microros小车-原生ubuntu支持系列:8-脸部检测与人脸特效
前面的都是使用了mediapipe框架。后面的这两节采用了opencv\dlib的框架。
一 脸部检测
核心:opencv
detectMultiScale函数
detectMultiScale(image, scaleFactor, minNeighbors, flags, minSize, maxSize)
image--待检测图片,一般为灰度图像加快检测速度;
scaleFactor参数控制每个图像序列的缩放比例。该参数决定了在每个图像序列中检测窗口的大小。默认值为1.1,表示每次图像被缩小10%。较小的值可以捕捉更多的细节,但也会增加计算量。较大的值可以加快检测速度,但可能会错过一些目标。
minNeighbors参数定义了每个目标至少应该有多少个邻居,才能被认为是一个目标。该参数用于过滤检测到的目标。
flags参数用于定义检测模式。它可以是以下几个值的组合:
- CASCADE_SCALE_IMAGE:使用缩放图像进行检测(默认值)。
- CASCADE_FIND_BIGGEST_OBJECT:只检测最大的目标。
- CASCADE_DO_ROUGH_SEARCH:快速搜索模式。
minSize、maxSize参数用于指定检测目标的最小、最大尺寸。
src/yahboom_esp32_mediapipe/yahboom_esp32_mediapipe/目录下新建05_FaceEyeDetection.py,代码如下:
#!/usr/bin/env python2
# encoding: utf-8
#import ros lib
import rclpy
from rclpy.node import Node
from geometry_msgs.msg import Point
import mediapipe as mp
from cv_bridge import CvBridge
from sensor_msgs.msg import CompressedImage,Image
#import define msg
from yahboomcar_msgs.msg import PointArray
#import commom lib
import cv2 as cv
import numpy as np
import time
from cv_bridge import CvBridge
from sensor_msgs.msg import Image, CompressedImagefrom rclpy.time import Time
import datetimeprint("import done")class FaceEyeDetection(Node):def __init__(self,name):super().__init__(name)self.bridge = CvBridge()#加载分类器self.eyeDetect = cv.CascadeClassifier( "/home/bohu/yahboomcar/yahboomcar_ws/src/yahboom_esp32_mediapipe/resource/haarcascade_eye.xml")self.faceDetect = cv.CascadeClassifier("/home/bohu/yahboomcar/yahboomcar_ws/src/yahboom_esp32_mediapipe/resource/haarcascade_frontalface_default.xml")self.pub_rgb = self.create_publisher(Image,"/FaceEyeDetection/image", 500)def cancel(self):self.pub_rgb.unregister()def face(self, frame):gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)#灰度faces = self.faceDetect.detectMultiScale(gray, 1.3)#检测for face in faces: frame = self.faceDraw(frame, face)return framedef eye(self, frame):gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)#灰度eyes = self.eyeDetect.detectMultiScale(gray, 1.3)#检测for eye in eyes:cv.circle(frame, (int(eye[0] + eye[2] / 2), int(eye[1] + eye[3] / 2)), (int(eye[3] / 2)), (0, 0, 255), 2)return frame#画框显示def faceDraw(self, frame, bbox, l=30, t=10):x, y, w, h = bboxx1, y1 = x + w, y + hcv.rectangle(frame, (x, y), (x + w, y + h), (255, 0, 255), 2)# Top left x,ycv.line(frame, (x, y), (x + l, y), (255, 0, 255), t)cv.line(frame, (x, y), (x, y + l), (255, 0, 255), t)# Top right x1,ycv.line(frame, (x1, y), (x1 - l, y), (255, 0, 255), t)cv.line(frame, (x1, y), (x1, y + l), (255, 0, 255), t)# Bottom left x1,y1cv.line(frame, (x, y1), (x + l, y1), (255, 0, 255), t)cv.line(frame, (x, y1), (x, y1 - l), (255, 0, 255), t)# Bottom right x1,y1cv.line(frame, (x1, y1), (x1 - l, y1), (255, 0, 255), t)cv.line(frame, (x1, y1), (x1, y1 - l), (255, 0, 255), t)return framedef pub_img(self, frame):self.pub_rgb.publish(self.bridge.cv2_to_imgmsg(frame, "bgr8"))class MY_Picture(Node):def __init__(self, name):super().__init__(name)self.bridge = CvBridge()self.sub_img = self.create_subscription(CompressedImage, '/espRos/esp32camera', self.handleTopic, 1) #获取esp32传来的图像self.last_stamp = Noneself.new_seconds = 0self.fps_seconds = 1self.face_eye_detection = FaceEyeDetection('face_eye_detection')self.content = ["face", "eye", "face_eye"]self.content_index = 0#回调函数def handleTopic(self, msg):self.last_stamp = msg.header.stamp if self.last_stamp:total_secs = Time(nanoseconds=self.last_stamp.nanosec, seconds=self.last_stamp.sec).nanosecondsdelta = datetime.timedelta(seconds=total_secs * 1e-9)seconds = delta.total_seconds()*100if self.new_seconds != 0:self.fps_seconds = seconds - self.new_secondsself.new_seconds = seconds#保留这次的值start = time.time()frame = self.bridge.compressed_imgmsg_to_cv2(msg)frame = cv.resize(frame, (640, 480))cv.waitKey(10)action = cv.waitKey(1) & 0xFFif action == ord("f") or action == ord("F"):self.content_index += 1if self.content_index >= len(self.content): self.content_index = 0if self.content[self.content_index] == "face": frame = self.face_eye_detection.face(frame)elif self.content[self.content_index] == "eye": frame = self.face_eye_detection.eye(frame)else: frame = self.face_eye_detection.eye(self.face_eye_detection.face(frame))end = time.time()fps = 1 / ((end - start)+self.fps_seconds)text = "FPS : " + str(int(fps))cv.putText(frame, text, (20, 30), cv.FONT_HERSHEY_SIMPLEX, 0.9, (0, 0, 255), 1)cv.imshow('frame', frame)self.face_eye_detection.pub_img(frame)# print(frame)cv.waitKey(10)def main():print("start it")rclpy.init()esp_img = MY_Picture("My_Picture")try:rclpy.spin(esp_img)except KeyboardInterrupt:passfinally:esp_img.destroy_node()rclpy.shutdown()
主要流程跟之前类似,这个不如mediapipe框架好,尤其是画框比较麻烦。
这个识别效果对比之前的face_recognition,我觉得不如那个好,参见:
ros2-4.2 用python实现人脸识别_ros2使用人脸检测-CSDN博客
构建后运行:
ros2 run yahboom_esp32_mediapipe FaceEyeDetection
效果如下
二 人脸特效
人脸检测
使用了dlib库
.get_frontal_face_detector()
功能:人脸检测画框
参数:无
返回值:默认的人脸检测器
shape_predictor()
功能:标记人脸关键点
参数:shape_predictor_68_face_landmarks.dat:68个关键点模型地址
返回值:人脸关键点预测器
import cv2
import mediapipe as mp
import dlibdetector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor("/home/bohu/yahboomcar/yahboomcar_ws/src/yahboom_esp32_mediapipe/resource/shape_predictor_68_face_landmarks.dat")cap = cv2.VideoCapture(0)#打开默认摄像头
while True:ret,frame = cap.read()#读取一帧图像#图像格式转换frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)# 因为摄像头是镜像的,所以将摄像头水平翻转# 不是镜像的可以不翻转frame= cv2.flip(frame,1)#输出结果gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)faces = detector(gray)print(f'faces:{len(faces)}')for face in faces:# 利用预测器预测shape = predictor(gray, face)# print(shape)# 标出68个点的位置for i in range(68):cv2.circle(frame, (shape.part(i).x, shape.part(i).y), 2, (0, 255, 0), -1, 1)cv2.putText(frame, str(i), (shape.part(i).x, shape.part(i).y), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255))cv2.imshow('opencv detectMultiScale', frame)if cv2.waitKey(1) & 0xFF == 27:break
cap.release()

人脸特效
基本思路就是在dlib人脸检测点上,在额外使用opencv画线\和fillConvexPoly填充多边形函数。
这种就是比较麻烦,没有官方的函数之间调用好。
src/yahboom_esp32_mediapipe/yahboom_esp32_mediapipe/目录下新建文件06_FaceLandmarks.py,代码如下:
#!/usr/bin/env python3
# encoding: utf-8
import rclpy
from rclpy.node import Node
import time
import dlib
import cv2 as cv
import numpy as np
from cv_bridge import CvBridge
from sensor_msgs.msg import Image, CompressedImagefrom rclpy.time import Time
import datetimeclass FaceLandmarks:def __init__(self, dat_file):self.hog_face_detector = dlib.get_frontal_face_detector()self.dlib_facelandmark = dlib.shape_predictor(dat_file)def get_face(self, frame, draw=True):gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)self.faces = self.hog_face_detector(gray)for face in self.faces:self.face_landmarks = self.dlib_facelandmark(gray, face)if draw:for n in range(68):x = self.face_landmarks.part(n).xy = self.face_landmarks.part(n).ycv.circle(frame, (x, y), 2, (0, 255, 255), 2)cv.putText(frame, str(n), (x, y), cv.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 255), 2)return framedef get_lmList(self, frame, p1, p2, draw=True):lmList = []if len(self.faces) != 0:for n in range(p1, p2):x = self.face_landmarks.part(n).xy = self.face_landmarks.part(n).ylmList.append([x, y])if draw:next_point = n + 1if n == p2 - 1: next_point = p1x2 = self.face_landmarks.part(next_point).xy2 = self.face_landmarks.part(next_point).ycv.line(frame, (x, y), (x2, y2), (0, 255, 0), 1)return lmListdef get_lipList(self, frame, lipIndexlist, draw=True):lmList = []if len(self.faces) != 0:for n in range(len(lipIndexlist)):x = self.face_landmarks.part(lipIndexlist[n]).xy = self.face_landmarks.part(lipIndexlist[n]).ylmList.append([x, y])if draw:next_point = n + 1if n == len(lipIndexlist) - 1: next_point = 0x2 = self.face_landmarks.part(lipIndexlist[next_point]).xy2 = self.face_landmarks.part(lipIndexlist[next_point]).ycv.line(frame, (x, y), (x2, y2), (0, 255, 0), 1)return lmListdef prettify_face(self, frame, eye=True, lips=True, eyebrow=True, draw=True):if eye:leftEye = landmarks.get_lmList(frame, 36, 42)rightEye = landmarks.get_lmList(frame, 42, 48)if draw:if len(leftEye) != 0: frame = cv.fillConvexPoly(frame, np.mat(leftEye), (0, 0, 0))if len(rightEye) != 0: frame = cv.fillConvexPoly(frame, np.mat(rightEye), (0, 0, 0))if lips:lipIndexlistA = [51, 52, 53, 54, 64, 63, 62]lipIndexlistB = [48, 49, 50, 51, 62, 61, 60]lipsUpA = landmarks.get_lipList(frame, lipIndexlistA, draw=True)lipsUpB = landmarks.get_lipList(frame, lipIndexlistB, draw=True)lipIndexlistA = [57, 58, 59, 48, 67, 66]lipIndexlistB = [54, 55, 56, 57, 66, 65, 64]lipsDownA = landmarks.get_lipList(frame, lipIndexlistA, draw=True)lipsDownB = landmarks.get_lipList(frame, lipIndexlistB, draw=True)if draw:if len(lipsUpA) != 0: frame = cv.fillConvexPoly(frame, np.mat(lipsUpA), (249, 0, 226))if len(lipsUpB) != 0: frame = cv.fillConvexPoly(frame, np.mat(lipsUpB), (249, 0, 226))if len(lipsDownA) != 0: frame = cv.fillConvexPoly(frame, np.mat(lipsDownA), (249, 0, 226))if len(lipsDownB) != 0: frame = cv.fillConvexPoly(frame, np.mat(lipsDownB), (249, 0, 226))if eyebrow:lefteyebrow = landmarks.get_lmList(frame, 17, 22)righteyebrow = landmarks.get_lmList(frame, 22, 27)if draw:if len(lefteyebrow) != 0: frame = cv.fillConvexPoly(frame, np.mat(lefteyebrow), (255, 255, 255))if len(righteyebrow) != 0: frame = cv.fillConvexPoly(frame, np.mat(righteyebrow), (255, 255, 255))return frameclass MY_Picture(Node):def __init__(self, name,landmarkss):super().__init__(name)self.bridge = CvBridge()self.sub_img = self.create_subscription(CompressedImage, '/espRos/esp32camera', self.handleTopic, 1) #获取esp32传来的图像self.landmarksros = landmarkssself.last_stamp = Noneself.new_seconds = 0self.fps_seconds = 1def handleTopic(self, msg):self.last_stamp = msg.header.stamp if self.last_stamp:total_secs = Time(nanoseconds=self.last_stamp.nanosec, seconds=self.last_stamp.sec).nanosecondsdelta = datetime.timedelta(seconds=total_secs * 1e-9)seconds = delta.total_seconds()*100if self.new_seconds != 0:self.fps_seconds = seconds - self.new_secondsself.new_seconds = seconds#保留这次的值start = time.time()frame = self.bridge.compressed_imgmsg_to_cv2(msg)frame = cv.resize(frame, (640, 480))cv.waitKey(10)frame = self.landmarksros.get_face(frame, draw=False)frame = self.landmarksros.prettify_face(frame, eye=True, lips=True, eyebrow=True, draw=True)end = time.time()fps = 1 / ((end - start)+self.fps_seconds)text = "FPS : " + str(int(fps))cv.putText(frame, text, (20, 30), cv.FONT_HERSHEY_SIMPLEX, 0.9, (0, 0, 255), 1)cv.imshow('frame', frame)landmarks = None
def main():global landmarksprint("start it")#使用官方训练好的dlib 68点模型dat_file = "/home/bohu/yahboomcar/yahboomcar_ws/src/yahboom_esp32_mediapipe/resource/shape_predictor_68_face_landmarks.dat"landmarks = FaceLandmarks(dat_file)rclpy.init()esp_img = MY_Picture("My_Picture",landmarks)try:rclpy.spin(esp_img)except KeyboardInterrupt:passfinally:esp_img.destroy_node()rclpy.shutdown()
构建后运行:
ros2 run yahboom_esp32_mediapipe FaceLandmarks
效果如下:

相关文章:
亚博microros小车-原生ubuntu支持系列:8-脸部检测与人脸特效
前面的都是使用了mediapipe框架。后面的这两节采用了opencv\dlib的框架。 一 脸部检测 核心:opencv detectMultiScale函数 detectMultiScale(image, scaleFactor, minNeighbors, flags, minSize, maxSize) image--待检测图片,一般为灰度图像加快检测…...
代码随想录算法训练营day32
代码随想录算法训练营 —day32 文章目录 代码随想录算法训练营前言一、动态规划理论基础二、509. 斐波那契数动态规划动态规划优化空间版递归法 三、70. 爬楼梯动态规划动态规划空间优化 746. 使用最小花费爬楼梯动态规划空间优化 总结 前言 今天是算法营的第32天,…...
缓存之美:万文详解 Caffeine 实现原理(下)
上篇文章:缓存之美:万文详解 Caffeine 实现原理(上) getIfPresent 现在我们对 put 方法有了基本了解,现在我们继续深入 getIfPresent 方法: public class TestReadSourceCode {Testpublic void doRead() …...
中企出海:从国际投资建厂:投前投中投后重点事项
1. 投前重点事项 1.1 市场调研与分析 在国际投资建厂的投前阶段,市场调研与分析是至关重要的基础工作,它能够帮助企业全面了解目标市场,为后续决策提供有力依据。 市场规模与潜力:通过收集和分析目标国家或地区的经济数据、行业…...
github登录用的TOTP和恢复码都丢失了怎么办
从22年左右开始github的登录就需要用TOTP的一个6位秘钥做二次认证登录,如果在用的TOTP软件失效了,可以用github开启二次认证时下载的恢复码重置认证,但是如果你和我一样这两个东西都没了就只能用邮箱重置了,过程给大家分享一下 一…...
最长递增子序列问题(Longest Increasing Subsequence),动态规划法解决,贪心算法 + 二分查找优化
问题描述:在一个大小乱序的数列中,找到一个最大长度的递增子序列,子序列中的数据在原始数列中的相对位置保持不变,可以不连续,但必须递增。 输入描述: 第一行输入数列的长度 n。(1 < n < 200) 第二…...
Python中采用.add_subplot绘制子图的方法简要举例介绍
Python中采用.add_subplot绘制子图的方法简要举例介绍 目录 Python中采用.add_subplot绘制子图的方法简要举例介绍一、Python中绘制子图的方法1.1 add_subplot函数1.2 基本语法(1)add_subplot的核心语法(2)add_subplot在中编程中的…...
纯 Python、Django、FastAPI、Flask、Pyramid、Jupyter、dbt 解析和差异分析
一、纯 Python 1.1 基础概念 Python 是一种高级、通用、解释型的编程语言,以其简洁易读的语法和丰富的标准库而闻名。“纯 Python” 在这里指的是不依赖特定的 Web 框架或数据分析工具,仅使用 Python 原生的功能和标准库来开发应用程序或执行任务。 1.…...
C++实现有限元二维杆单元计算 Bar2D2Node类(纯自研 非套壳)
本系列文章致力于实现“手搓有限元,干翻Ansys的目标”,基本框架为前端显示使用QT实现交互,后端计算采用Visual Studio C。 QT软件界面 具体软件操作可查看下方视频哦。也可以点击这里直接跳转。 直接干翻Ansys?小伙自研有限元 1、…...
wx036基于springboot+vue+uniapp的校园快递平台小程序
开发语言:Java框架:springbootuniappJDK版本:JDK1.8服务器:tomcat7数据库:mysql 5.7(一定要5.7版本)数据库工具:Navicat11开发软件:eclipse/myeclipse/ideaMaven包&#…...
Unity中两个UGUI物体的锚点和中心点设置成不一样的,然后怎么使两个物体的位置一样?
一、问题复现 需求:go1物体和我想把go1的位置跟go2的位置一样,但是我通过物体的anchoredPosition以及position还有localposiiton都没有解决问题,使用上面的这三个属性的效果如下: 运行之后,可以看出,go1的…...
兼职全职招聘系统架构与功能分析
2015工作至今,10年资深全栈工程师,CTO,擅长带团队、攻克各种技术难题、研发各类软件产品,我的代码态度:代码虐我千百遍,我待代码如初恋,我的工作态度:极致,责任ÿ…...
HTML5 History API
在 HTML5 的 History API 中,pushState 和 replaceState 方法也可以接受一个 state 对象作为参数。这些方法允许你在改变浏览器路由时不重新加载页面,并且可以附加一些自定义数据。 state 返回在 history 栈顶的 任意 值的拷贝。 let currentState h…...
2025_1_22打卡
402. 移掉 K 位数字 - 力扣(LeetCode) 279. 完全平方数 - 力扣(LeetCode)...
Formality:不可读(unread)的概念
相关阅读 Formalityhttps://blog.csdn.net/weixin_45791458/category_12841971.html?spm1001.2014.3001.5482https://blog.csdn.net/weixin_45791458/category_12841971.html?spm1001.2014.3001.5482 在Formality中有时会遇到不可读(unread)这个概念,本文就将对此…...
stm32f103C8T6和AT24C256链接
模拟IIC总线 myiic.c #ifndef __24CXX_H #define __24CXX_H #include "myiic.h" #define AT24C01 127 //1kbit1*1024/8128byte地址寻址范围为0-127 #define AT24C02 255 #define AT24C04 511 #define AT24C08 1023 #define AT24C16 2047 #define AT24C32 …...
5.SQLAlchemy对两张有关联关系表查询
问题 例如,一个用户可以有多个收获地址。 定义表如下: 用户表 地址表 一般情况,我们会先查询用户表,拿到用户id后,再到地址表中查询关联的地址数据。这样就要执行两次查询。 仅仅为了方便查询,需要一些属…...
2.2.1 语句结构
ST(Structured Text)语言是一种基于IEC 61131-3标准的高级文本编程语言,其语法规则严格且清晰。以下是ST语言中关于分号、注释和代码块的详细语法规则说明: 分号(;)作用:分号用于表示语句的结束。语法规则: 每个独立的语句必须以分号结尾。分号是语句的终止符,用于分隔…...
安当二代TDE透明加密技术与SMS凭据管理系统相结合的数据库安全解决方案
安当二代TDE透明加密技术与安当SMS凭据管理系统的结合,为企业提供了一套完整的数据库安全解决方案,涵盖字段级加密脱敏和动态凭据管理两大核心功能。以下是其实现方式和技术特点的详细说明: 一、安当二代TDE透明加密技术:字段级加…...
es的date类型字段按照原生格式进行分组聚合
PUT student2 { "mappings": {"properties": {"name": {"type": "text","analyzer": "standard" // 使用标准分析器,适合姓名字段},"birthday": {"type": "date&…...
(十)学生端搭建
本次旨在将之前的已完成的部分功能进行拼装到学生端,同时完善学生端的构建。本次工作主要包括: 1.学生端整体界面布局 2.模拟考场与部分个人画像流程的串联 3.整体学生端逻辑 一、学生端 在主界面可以选择自己的用户角色 选择学生则进入学生登录界面…...
AtCoder 第409场初级竞赛 A~E题解
A Conflict 【题目链接】 原题链接:A - Conflict 【考点】 枚举 【题目大意】 找到是否有两人都想要的物品。 【解析】 遍历两端字符串,只有在同时为 o 时输出 Yes 并结束程序,否则输出 No。 【难度】 GESP三级 【代码参考】 #i…...
linux arm系统烧录
1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 (忘了有没有这步了 估计有) 刷机程序 和 镜像 就不提供了。要刷的时…...
Python爬虫(二):爬虫完整流程
爬虫完整流程详解(7大核心步骤实战技巧) 一、爬虫完整工作流程 以下是爬虫开发的完整流程,我将结合具体技术点和实战经验展开说明: 1. 目标分析与前期准备 网站技术分析: 使用浏览器开发者工具(F12&…...
Python爬虫(一):爬虫伪装
一、网站防爬机制概述 在当今互联网环境中,具有一定规模或盈利性质的网站几乎都实施了各种防爬措施。这些措施主要分为两大类: 身份验证机制:直接将未经授权的爬虫阻挡在外反爬技术体系:通过各种技术手段增加爬虫获取数据的难度…...
从零实现STL哈希容器:unordered_map/unordered_set封装详解
本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说,直接开始吧! 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...
初探Service服务发现机制
1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能:服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源…...
现有的 Redis 分布式锁库(如 Redisson)提供了哪些便利?
现有的 Redis 分布式锁库(如 Redisson)相比于开发者自己基于 Redis 命令(如 SETNX, EXPIRE, DEL)手动实现分布式锁,提供了巨大的便利性和健壮性。主要体现在以下几个方面: 原子性保证 (Atomicity)ÿ…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
比较数据迁移后MySQL数据库和OceanBase数据仓库中的表
设计一个MySQL数据库和OceanBase数据仓库的表数据比较的详细程序流程,两张表是相同的结构,都有整型主键id字段,需要每次从数据库分批取得2000条数据,用于比较,比较操作的同时可以再取2000条数据,等上一次比较完成之后,开始比较,直到比较完所有的数据。比较操作需要比较…...

