-
一、Pillow PIL:Python Imaging Library,已经是Python平台事实上的图像处理标准库了。PIL功能非常强大,但API却非常简单易用。 由于PIL仅支持到Python 2.7,加上年久失修,于是一群志愿者在PIL的基础上创建了兼容的版本,名字叫Pillow,支持最新Python 3.x,又加入了许多新特性,因此,我们可以直接安装使用Pillow。 安装Pillow 如果安装了Anaconda,Pillow就已经可用了。否则,需要在命令行下通过pip安装: $ pip install pillow 如果遇到Permission denied安装失败,请加上sudo重试。 操作图像 来看看最常见的图像缩放操作,只需三四行代码: from PIL import Image # 打开一个jpg图像文件,注意是当前路径: im = Image.open('test.jpg') # 获得图像尺寸: w, h = im.size print('Original image size: %sx%s' % (w, h)) # 缩放到50%: im.thumbnail((w//2, h//2)) print('Resize image to: %sx%s' % (w//2, h//2)) # 把缩放后的图像用jpeg格式保存: im.save('thumbnail.jpg', 'jpeg') 其他功能如切片、旋转、滤镜、输出文字、调色板等一应俱全。 比如,模糊效果也只需几行代码: from PIL import Image, ImageFilter # 打开一个jpg图像文件,注意是当前路径: im = Image.open('test.jpg') # 应用模糊滤镜: im2 = im.filter(ImageFilter.BLUR) im2.save('blur.jpg', 'jpeg') 效果如下: PIL的ImageDraw提供了一系列绘图方法,让我们可以直接绘图。比如要生成字母验证码图片: from PIL import Image, ImageDraw, ImageFont, ImageFilter import random # 随机字母: def rndChar(): return chr(random.randint(65, 90)) # 随机颜色1: def rndColor(): return (random.randint(64, 255), random.randint(64, 255), random.randint(64, 255)) # 随机颜色2: def rndColor2(): return (random.randint(32, 127), random.randint(32, 127), random.randint(32, 127)) # 240 x 60: width = 60 * 4 height = 60 image = Image.new('RGB', (width, height), (255, 255, 255)) # 创建Font对象: font = ImageFont.truetype('Arial.ttf', 36) # 创建Draw对象: draw = ImageDraw.Draw(image) # 填充每个像素: for x in range(width): for y in range(height): draw.point((x, y), fill=rndColor()) # 输出文字: for t in range(4): draw.text((60 * t + 10, 10), rndChar(), font=font, fill=rndColor2()) # 模糊: image = image.filter(ImageFilter.BLUR) image.save('code.jpg', 'jpeg') 我们用随机颜色填充背景,再画上文字,最后对图像进行模糊,得到验证码图片如下: 如果运行的时候报错: IOError: cannot open resource 这是因为PIL无法定位到字体文件的位置,可以根据操作系统提供绝对路径,比如: '/Library/Fonts/Arial.ttf' 要详细了解PIL的强大功能,请请参考Pillow官方文档: Pillow (PIL Fork) 9.2.0 documentation 总结: PIL提供了操作图像的强大功能,可以通过简单的代码完成复杂的图像处理。 二、海龟绘图 在1966年,Seymour Papert和Wally Feurzig发明了一种专门给儿童学习编程的语言——LOGO语言,它的特色就是通过编程指挥一个小海龟(turtle)在屏幕上绘图。 海龟绘图(Turtle Graphics)后来被移植到各种高级语言中,Python内置了turtle库,基本上100%复制了原始的Turtle Graphics的所有功能。 我们来看一个指挥小海龟绘制一个长方形的简单代码: # 导入turtle包的所有内容: from turtle import * # 设置笔刷宽度: width(4) # 前进: forward(200) # 右转90度: right(90) # 笔刷颜色: pencolor('red') forward(100) right(90) pencolor('green') forward(200) right(90) pencolor('blue') forward(100) right(90) # 调用done()使得窗口等待被关闭,否则将立刻关闭窗口: done() 在命令行运行上述代码,会自动弹出一个绘图窗口,然后绘制出一个长方形: 从程序代码可以看出,海龟绘图就是指挥海龟前进、转向,海龟移动的轨迹就是绘制的线条。要绘制一个长方形,只需要让海龟前进、右转90度,反复4次。 调用width()函数可以设置笔刷宽度,调用pencolor()函数可以设置颜色。更多操作请参考turtle库的说明。 绘图完成后,记得调用done()函数,让窗口进入消息循环,等待被关闭。否则,由于Python进程会立刻结束,将导致窗口被立刻关闭。 turtle包本身只是一个绘图库,但是配合Python代码,就可以绘制各种复杂的图形。例如,通过循环绘制5个五角星: from turtle import * def drawStar(x, y): pu() goto(x, y) pd() # set heading: 0 seth(0) for i in range(5): fd(40) rt(144) for x in range(0, 250, 50): drawStar(x, 0) done() 程序执行效果如下: 使用递归,可以绘制出非常复杂的图形。例如,下面的代码可以绘制一棵分型树: from turtle import * # 设置色彩模式是RGB: colormode(255) lt(90) lv = 14 l = 120 s = 45 width(lv) # 初始化RGB颜色: r = 0 g = 0 b = 0 pencolor(r, g, b) penup() bk(l) pendown() fd(l) def draw_tree(l, level): global r, g, b # save the current pen width w = width() # narrow the pen width width(w * 3.0 / 4.0) # set color: r = r + 1 g = g + 2 b = b + 3 pencolor(r % 200, g % 200, b % 200) l = 3.0 / 4.0 * l lt(s) fd(l) if level < lv: draw_tree(l, level + 1) bk(l) rt(2 * s) fd(l) if level < lv: draw_tree(l, level + 1) bk(l) lt(s) # restore the previous pen width width(w) speed("fastest") draw_tree(l, 4) done() 执行上述程序需要花费一定的时间,最后的效果如下: ———————————————— 版权声明:本文为CSDN博主「wespten」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qq_35029061/article/details/127356389
-
Hi~!合作伙伴们!欢迎来到政务一网通军团伙伴使能中心-AI使能版块!!不管你是为了什么来到这里,我们都会想办法满足你的需求!在AI使能中心,我们会为您介绍政务一网通军团AI领域使能地图、技术干货、使用宝典、培训资料、问题求助等模块,为各位开发者提供开发必须的资料和平台~下面就来一一介绍!AI领域使能地图 LINK由云能力中心,结合政务一网通各个行业场景,整理得出的行业场景流程内使用AI领域各个云服务做了哪些事情,从全局到具体,一览无遗,清晰各个云服务定位。技术干货 LINK我们会为合作伙伴通过通用场景,聚合云服务原子化API形成场景化API,加速开发。也会为您推送AI领域更多技术干货~使用宝典 LINK为您提供更细节的使用手册,提高开发效率,快速集成场景化能力!培训资料 LINK针对政务一网通军团开发场景,为开发者针对性整理培训资料,包括常用的开发架构、开发工具等等。磨刀不误砍柴工,懂得越多,干得越快!!问题求助 LINK 如果您在开发中遇到问题或者有流程问题,欢迎来本社区发帖求助。如果您的问题较为敏感,也可以私信版主 -》 E青年发帖格式如下~标题:【场景 如:一网通办】【涉及华为云产品:OCR算法】具体问题描述内容:问题一句话总结:使用场景或目的:(选填)问题现象截图:(选填)操作步骤:(选填)希望优化的地方或希望实现的效果:填写完成后,“接收回复通知”,上传必要的附件,点击“发表帖子”。注意:如果要输入手机号和邮箱等隐私数据时可选择“回帖仅作者可见”。 “回帖仅作者可见”模式下其余论坛用户将看到不到回帖内容,仅园区版主可见。更多资讯 LINK后面我们也会不定期发布应用开发招募令,一起来加入政务一网通AI开发吧!!当前版块还在搭建中,材料缺失,还望海涵,我们会不断完善版块资料,快速成为合作伙伴可以依赖的开发者社区 ٩( ´︶` )( ´︶` )۶ ~
-
目录 0 写在前面 1 diffusion vs GAN 2 NovelAI 3 AI绘画环境搭建 4 体验AI创作 0 写在前面 机器学习强基计划聚焦深度和广度,加深对机器学习模型的理解与应用。“深”在详细推导算法模型背后的数学原理;“广”在分析多个机器学习模型:决策树、支持向量机、贝叶斯与马尔科夫决策、强化学习等。 🚀详情:机器学习强基计划(附几十种经典模型源码合集) 话不多说,先看看AI绘画的效果 接下来带大家体验一下二次元画师novelAI 1 diffusion vs GAN 所谓扩散算法diffusion是指先将一幅画面逐步加入噪点,一直到整个画面都变成白噪声。记录这个过程,然后逆转过来给AI学习。 AI看到的是什么?一个全是噪点的画面如何一点点变清晰直到变成一幅画,AI通过学习这个逐步去噪点的过程来学会作画。 diffusion和之前大火的GAN模型相比,有什么优势呢?用OpenAI的一篇论文内容来讲,用diffusion生成的图像质量明显优于GAN模型;而且与GAN不同,diffusion不用在鞍点问题上纠结——涉及稳定性问题,只需要去最小化一个标准的凸交叉熵损失即可,这样就大大简化了模型训练过程中,数据处理的难度。 总结来说,目前的训练技术让diffusion直接跨越了GAN领域调模型的阶段,而是直接可以用来做下游任务,是一个新的数学范式在图像领域应用的实例。 2 NovelAI NovelAI是基于人工智能的绘画创作辅助工具,单纯训练就使用了数千个网站的数十亿张图片。NovelAI是一个商业化的网站,绘画是需要付费的,然而这次其商业模型疑似流出,不管是小白还是画师都能体验一次AI创作的乐趣。这次流出的模型是stable-diffusion-webui,也就是第一节介绍的扩散算法。 3 AI绘画环境搭建 主要分为以下步骤: 创建Python3.10的虚拟环境 conda create -n ai_draw python=3.10 1 虚拟环境相关操作请参考:Anaconda安装与Python虚拟环境配置保姆级图文教程(附速查字典) 在虚拟环境中安装Pytorch 这步比较涉及显卡相关的配置,比较复杂,请参考:最新CUDA/cuDNN与Pytorch保姆级图文安装教程(速查字典版) 下载模型stable-diffusion-webui git clone https://github.com/AUTOMATIC1111/stable-diffusion-webui 1 安装依赖库 首先进入虚拟环境 conda activate ai_draw 1 接着进入stable-diffusion-webui根目录运行 pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt 1 采用清华源可以更快完成安装 下载模型和权重文件 模型文件model.cpkt:model.cpkt,权重文件GFPGANv1.4.pth:GFPGANv1.4.pth,模型文件移动到这个目录stable-diffusion-webui-master\models\Stable-diffusion,权重文件留在根目录即可,最后的文件组织如图所示 根目录运行启动文件 python launch.py 1 期间会下载一些新的依赖,有可能超时报错,多启动几次即可 4 体验AI创作 通过第三节的配置,成功后即可在终端看到 开放了一个本地端口,在浏览器中输入即可 接下来测试最简单的文本生成图像功能: 输入正面标签: NSFW,anime fine details portrait of a white haired girl ,black eyes,wearing hoodie on the city street background, close-up view, anime masterpiece,4k, sharp high quality anime, artstation Prhololive, uruha_rushia, 1girl, bangs, bare shoulders, red eyes, blue dress, blue green hair,blue sleeves, blush, bow, breasts, chick, collarbone, detached collar, detached sleeves, double bun, eyebrows visible through hair,frills, hair orhament, medium hair, off-shoulder dress 1girl looking_at_viewer upper_body, too many flowers,kyoto animation, bishojo, bare_shoulders black sleeves yellow_eyes emeraldblue_hair, sailor_collar and neckerchief, detached_sleeves frilled_shirt_collar frills grey_shirt headset heart long_hairsleeveless shirt twintails verv long hair wide sleeves highres 2girl, two bishojo snuggled up selfie, with white marble glowing skin and perfect symmetrical pretty face with blush cheeks and glaring eyes wearing a school uniform,golden hour lighting, strong rim light, intense shadowse , Ernst Thoms 1 输入反面标签: lowres, bad anatomy, bad hands, text,error, missing fngers,extra digt ,fewer digits,cropped, wort quality ,low quality,normal quality, jpeg artifacts,signature,watermark, username, blurry, bad feet 1 设置采样步长sampling step为28 配置CFG Scale为12 点击Generate 当然这里还有img2img等丰富的功能,读者可自行测试 ———————————————— 版权声明:本文为CSDN博主「Mr.Winter`」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/FRIGIDWINTER/article/details/127471600
-
如何用人工智能自动玩游戏 一、前言 让AI玩游戏的思想早在上世纪就已经有了,那个时候更偏向棋类游戏。像是五子棋、象棋等。在上世纪“深蓝”就击败了国际象棋冠军,而到2016年“Alpha Go”击败了人类围棋冠军。 到现在,AI涉略的不仅仅是棋类游戏。像是超级马里奥、王者荣耀这种游戏,AI也能有比较好的表现。今天我们就来用一个实际的例子讨论AI自动玩游戏这一话题,本文会用非常简单的机器学习算法让AI自动玩Google小恐龙游戏。 二、Google小恐龙与监督学习 2.1、Google小恐龙 如果你使用的是Chrome浏览器,那么相信你应该见过下面这个恐龙: 当我们用Chrome断网访问网页时,就会显示这个恐龙,或者直接在地址栏输入:chrome://dino直接访问该游戏。 游戏的玩法非常简单,只需要按空格键即可。比如下面左图,快碰到障碍物,这时需要按空格,而下面右图没有障碍(或离障碍比较远),则不需要按按键。 当然还有出现鸟的情况,我们也可以归为跳的情况。大家可以玩一下。 2.2、监督学习 玩游戏很多时候会使用一个叫强化学习的方式来实现,而本文使用比较简单的监督学习来实现。 本文会使用逻辑回归算法实现,其代码如下: from sklearn.linear_model import LogisticRegression # 逻辑回归模型 from sklearn.model_selection import train_test_split # 数据集拆分 # 1、准备数据 X = [ # 天河区的坐标 [1, 1], [1, 2], [2, 0], [3, 2], [3, 3], # 花都区的坐标 [7, 7], [6, 7], [7, 6], [8, 6], [8, 5] ] y = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1] # 2、拆分数据集 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2) # 3、定义模型 model = LogisticRegression() # 4、填充数据并训练 model.fit(X_train, y_train) # 5、评估模型 score1 = model.score(X_train, y_train) score2 = model.score(X_test, y_test) print(score1, score2) # 6、预测 input = [ [4, 4] ] pred = model.predict(input) print(pred) 关于逻辑回归的讲解可以查看:Python快速构建神经网络 。 我们可以把玩游戏看作一个分类问题,即输入为当前游戏的图像,输出为0、1的一个二分类问题(0表示跳,1表示不跳)。要让AI实现自动玩游戏,我们需要做几件事情。分别如下: 玩游戏,收集一些需要跳的图片和一些不需要条的图片 选择合适的分类算法,训练一个模型 截取当前游戏画面,预测结果,判断是否需要跳跃 如果需要跳跃,则用程序控制键盘,按下跳跃键 下面我们来依次完成上面的事情。 三、收集数据 收集数据我们需要在玩游戏的过程中不停地截图,这里可以用Pillow模块来实现截图。Pillow模块需要单独安装,安装语句如下: pip install pillow 截图的代码如下: import time from PIL import ImageGrab # 截图 time.sleep(3) while True: # 截图 img = ImageGrab.grab() # print(img.size) # 960 540 480 270 img = img.resize((960, 540)) # 保存图片 img.save(f'imgs/{str(time.time())}.jpg') # 修改name time.sleep(0.1) 运行程序后就可以切换到Chrome开始游戏了。进行一段时间后,我们会截取一些图片,大致如下: 这时就轮到人类智能上场了,我们手动的把我们决定需要跳的场景放置到imgs/jump目录下,把觉得不需要跳的场景放到imgs/none目录下。然后就可以进行下一步了,这里截取的图片通常不需要跳的要多很多,所有可以多收集几次。 收集完成后我们就可以把图片读入,并转换成一个1维数组,这部分代码如下: import os import cv2 # 所有图片的全路径 files = [os.path.join(jump_path, jump) for jump in os.listdir(jump_path)] + \ [os.path.join(none_path, none) for none in os.listdir(none_path)] X = [] y = [0] * len(os.listdir(jump_path)) + [1] * len(os.listdir(none_path)) # 遍历jump目录下的图片 for idx, file in enumerate(files): filepath = os.path.join(none_path, file) x = cv2.imread(filepath, 0).reshape(-1) X.append(x) 此时X和y就是我们的特征和目标了。有了X和y就可以开始训练模型了。 四、训练分类模型 训练部分的代码非常简单,我们可以在训练完成后保存模型。代码如下: import os import cv2 import joblib from sklearn.model_selection import train_test_split from sklearn.linear_model import LogisticRegression jump_path = os.path.join('imgs', 'jump') # 需要跳的图片的根目录 none_path = os.path.join('imgs', 'none') # 不需要跳的图片的根目录 # 所有图片的全路径 files = [os.path.join(jump_path, jump) for jump in os.listdir(jump_path)] + \ [os.path.join(none_path, none) for none in os.listdir(none_path)] X = [] y = [0] * len(os.listdir(jump_path)) + [1] * len(os.listdir(none_path)) # 遍历jump目录下的图片 for file in files: x = cv2.imread(file, 0).reshape(-1) X.append(x) # 2、拆分数据集 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2) # 3、定义模型 model = LogisticRegression(max_iter=500) # 4、训练模型 model.fit(X_train, y_train) # 5、评估模型 train_score = model.score(X_train, y_train) test_score = model.score(X_test, y_test) print(train_score, test_score) # 保存模型 joblib.dump(model, 'auto_play.m') 在我电脑上训练的准确率在90%以上,总体效果还是不错的。不过有几个可以改进的地方。这里说几点: 图像只有中间部分会对下一步操作有影响,因此可以选择对训练图片进行一些处理。把上面和下面部分设置为0。如果做了这个处理,那么在实际应用时也要做同样的处理。 这些图片如果移植到其它电脑可能不适用,因为分辨率等原因。所有可以选择使用更复杂的模型,比如CNN网络。 因为手动收集数据比较麻烦,可以选择做一下数据增强。 在这里我们不做这些改进,直接使用最简单的模型。 五、自动玩游戏 自动玩游戏需要借助pynput模块来实现,其安装如下: pip install pynput 我们可以用下面的代码实现按下键盘的空格键: from pynput import keyboard from pynput.keyboard import Key # 创建键盘 kb = keyboard.Controller() # 按下空格键 kb.press(Key.space) 知道了如何控制键盘后,我们就可以使用模型截取预测,如何判断是否要按空格,代码如下: import time import cv2 import joblib import numpy as np from PIL import ImageGrab from pynput import keyboard from pynput.keyboard import Key time.sleep(3) # 0、创建键盘 kb = keyboard.Controller() # 1、加载模型 model = joblib.load('auto_play.m') while True: # 2、准备数据 ImageGrab.grab().resize((960, 540)).save('current.jpg') # 保存当前屏幕截屏 x = cv2.imread('current.jpg', 0).reshape(-1) x = [x] # 3、预测 pred = model.predict(x) print(pred) # 如果需要跳,则按下空格 if pred[0] == 0: kb.press(Key.space) 运行上面的程序后,打开浏览器即可开始游戏。程序的代码和图片文件:https://download.csdn.net/download/ZackSock/86543410 GitHub地址为:https://github.com/IronSpiderMan/AutoPlayGoogleDino ———————————————— 版权声明:本文为CSDN博主「ZackSock」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/ZackSock/article/details/126908649
-
FunctionGraph+OBS+FRS+IVS实现人证核身目录场景设计方案架构图方案设计云服务介绍环境准备开发流程1.客户端开发——上传图片/视频到OBS2.FunctionGraph函数代码开发3.下载OBS对象+Base64编码4.FRS活体检测5.IVS人证核身6.SMN发布结果消息7.客户端开发——SMN之HTTP(S)终端接口开发8.FunctionGraph函数整合实现效果部署测试示例工程一、场景设计客户通过页面上传人物照片、视频到OBS并触发FunctionGraph函数,函数调用FRS人脸识别服务判断是否为真人,如果是真人继续调用IVS人证核身服务校验身份真实性,最后通过SMN将结果消息发布出去。方案架构图![方案架构图]二、方案设计创建FunctionGraph函数,并为该函数配置OBS触发器,上传图片或视频到OBS后触发FunctionGraph函数,函数下载OBS对象并调用FRS进行活体检测,活体检测通过后函数再调用IVS进行人证核身,最后函数通过SMN发布人证核身结果消息。云产品提供服务作用OBS对象操作实现上传/下载 图片/视频FunctionGraph配置OBS触发器实现OBS上传时触发函数FunctionGraph创建Java函数创建Java语言的函数FRS活体检测对图片或视频进行动作/静默活体检测IVS认证核身标准版实现对上传的人脸图片进行身份验证SMN发送HTTP(S)消息实现将结果发送到HTTP(S)终端三、云服务介绍华为云OBS: 对象存储服务(Object Storage Service,OBS)是一个基于对象的海量存储服务,为客户提供海量、安全、高可靠、低成本的数据存储能力,使用时无需考虑容量限制,并且提供多种存储类型供选择,满足客户各类业务场景诉求。产品页面华为云FunctionGraph:函数工作流(FunctionGraph)是一项基于事件驱动的函数托管计算服务。通过函数工作流,只需编写业务函数代码并设置运行的条件,无需配置和管理服务器等基础设施,函数以弹性、免运维、高可靠的方式运行。产品页面华为云FRS:人脸识别服务(Face Recognition Service),能够在图像中快速检测人脸、分析人脸关键点信息、获取人脸属性、实现人脸的精确比对和检索。该服务可应用于身份验证、电子考勤、客流分析等场景。产品页面华为云IVS:人证核身服务(Identity Verification Solution)将用户本人与身份证信息关联起来,应用人脸识别与文字识别等技术,对接权威数据库,支持基于二要素(姓名、身份证)认证或三要素(人脸、姓名、身份证)认证,实现对身份真实性的精准核验。产品页面华为云SMN:消息通知服务(Simple Message Notification)为用户提供快速简便、稳定可靠、简化运维、高可扩展、安全可信的消息通知能力。最终用户可以通过HTTP、HTTPS、邮件、短信、触发函数执行、即时通讯工具等方式接收通知信息。华为云用户也可以在应用之间通过消息通知服务实现应用的功能集成,降低系统的复杂性。产品页面四、环境准备| 开发语言:Java| 开发环境:jdk1.8 maven3.6.3| 华为云环境:注册云账号,并完成实名认证五、开发流程| 分为客户端开发和FunctionGraph的Java函数开发。1、客户端开发--上传图片/视频到OBS华为云OBS服务控制台创建桶用来存储人脸图片或视频 如果涉及视频需要压缩上传,需要配置在线解压策略(具体桶中->数据处理>在线解压) 拿到OBS的maven依赖坐标,使用SDK开发上传OBS对象客户端,这里文件传到固定的文件夹下,并使用固定对象名命名,比如face。帮助文档上传对象方法代码参考public void uploadFile(MultipartFile multipartFile){ String endPoint = "https://终端节点"; String ak = "ak"; String sk = "sk"; //1.创建OBS客户端对象 ObsClient obsClient = new ObsClient(ak, sk, endPoint); //2.获取文件输入流 InputStream in = null; try { in = multipartFile.getInputStream(); //3.上传对象,后面根据该对象名下载对象,保持上传下载一致 PutObjectResult putObjectResult = obsClient.putObject("桶名", "对象名", in); System.out.println(putObjectResult); } catch (IOException e) { throw new RuntimeException(e); }finally { if (in!=null){ try { in.close(); } catch (IOException e) { throw new RuntimeException(e); } } } }2、FunctionGraph函数代码开发(1)FunctionGraph函数时序图(2)Java函数环境搭建创建maven项目,导入FunctionGraph提供的Runtime依赖 操作指导创建FunctionGraph Java函数,并配置OBS触发器。 3、下载OBS对象+Base64编码| 终端节点查询 | ak、sk获取从OBS下载对象并进行Base64编码,后面使用该Base64编码结果进行活体检测。导入OBS的Maven依赖。可以从SDK中心查询依赖坐标下载对象+Base64编码示例代码:public static String download() { //1.相关配置数据 String endPoint = "https://终端节点"; String ak = "ak"; String sk = "sk"; //2.创建ObsClient实例 final ObsClient obsClient = new ObsClient(ak, sk, endPoint); //对象名保持和上传对象名一致 ObsObject obsObject = obsClient.getObject("桶名", "对象名"); //3.读取对象内容 ByteArrayOutputStream bos = null; InputStream input = null; String imageStr = null; try { System.out.println("Object content:"); input = obsObject.getObjectContent(); byte[] b = new byte[1024]; bos = new ByteArrayOutputStream(); int len; while ((len = input.read(b)) != -1) { bos.write(b, 0, len); } //4.Base64编码 byte[] data = new byte[bos.size()]; data = bos.toByteArray(); imageStr = new String(Base64.encodeBase64(data)); System.out.println(imageStr); } catch (Exception e) { e.printStackTrace(); } finally { try { bos.close(); } catch (IOException e) { throw new RuntimeException(e); } try { input.close(); } catch (IOException e) { throw new RuntimeException(e); } } return imageStr; }4、FRS活体检测活体检测,判断图片或视频中的人物是否为真人活体。对Base64编码后的结果进行活体检测,图片使用静默活体检测,视频使用动作活体检测。以图片对应的静默活体检测为例,使用SDK开发。开通活体检测服务控制台 导入FRS的Maven依赖。可以从SDK中心查询依赖坐标静默活体检测示例代码://方法参数imageStr:人像图片Base64编码 public static DetectLiveFaceByBase64Response detectLive(String imageStr) { //1.配置基本信息 ICredential credential = new BasicCredentials().withAk("ak").withSk("sk"); FrsClient client = FrsClient.newBuilder() .withCredential(credential) .withRegion(FrsRegion.valueOf("区域")) // 选择服务所在区域,如华北-北京四: FrsRegion.valueOf("cn-north-4") .build(); //2.通过base64编码进行活体检测 DetectLiveFaceByBase64Request request = new DetectLiveFaceByBase64Request(); LiveDetectFaceBase64Req body = new LiveDetectFaceBase64Req(); body.withImageBase64(imageStr); request.withBody(body); DetectLiveFaceByBase64Response response = null; try { response = client.detectLiveFaceByBase64(request); System.out.println(response.toString()); } catch (Exception e) { e.printStackTrace(); } return response; }5、IVS人证核身这里用到IVS标准版(三要素)对图片中人物进行身份认证,这里用到的图片Base64是FRS活体检测返回的结果中的图片Base64编码。开通人证核身服务控制台 导入IVS的Maven依赖。可以从SDK中心查询依赖坐标人证核身标准版:使用身份证图片Base64、人像图片Base64进行认证,示例代码://方法参数imageStr:人像图片Base64编码 public static String ivsVerify(String imageStr) { //1.配置基本信息 ICredential credential = new BasicCredentials().withAk("ak").withSk("sk"); IvsClient client = IvsClient.newBuilder() .withCredential(credential) .withRegion(IvsRegion.valueOf("区域"))// 选择服务所在区域,如华北-北京四就是 FrsRegion.valueOf("cn-north-4") .build(); DetectStandardByIdCardImageRequest request = new DetectStandardByIdCardImageRequest(); IvsStandardByIdCardImageRequestBody body = new IvsStandardByIdCardImageRequestBody(); List listIvsStandardByIdCardImageRequestBodyDataReqData = new ArrayList<>(); listIvsStandardByIdCardImageRequestBodyDataReqData.add( new ReqDataByIdCardImage() .withIdcardImage1("身份证人像面图片Base64") .withFaceImage(imageStr) ); IvsStandardByIdCardImageRequestBodyData databody = new IvsStandardByIdCardImageRequestBodyData(); databody.withReqData(listIvsStandardByIdCardImageRequestBodyDataReqData); Meta metabody = new Meta(); //唯一标识此次请求的ID,用户自定义,不超过64位 metabody.withUuid(UUID.randomUUID().toString()); body.withData(databody); body.withMeta(metabody); request.withBody(body); String result = null; try { //2.调用服务 DetectStandardByIdCardImageResponse response = client.detectStandardByIdCardImage(request); System.out.println(response.toString()); //result取值:valid-成功;invalid-失败 result = response.getResult().getRespData().get(0).getVerificationResult(); } catch (Exception e) { e.printStackTrace(); } //3.返回人证核身结果 return result; }6、SMN发布结果消息这里用SMN向HTTP(S)终端发送消息,将人证核身结果发布到指定的URL地址。开通服务(创建主题+创建订阅)控制台 导入IVS的Maven依赖。可以从SDK中心查询依赖坐标SMN发布消息示例代码://方法请求参数msgContent:消息内容 public static void sendMsg(String msgContent){ String ak = "ak"; String sk = "sk"; //创建SMN客户端 ICredential credential = new BasicCredentials().withAk(ak).withSk(sk); SmnClient client = SmnClient.newBuilder() .withCredential(credential) .withRegion(区域)//如华北-北京四:SmnRegion.CN_NORTH_4 .build(); //创建请求 PublishMessageRequest publishMessageRequest = new PublishMessageRequest(); //创建请求体 PublishMessageRequestBody body = new PublishMessageRequestBody(); body.withMessage(msgContent); body.withSubject("主题"); publishMessageRequest.withTopicUrn("主题URN,可在控制台获取"); publishMessageRequest.withBody(body); //执行请求 PublishMessageResponse response = client.publishMessage(publishMessageRequest); System.out.println(response); }7、客户端开发--SMN之HTTP(S)终端接口开发HTTP(S)终端对应的URL接口需要根据type参数判断是确定订阅、消息或是取消订阅。接口示例代码:@PostMapping("/smn") //该接口完整地址就是创建时配置的终端地址 public void subscriptionConfirmation2(@RequestBody SubscriptionReq subscriptionReq,HttpServletRequest request,HttpServletResponse response){ System.out.println(subscriptionReq); try{ //1.获取参数,封装到MAP中 Map parameterMap = BeanUtils.describe(subscriptionReq); String signing_cert_url = parameterMap.get("signing_cert_url"); String signature = parameterMap.get("signature"); //2.检验消息签名 boolean flag = SmnUtil.isMessageValid(signing_cert_url, signature, parameterMap); //3.判断是订阅还是发布消息 String type = parameterMap.get("type"); if ("SubscriptionConfirmation".equals(type)){ //4.访问订阅确认页面 String map = restTemplate.getForObject(parameterMap.get("subscribe_url"), String.class); System.out.println(map); }else if ("Notification".equals(type)){ //5.消息,打印消息 System.out.println(parameterMap); }else{ //6.取消订阅 } }catch (Exception e){ e.printStackTrace(); } }接口接收请求参数实体类可参考:@Data //Lombok注解 public class SubscriptionReq { private String type; private String signature; private String subject; private String topic_urn; private String message_id; private String signature_version; private String message; private String subscribe_url; private String signing_cert_url; private String timestamp; }8、FunctionGraph函数整合在触发函数对应的方法中调用上面服务方法,实现认证核身认证流程。即在搭建的FunctionGraph Java函数项目中的TriggerTests.java中的apiTest方法中编写人证核身业务逻辑。TriggerTests代码:import java.util.HashMap; import java.util.Map; import com.huawei.services.runtime.Context; import com.huawei.services.runtime.entity.apig.APIGTriggerEvent; import com.huawei.services.runtime.entity.apig.APIGTriggerResponse; import com.huawei.utils.FrsUtils; import com.huawei.utils.IvsUtils; import com.huawei.utils.ObsUtils; import com.huawei.utils.SmnUtils; import com.huaweicloud.sdk.frs.v1.model.DetectLiveFaceByBase64Response; public class TriggerTests { //functionGraph函数触发方法 public APIGTriggerResponse apigTest(APIGTriggerEvent event, Context context) { //1.下载对象,并得到base64编码后结果 String imageBase64 = ObsUtils.download(); //2.活体检测 DetectLiveFaceByBase64Response response = FrsUtils.detectLive(imageBase64); Boolean alive = response.getResult().getAlive(); //3.人证核身 String msg = null; if (alive) { //活体检测成功 String result = IvsUtils.ivsVerify(response.getResult().getPicture()); //result取值:valid-成功;invalid-失败 msg = "valid".equals(result) ? "人证核身成功!是XXX本人。" : "人证核身失败!"; System.out.println(result+"--"+msg); //4.发送结果信息 SmnUtils.sendMsg(msg); } else { msg = "活体检测失败!"; System.out.println(msg); //发送结果消息 SmnUtils.sendMsg(msg); } Map headers = new HashMap(); headers.put("Content-Type", "application/json"); return new APIGTriggerResponse(200, headers, msg); } }六、实现效果1、部署测试将FunctionGraph Java函数对应项目打成jar包。参照帮助文档FunctionGraph创建Java函数,并配置OBS触发器(对应:开发流程->二、FunctionGraph函数代码开发->1、Java函数环境搭建)。FunctionGraph上传项目jar包到函数。通过开发的客户端上传图片或视频到OBS。在客户端刷新接收SMN消息的HTTP(S)终端接口,查看结果信息;查看FunctionGraph日志。 2、示例工程示例代码附件,待补充……
-
好的学习资源是宝贵的,让人开心的事,现在华为云上有不少的学习资源。质量很不错,而且目前可以免费使用哦。比如华为云实验那么不仅仅是理论上的学习,通过云实验可以进行实践操作。在云环境里已经规划设置好了学习用的虚拟资源,可以说是开箱即用。而且操作指引非常的简单明了。可要好好的珍惜哦。
-
用300行Python代码实现一个人脸识别系统 最近又多了不少朋友关注,先在这里谢谢大家。关注我的朋友大多数都是大学生,而且我简单看了一下,低年级的大学生居多,大多数都是为了完成课程设计,作为一个过来人,还是希望大家平时能多抽出点时间学习一下,这种临时抱佛脚的策略要少用嗷。今天我们来python实现一个人脸识别系统,主要是借助了dlib这个库,相当于我们直接调用现成的库来进行人脸识别,就省去了之前教程中的数据收集和模型训练的步骤了。 B站视频:用300行代码实现人脸识别系统_哔哩哔哩_bilibili CSDN博客:用300行Python代码实现一个人脸识别系统_dejahu的博客-CSDN博客 码云地址:face_dlib_py37_42: 用300行代码开发一个人脸识别系统-42 (gitee.com) 预编译dlib库下载地址:人脸识别系统+windows64位-dlib-19.17.0-cp37-cp37m-win_amd64.zip-深度学习文档类资源-CSDN文库 注:直接安装dlib库可能会有编译错误,可以通过下列方式获取编译好的dlib库 获取方式1: 直接从付费资源下载 人脸识别系统+windows64位-dlib-19.17.0-cp37-cp37m-win_amd64.zip-深度学习文档类资源-CSDN文库 获取方式2: 在B站视频三连并在评论区留下你的邮箱地址 用300行代码实现人脸识别系统_哔哩哔哩_bilibili 获取方式3: 在CSDN博客中三连并在评论区留下你的邮箱地址 用300行Python代码实现一个人脸识别系统_dejahu的博客-CSDN博客 基本原理 人脸识别和目标检测这些还不太一样,比如大家传统的训练一个目标检测模型,你只有对这个目标训练了之后,你的模型才能找到这样的目标,比如你的目标检测模型如果是检测植物的,那显然就不能检测动物。但是人脸识别就不一样,以你的手机为例,你发现你只录入了一次你的人脸信息,不需要训练,他就能准确的识别你,这里识别的原理是通过人脸识别的模型提取你脸部的特征向量,然后将实时检测到的你的人脸同数据库中保存的人脸进行比对,如果相似度超过一定的阈值之后,就认为比对成功。不过我这里说的只是简化版本的人脸识别,现在手机和门禁这些要复杂和安全的多,也不是简单平面上的人脸识别。 总结下来可以分为下面的步骤: 上传人脸到数据库 人脸检测 数据库比对并返回结果 这里我做了一个简答的示意图,可以帮助大家简单理解一下。 代码实现 废话不多说,这里就是我们的代码实现,代码我已经上传到码云,大家直接下载就行,地址就在博客开头。 不会安装python环境的兄弟请看这里:如何在pycharm中配置anaconda的虚拟环境_dejahu的博客-CSDN博客_如何在pycharm中配置anaconda 创建虚拟环境 创建虚拟环境前请大家先下载博客开头的码云源码到本地。 本次我们需要使用到python3.7的虚拟环境,命令如下: conda create -n face python==3.7.3 conda activate face 1 2 安装必要的库 pip install -r requirements.txt 1 愉快地开始你的人脸识别吧! 执行下面的主文件即可 python UI.py 1 或者在pycharm中按照下面的方式直接运行即可 首先将你需要识别的人脸上传到数据库中 通过第二个视频检测功能识别实时的人脸 详细的代码如下: # -*- coding: utf-8 -*- """ ------------------------------------------------- Project Name: yolov5-jungong File Name: window.py.py Author: chenming Create Date: 2021/11/8 Description:图形化界面,可以检测摄像头、视频和图片文件 ------------------------------------------------- """ # 应该在界面启动的时候就将模型加载出来,设置tmp的目录来放中间的处理结果 import shutil import PyQt5.QtCore from PyQt5.QtGui import * from PyQt5.QtCore import * from PyQt5.QtWidgets import * import threading import argparse import os import sys from pathlib import Path import cv2 import torch import torch.backends.cudnn as cudnn import os.path as osp FILE = Path(__file__).resolve() ROOT = FILE.parents[0] # YOLOv5 root directory if str(ROOT) not in sys.path: sys.path.append(str(ROOT)) # add ROOT to PATH ROOT = Path(os.path.relpath(ROOT, Path.cwd())) # relative from models.common import DetectMultiBackend from utils.datasets import IMG_FORMATS, VID_FORMATS, LoadImages, LoadStreams from utils.general import (LOGGER, check_file, check_img_size, check_imshow, check_requirements, colorstr, increment_path, non_max_suppression, print_args, scale_coords, strip_optimizer, xyxy2xywh) from utils.plots import Annotator, colors, save_one_box from utils.torch_utils import select_device, time_sync # 添加一个关于界面 # 窗口主类 class MainWindow(QTabWidget): # 基本配置不动,然后只动第三个界面 def __init__(self): # 初始化界面 super().__init__() self.setWindowTitle('Target detection system') self.resize(1200, 800) self.setWindowIcon(QIcon("images/UI/lufei.png")) # 图片读取进程 self.output_size = 480 self.img2predict = "" self.device = 'cpu' # # 初始化视频读取线程 self.vid_source = '0' # 初始设置为摄像头 self.stopEvent = threading.Event() self.webcam = True self.stopEvent.clear() self.model = self.model_load(weights="runs/train/exp_yolov5s/weights/best.pt", device="cpu") # todo 指明模型加载的位置的设备 self.initUI() self.reset_vid() ''' ***模型初始化*** ''' @torch.no_grad() def model_load(self, weights="", # model.pt path(s) device='', # cuda device, i.e. 0 or 0,1,2,3 or cpu half=False, # use FP16 half-precision inference dnn=False, # use OpenCV DNN for ONNX inference ): device = select_device(device) half &= device.type != 'cpu' # half precision only supported on CUDA device = select_device(device) model = DetectMultiBackend(weights, device=device, dnn=dnn) stride, names, pt, jit, onnx = model.stride, model.names, model.pt, model.jit, model.onnx # Half half &= pt and device.type != 'cpu' # half precision only supported by PyTorch on CUDA if pt: model.model.half() if half else model.model.float() print("模型加载完成!") return model ''' ***界面初始化*** ''' def initUI(self): # 图片检测子界面 font_title = QFont('楷体', 16) font_main = QFont('楷体', 14) # 图片识别界面, 两个按钮,上传图片和显示结果 img_detection_widget = QWidget() img_detection_layout = QVBoxLayout() img_detection_title = QLabel("图片识别功能") img_detection_title.setFont(font_title) mid_img_widget = QWidget() mid_img_layout = QHBoxLayout() self.left_img = QLabel() self.right_img = QLabel() self.left_img.setPixmap(QPixmap("images/UI/up.jpeg")) self.right_img.setPixmap(QPixmap("images/UI/right.jpeg")) self.left_img.setAlignment(Qt.AlignCenter) self.right_img.setAlignment(Qt.AlignCenter) mid_img_layout.addWidget(self.left_img) mid_img_layout.addStretch(0) mid_img_layout.addWidget(self.right_img) mid_img_widget.setLayout(mid_img_layout) up_img_button = QPushButton("上传图片") det_img_button = QPushButton("开始检测") up_img_button.clicked.connect(self.upload_img) det_img_button.clicked.connect(self.detect_img) up_img_button.setFont(font_main) det_img_button.setFont(font_main) up_img_button.setStyleSheet("QPushButton{color:white}" "QPushButton:hover{background-color: rgb(2,110,180);}" "QPushButton{background-color:rgb(48,124,208)}" "QPushButton{border:2px}" "QPushButton{border-radius:5px}" "QPushButton{padding:5px 5px}" "QPushButton{margin:5px 5px}") det_img_button.setStyleSheet("QPushButton{color:white}" "QPushButton:hover{background-color: rgb(2,110,180);}" "QPushButton{background-color:rgb(48,124,208)}" "QPushButton{border:2px}" "QPushButton{border-radius:5px}" "QPushButton{padding:5px 5px}" "QPushButton{margin:5px 5px}") img_detection_layout.addWidget(img_detection_title, alignment=Qt.AlignCenter) img_detection_layout.addWidget(mid_img_widget, alignment=Qt.AlignCenter) img_detection_layout.addWidget(up_img_button) img_detection_layout.addWidget(det_img_button) img_detection_widget.setLayout(img_detection_layout) # todo 视频识别界面 # 视频识别界面的逻辑比较简单,基本就从上到下的逻辑 vid_detection_widget = QWidget() vid_detection_layout = QVBoxLayout() vid_title = QLabel("视频检测功能") vid_title.setFont(font_title) self.vid_img = QLabel() self.vid_img.setPixmap(QPixmap("images/UI/up.jpeg")) vid_title.setAlignment(Qt.AlignCenter) self.vid_img.setAlignment(Qt.AlignCenter) self.webcam_detection_btn = QPushButton("摄像头实时监测") self.mp4_detection_btn = QPushButton("视频文件检测") self.vid_stop_btn = QPushButton("停止检测") self.webcam_detection_btn.setFont(font_main) self.mp4_detection_btn.setFont(font_main) self.vid_stop_btn.setFont(font_main) self.webcam_detection_btn.setStyleSheet("QPushButton{color:white}" "QPushButton:hover{background-color: rgb(2,110,180);}" "QPushButton{background-color:rgb(48,124,208)}" "QPushButton{border:2px}" "QPushButton{border-radius:5px}" "QPushButton{padding:5px 5px}" "QPushButton{margin:5px 5px}") self.mp4_detection_btn.setStyleSheet("QPushButton{color:white}" "QPushButton:hover{background-color: rgb(2,110,180);}" "QPushButton{background-color:rgb(48,124,208)}" "QPushButton{border:2px}" "QPushButton{border-radius:5px}" "QPushButton{padding:5px 5px}" "QPushButton{margin:5px 5px}") self.vid_stop_btn.setStyleSheet("QPushButton{color:white}" "QPushButton:hover{background-color: rgb(2,110,180);}" "QPushButton{background-color:rgb(48,124,208)}" "QPushButton{border:2px}" "QPushButton{border-radius:5px}" "QPushButton{padding:5px 5px}" "QPushButton{margin:5px 5px}") self.webcam_detection_btn.clicked.connect(self.open_cam) self.mp4_detection_btn.clicked.connect(self.open_mp4) self.vid_stop_btn.clicked.connect(self.close_vid) # 添加组件到布局上 vid_detection_layout.addWidget(vid_title) vid_detection_layout.addWidget(self.vid_img) vid_detection_layout.addWidget(self.webcam_detection_btn) vid_detection_layout.addWidget(self.mp4_detection_btn) vid_detection_layout.addWidget(self.vid_stop_btn) vid_detection_widget.setLayout(vid_detection_layout) # todo 关于界面 about_widget = QWidget() about_layout = QVBoxLayout() about_title = QLabel('欢迎使用目标检测系统\n\n 提供付费指导:有需要的好兄弟加下面的QQ即可') # todo 修改欢迎词语 about_title.setFont(QFont('楷体', 18)) about_title.setAlignment(Qt.AlignCenter) about_img = QLabel() about_img.setPixmap(QPixmap('images/UI/qq.png')) about_img.setAlignment(Qt.AlignCenter) # label4.setText("如何调整学习率") label_super = QLabel() # todo 更换作者信息 label_super.setText("或者你可以在这里找到我-->肆十二") label_super.setFont(QFont('楷体', 16)) label_super.setOpenExternalLinks(True) # label_super.setOpenExternalLinks(True) label_super.setAlignment(Qt.AlignRight) about_layout.addWidget(about_title) about_layout.addStretch() about_layout.addWidget(about_img) about_layout.addStretch() about_layout.addWidget(label_super) about_widget.setLayout(about_layout) self.left_img.setAlignment(Qt.AlignCenter) self.addTab(img_detection_widget, '图片检测') self.addTab(vid_detection_widget, '视频检测') self.addTab(about_widget, '联系我') self.setTabIcon(0, QIcon('images/UI/lufei.png')) self.setTabIcon(1, QIcon('images/UI/lufei.png')) self.setTabIcon(2, QIcon('images/UI/lufei.png')) ''' ***上传图片*** ''' def upload_img(self): # 选择录像文件进行读取 fileName, fileType = QFileDialog.getOpenFileName(self, 'Choose file', '', '*.jpg *.png *.tif *.jpeg') if fileName: suffix = fileName.split(".")[-1] save_path = osp.join("images/tmp", "tmp_upload." + suffix) shutil.copy(fileName, save_path) # 应该调整一下图片的大小,然后统一防在一起 im0 = cv2.imread(save_path) resize_scale = self.output_size / im0.shape[0] im0 = cv2.resize(im0, (0, 0), fx=resize_scale, fy=resize_scale) cv2.imwrite("images/tmp/upload_show_result.jpg", im0) # self.right_img.setPixmap(QPixmap("images/tmp/single_result.jpg")) self.img2predict = fileName self.left_img.setPixmap(QPixmap("images/tmp/upload_show_result.jpg")) # todo 上传图片之后右侧的图片重置, self.right_img.setPixmap(QPixmap("images/UI/right.jpeg")) ''' ***检测图片*** ''' def detect_img(self): model = self.model output_size = self.output_size source = self.img2predict # file/dir/URL/glob, 0 for webcam imgsz = 640 # inference size (pixels) conf_thres = 0.25 # confidence threshold iou_thres = 0.45 # NMS IOU threshold max_det = 1000 # maximum detections per image device = self.device # cuda device, i.e. 0 or 0,1,2,3 or cpu view_img = False # show results save_txt = False # save results to *.txt save_conf = False # save confidences in --save-txt labels save_crop = False # save cropped prediction boxes nosave = False # do not save images/videos classes = None # filter by class: --class 0, or --class 0 2 3 agnostic_nms = False # class-agnostic NMS augment = False # ugmented inference visualize = False # visualize features line_thickness = 3 # bounding box thickness (pixels) hide_labels = False # hide labels hide_conf = False # hide confidences half = False # use FP16 half-precision inference dnn = False # use OpenCV DNN for ONNX inference print(source) if source == "": QMessageBox.warning(self, "请上传", "请先上传图片再进行检测") else: source = str(source) device = select_device(self.device) webcam = False stride, names, pt, jit, onnx = model.stride, model.names, model.pt, model.jit, model.onnx imgsz = check_img_size(imgsz, s=stride) # check image size save_img = not nosave and not source.endswith('.txt') # save inference images # Dataloader if webcam: view_img = check_imshow() cudnn.benchmark = True # set True to speed up constant image size inference dataset = LoadStreams(source, img_size=imgsz, stride=stride, auto=pt and not jit) bs = len(dataset) # batch_size else: dataset = LoadImages(source, img_size=imgsz, stride=stride, auto=pt and not jit) bs = 1 # batch_size vid_path, vid_writer = [None] * bs, [None] * bs # Run inference if pt and device.type != 'cpu': model(torch.zeros(1, 3, *imgsz).to(device).type_as(next(model.model.parameters()))) # warmup dt, seen = [0.0, 0.0, 0.0], 0 for path, im, im0s, vid_cap, s in dataset: t1 = time_sync() im = torch.from_numpy(im).to(device) im = im.half() if half else im.float() # uint8 to fp16/32 im /= 255 # 0 - 255 to 0.0 - 1.0 if len(im.shape) == 3: im = im[None] # expand for batch dim t2 = time_sync() dt[0] += t2 - t1 # Inference # visualize = increment_path(save_dir / Path(path).stem, mkdir=True) if visualize else False pred = model(im, augment=augment, visualize=visualize) t3 = time_sync() dt[1] += t3 - t2 # NMS pred = non_max_suppression(pred, conf_thres, iou_thres, classes, agnostic_nms, max_det=max_det) dt[2] += time_sync() - t3 # Second-stage classifier (optional) # pred = utils.general.apply_classifier(pred, classifier_model, im, im0s) # Process predictions for i, det in enumerate(pred): # per image seen += 1 if webcam: # batch_size >= 1 p, im0, frame = path[i], im0s[i].copy(), dataset.count s += f'{i}: ' else: p, im0, frame = path, im0s.copy(), getattr(dataset, 'frame', 0) p = Path(p) # to Path s += '%gx%g ' % im.shape[2:] # print string gn = torch.tensor(im0.shape)[[1, 0, 1, 0]] # normalization gain whwh imc = im0.copy() if save_crop else im0 # for save_crop annotator = Annotator(im0, line_width=line_thickness, example=str(names)) if len(det): # Rescale boxes from img_size to im0 size det[:, :4] = scale_coords(im.shape[2:], det[:, :4], im0.shape).round() # Print results for c in det[:, -1].unique(): n = (det[:, -1] == c).sum() # detections per class s += f"{n} {names[int(c)]}{'s' * (n > 1)}, " # add to string # Write results for *xyxy, conf, cls in reversed(det): if save_txt: # Write to file xywh = (xyxy2xywh(torch.tensor(xyxy).view(1, 4)) / gn).view( -1).tolist() # normalized xywh line = (cls, *xywh, conf) if save_conf else (cls, *xywh) # label format # with open(txt_path + '.txt', 'a') as f: # f.write(('%g ' * len(line)).rstrip() % line + '\n') if save_img or save_crop or view_img: # Add bbox to image c = int(cls) # integer class label = None if hide_labels else (names[c] if hide_conf else f'{names[c]} {conf:.2f}') annotator.box_label(xyxy, label, color=colors(c, True)) # if save_crop: # save_one_box(xyxy, imc, file=save_dir / 'crops' / names[c] / f'{p.stem}.jpg', # BGR=True) # Print time (inference-only) LOGGER.info(f'{s}Done. ({t3 - t2:.3f}s)') # Stream results im0 = annotator.result() # if view_img: # cv2.imshow(str(p), im0) # cv2.waitKey(1) # 1 millisecond # Save results (image with detections) resize_scale = output_size / im0.shape[0] im0 = cv2.resize(im0, (0, 0), fx=resize_scale, fy=resize_scale) cv2.imwrite("images/tmp/single_result.jpg", im0) # 目前的情况来看,应该只是ubuntu下会出问题,但是在windows下是完整的,所以继续 self.right_img.setPixmap(QPixmap("images/tmp/single_result.jpg")) # 视频检测,逻辑基本一致,有两个功能,分别是检测摄像头的功能和检测视频文件的功能,先做检测摄像头的功能。 ''' ### 界面关闭事件 ### ''' def closeEvent(self, event): reply = QMessageBox.question(self, 'quit', "Are you sure?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: self.close() event.accept() else: event.ignore() ''' ### 视频关闭事件 ### ''' def open_cam(self): self.webcam_detection_btn.setEnabled(False) self.mp4_detection_btn.setEnabled(False) self.vid_stop_btn.setEnabled(True) self.vid_source = '0' self.webcam = True th = threading.Thread(target=self.detect_vid) th.start() ''' ### 开启视频文件检测事件 ### ''' def open_mp4(self): fileName, fileType = QFileDialog.getOpenFileName(self, 'Choose file', '', '*.mp4 *.avi') if fileName: self.webcam_detection_btn.setEnabled(False) self.mp4_detection_btn.setEnabled(False) # self.vid_stop_btn.setEnabled(True) self.vid_source = fileName self.webcam = False th = threading.Thread(target=self.detect_vid) th.start() ''' ### 视频开启事件 ### ''' # 视频和摄像头的主函数是一样的,不过是传入的source不同罢了 def detect_vid(self): # pass model = self.model output_size = self.output_size # source = self.img2predict # file/dir/URL/glob, 0 for webcam imgsz = 640 # inference size (pixels) conf_thres = 0.25 # confidence threshold iou_thres = 0.45 # NMS IOU threshold max_det = 1000 # maximum detections per image # device = self.device # cuda device, i.e. 0 or 0,1,2,3 or cpu view_img = False # show results save_txt = False # save results to *.txt save_conf = False # save confidences in --save-txt labels save_crop = False # save cropped prediction boxes nosave = False # do not save images/videos classes = None # filter by class: --class 0, or --class 0 2 3 agnostic_nms = False # class-agnostic NMS augment = False # ugmented inference visualize = False # visualize features line_thickness = 3 # bounding box thickness (pixels) hide_labels = False # hide labels hide_conf = False # hide confidences half = False # use FP16 half-precision inference dnn = False # use OpenCV DNN for ONNX inference source = str(self.vid_source) webcam = self.webcam device = select_device(self.device) stride, names, pt, jit, onnx = model.stride, model.names, model.pt, model.jit, model.onnx imgsz = check_img_size(imgsz, s=stride) # check image size save_img = not nosave and not source.endswith('.txt') # save inference images # Dataloader if webcam: view_img = check_imshow() cudnn.benchmark = True # set True to speed up constant image size inference dataset = LoadStreams(source, img_size=imgsz, stride=stride, auto=pt and not jit) bs = len(dataset) # batch_size else: dataset = LoadImages(source, img_size=imgsz, stride=stride, auto=pt and not jit) bs = 1 # batch_size vid_path, vid_writer = [None] * bs, [None] * bs # Run inference if pt and device.type != 'cpu': model(torch.zeros(1, 3, *imgsz).to(device).type_as(next(model.model.parameters()))) # warmup dt, seen = [0.0, 0.0, 0.0], 0 for path, im, im0s, vid_cap, s in dataset: t1 = time_sync() im = torch.from_numpy(im).to(device) im = im.half() if half else im.float() # uint8 to fp16/32 im /= 255 # 0 - 255 to 0.0 - 1.0 if len(im.shape) == 3: im = im[None] # expand for batch dim t2 = time_sync() dt[0] += t2 - t1 # Inference # visualize = increment_path(save_dir / Path(path).stem, mkdir=True) if visualize else False pred = model(im, augment=augment, visualize=visualize) t3 = time_sync() dt[1] += t3 - t2 # NMS pred = non_max_suppression(pred, conf_thres, iou_thres, classes, agnostic_nms, max_det=max_det) dt[2] += time_sync() - t3 # Second-stage classifier (optional) # pred = utils.general.apply_classifier(pred, classifier_model, im, im0s) # Process predictions for i, det in enumerate(pred): # per image seen += 1 if webcam: # batch_size >= 1 p, im0, frame = path[i], im0s[i].copy(), dataset.count s += f'{i}: ' else: p, im0, frame = path, im0s.copy(), getattr(dataset, 'frame', 0) p = Path(p) # to Path # save_path = str(save_dir / p.name) # im.jpg # txt_path = str(save_dir / 'labels' / p.stem) + ( # '' if dataset.mode == 'image' else f'_{frame}') # im.txt s += '%gx%g ' % im.shape[2:] # print string gn = torch.tensor(im0.shape)[[1, 0, 1, 0]] # normalization gain whwh imc = im0.copy() if save_crop else im0 # for save_crop annotator = Annotator(im0, line_width=line_thickness, example=str(names)) if len(det): # Rescale boxes from img_size to im0 size det[:, :4] = scale_coords(im.shape[2:], det[:, :4], im0.shape).round() # Print results for c in det[:, -1].unique(): n = (det[:, -1] == c).sum() # detections per class s += f"{n} {names[int(c)]}{'s' * (n > 1)}, " # add to string # Write results for *xyxy, conf, cls in reversed(det): if save_txt: # Write to file xywh = (xyxy2xywh(torch.tensor(xyxy).view(1, 4)) / gn).view( -1).tolist() # normalized xywh line = (cls, *xywh, conf) if save_conf else (cls, *xywh) # label format # with open(txt_path + '.txt', 'a') as f: # f.write(('%g ' * len(line)).rstrip() % line + '\n') if save_img or save_crop or view_img: # Add bbox to image c = int(cls) # integer class label = None if hide_labels else (names[c] if hide_conf else f'{names[c]} {conf:.2f}') annotator.box_label(xyxy, label, color=colors(c, True)) # if save_crop: # save_one_box(xyxy, imc, file=save_dir / 'crops' / names[c] / f'{p.stem}.jpg', # BGR=True) # Print time (inference-only) LOGGER.info(f'{s}Done. ({t3 - t2:.3f}s)') # Stream results # Save results (image with detections) im0 = annotator.result() frame = im0 resize_scale = output_size / frame.shape[0] frame_resized = cv2.resize(frame, (0, 0), fx=resize_scale, fy=resize_scale) cv2.imwrite("images/tmp/single_result_vid.jpg", frame_resized) self.vid_img.setPixmap(QPixmap("images/tmp/single_result_vid.jpg")) # self.vid_img # if view_img: # cv2.imshow(str(p), im0) # self.vid_img.setPixmap(QPixmap("images/tmp/single_result_vid.jpg")) # cv2.waitKey(1) # 1 millisecond if cv2.waitKey(25) & self.stopEvent.is_set() == True: self.stopEvent.clear() self.webcam_detection_btn.setEnabled(True) self.mp4_detection_btn.setEnabled(True) self.reset_vid() break # self.reset_vid() ''' ### 界面重置事件 ### ''' def reset_vid(self): self.webcam_detection_btn.setEnabled(True) self.mp4_detection_btn.setEnabled(True) self.vid_img.setPixmap(QPixmap("images/UI/up.jpeg")) self.vid_source = '0' self.webcam = True ''' ### 视频重置事件 ### ''' def close_vid(self): self.stopEvent.set() self.reset_vid() if __name__ == "__main__": app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() sys.exit(app.exec_()) ———————————————— 版权声明:本文为CSDN博主「肆十二」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/ECHOSON/article/details/122404926
-
一:Faster R-CNN的改进 想要更好地了解Faster R-CNN,需先了解传统R-CNN和Fast R-CNN原理,可参考本人呕心撰写的两篇博文 R-CNN史上最全讲解 和 Fast R-CNN讲解。 回到正题,经过R-CNN和Fast RCNN的积淀,Ross B. Girshick在2016年提出了新的Faster RCNN。从网络命名上看就很直白,那么相较于Faster R-CNN到底Faster在哪儿里呢?答案就是:region proposal的提取方式的改变。 Fast R-CNN虽然提出了ROI Pooling的特征提取方式,很好地解决了传统R-CNN中将Region Proposal区域分别输入CNN网络中的弊端。但是!!!始终都是用的传统Selective Search搜索方式确定Region Proposal,训练和测试时消耗了大量时间在RP搜索上。而Faster R-CNN突破性地使用了RPN网络直接提取出RP,并将其融入进整体网络中,使得综合性能有较大提高,在检测速度方面尤为明显。 二:网络架构 上图展示了python版本中的VGG16模型中的faster_rcnn_test.pt的网络结构,可以清晰的看到该网络架构分为以下几个模块: Conv layers 该Backbone层主要用来提取输入图像中的特征,生成Feature Map以供后两个模块使用。 Region Proposal Networks(RPN) RPN模块用来训练提取出原图中的Region Proposal区域,是整个网络模型中最重要的一个模块。 Semi-Fast R-CNN Semi-Fast R-CNN是我自创的命名,因为和Fast R-CNN的head层几乎一模一样,更多叫法是RoiHead层。当通过RPN模块确定了RP后,就可以训练Fast R-CNN网络了,完成对RP区域的分类与bbox框的微调。 综上述可见,细心的人会发现,Conv layers+Semi-Fast R-CNN不就是Fast R-CNN嘛!所以,Faster R-CNN网络实际上就是RPN + Fast R-CNN,也就是two-stage,训练时也是对两个模块分开训练,测试时先由RPN生成RP,再将带有RP的Feature Map输入进Fast R-CNN中完成分类和预测框回归任务。下面,我将依次对三个模块进行详细讲解。 三:Conv layers模块 Conv layers包含了conv,pooling,relu三种层。以python版本中的VGG16模型中faster_rcnn_test.pt的网络结构为例。Conv layers部分共有13个conv层,13个relu层,4个pooling层。不太熟悉VGG16的小伙伴们要注意一下两个细节: 所有的conv层都是:kernel_size=3,pad=1,stride=1 所有的pooling层都是:kernel_size=2,pad=0,stride=2 经过Conv layers模块后,一个MxN(800×600)大小的输入图像就变为了(M/16)x(N/16)的Feature Map!这样Conv layers生成的feature map中都可以和原图对应起来。 四:Region Proposal Networks(RPN)模块 终于迎来了最重要的RPN模块,大家提起精神,和我一起分析!RPN说到底就是两个功能模块,第一个功能模块是利用二分类给每个anchor打出前景得分,并利用回归计算出每个anchor与其对应的GT间的四个微调参数。第二个功能模块则是根据第一个功能模块输出的得分和四个微调参数,得出ROI并选出合适的RP。 其实RPN只有第一个功能模块需要训练,第二个模块都是基本的选择运算,没有参数需要训练,只是为RoiHead选出训练和测试要用到的region proposal。下面我从这两个功能模块依次讲解: 【Module 1】 在第一模块中,将讲解如何在原图中生成anchors并进行标注,又是如何利用标注好的anchors训练RPN对每个anchor进行前景与背景的二分类与anchor位置的回归微调。下面分步骤进行讲解: step1: generate_anchor_base 首先,我们需要使用generate_anchor_base函数生成anchors。代码的主要实现思想:首先以特征图feature map的左上角点为基准产生9个anchor,三种尺度,每个尺度再对应三种比例。接着,将特征图左上角的9个anchors乘上原图的缩放比例base_size,也就是经过4个池化层后的16倍。锚点由特征图中的(0.5,0.5)变为了原图上的(8,8),原图上的9个anchors的w和h也变为16倍。然后以原图左上角的anchors为基准,每隔base_size个像素,画9个anchors。画完之后,原图上大概有20000个anchors。 具体实现方法,见如下代码并附手写图: def generate_anchor_base(base_size=16, ratios=[0.5, 1, 2], # anchor_scales=[8, 16, 32]): #对特征图features以基准长度为16、选择合适的ratios和scales取基准锚点anchor_base。(选择长度为16的原因是图片大小为600*800左右,基准长度16对应的原图区域是256*256,考虑放缩后的大小有128*128,512*512比较合适) #根据基准点生成9个基本的anchor的功能,ratios=[0.5,1,2],anchor_scales=[8,16,32]是长宽比和缩放比例,anchor_scales也就是在base_size的基础上再增加的量,本代码中对应着三种面积的大小(16*8)^2 ,(16*16)^2 (16*32)^2 也就是128,256,512的平方大小 py = base_size / 2. px = base_size / 2. anchor_base = np.zeros((len(ratios) * len(anchor_scales), 4), dtype=np.float32) #(9,4),注意:这里只是以特征图的左上角点为基准产生的9个anchor, for i in six.moves.range(len(ratios)): #six.moves 是用来处理那些在python2 和 3里面函数的位置有变化的,直接用six.moves就可以屏蔽掉这些变化 for j in six.moves.range(len(anchor_scales)): h = base_size * anchor_scales[j] * np.sqrt(ratios[i]) w = base_size * anchor_scales[j] * np.sqrt(1. / ratios[i]) #生成9种不同比例的h和w ''' 这9个anchor形状应为: 90.50967 *181.01933 = 128^2 181.01933 * 362.03867 = 256^2 362.03867 * 724.07733 = 512^2 128.0 * 128.0 = 128^2 256.0 * 256.0 = 256^2 512.0 * 512.0 = 512^2 181.01933 * 90.50967 = 128^2 362.03867 * 181.01933 = 256^2 724.07733 * 362.03867 = 512^2 该函数返回值为anchor_base,形状9*4,是9个anchor的左上右下坐标: -37.2548 -82.5097 53.2548 98.5097 -82.5097 -173.019 98.5097 189.019 -173.019 -354.039 189.019 370.039 -56 -56 72 72 -120 -120 136 136 -248 -248 264 264 -82.5097 -37.2548 98.5097 53.2548 -173.019 -82.5097 189.019 98.5097 -354.039 -173.019 370.039 189.019 ''' index = i * len(anchor_scales) + j anchor_base[index, 0] = py - h / 2. anchor_base[index, 1] = px - w / 2. anchor_base[index, 2] = py + h / 2. anchor_base[index, 3] = px + w / 2. #计算出anchor_base画的9个框的左下角和右上角的4个anchor坐标值 return anchor_base 由于后面讲解中有一些转换函数的运用,在这里先贴上,大家可以根据注释先行理解: def loc2bbox(src_bbox, loc): #已知源bbox 和位置偏差dx,dy,dh,dw,求目标框G if src_bbox.shape[0] == 0: return xp.zeros((0, 4), dtype=loc.dtype) #src_bbox:(R,4),R为bbox个数,4为左下角和右上角四个坐标(这里有误,按照标准坐标系中y轴向下,应该为左上和右下角坐标) src_bbox = src_bbox.astype(src_bbox.dtype, copy=False) src_height = src_bbox[:, 2] - src_bbox[:, 0] #ymax-ymin src_width = src_bbox[:, 3] - src_bbox[:, 1] #xmax-xmin src_ctr_y = src_bbox[:, 0] + 0.5 * src_height y0+0.5h src_ctr_x = src_bbox[:, 1] + 0.5 * src_width #x0+0.5w,计算出中心点坐标 #src_height为Ph,src_width为Pw,src_ctr_y为Py,src_ctr_x为Px dy = loc[:, 0::4] #python [start:stop:step] dx = loc[:, 1::4] dh = loc[:, 2::4] dw = loc[:, 3::4] RCNN中提出的边框回归:寻找原始proposal与近似目标框G之间的映射关系,公式在上面 ctr_y = dy * src_height[:, xp.newaxis] + src_ctr_y[:, xp.newaxis] #ctr_y为Gy ctr_x = dx * src_width[:, xp.newaxis] + src_ctr_x[:, xp.newaxis] # ctr_x为Gx h = xp.exp(dh) * src_height[:, xp.newaxis] #h为Gh w = xp.exp(dw) * src_width[:, xp.newaxis] #w为Gw #上面四行得到了回归后的目标框(Gx,Gy,Gh,Gw) dst_bbox = xp.zeros(loc.shape, dtype=loc.dtype) #loc.shape:(R,4),同src_bbox dst_bbox[:, 0::4] = ctr_y - 0.5 * h dst_bbox[:, 1::4] = ctr_x - 0.5 * w dst_bbox[:, 2::4] = ctr_y + 0.5 * h dst_bbox[:, 3::4] = ctr_x + 0.5 * w #由中心点转换为左上角和右下角坐标 return dst_bbox def bbox2loc(src_bbox, dst_bbox): #已知源框和目标框求出其位置偏差 height = src_bbox[:, 2] - src_bbox[:, 0] width = src_bbox[:, 3] - src_bbox[:, 1] ctr_y = src_bbox[:, 0] + 0.5 * height ctr_x = src_bbox[:, 1] + 0.5 * width #计算出源框中心点坐标 base_height = dst_bbox[:, 2] - dst_bbox[:, 0] base_width = dst_bbox[:, 3] - dst_bbox[:, 1] base_ctr_y = dst_bbox[:, 0] + 0.5 * base_height base_ctr_x = dst_bbox[:, 1] + 0.5 * base_width ##计算出目标框中心点坐标 eps = xp.finfo(height.dtype).eps #求出最小的正数 height = xp.maximum(height, eps) width = xp.maximum(width, eps) #将height,width与其比较保证全部是非负 dy = (base_ctr_y - ctr_y) / height dx = (base_ctr_x - ctr_x) / width dh = xp.log(base_height / height) dw = xp.log(base_width / width) #根据上面的公式二计算dx,dy,dh,dw loc = xp.vstack((dy, dx, dh, dw)).transpose() #np.vstack按照行的顺序把数组给堆叠起来 return loc def bbox_iou(bbox_a, bbox_b): #求两个bbox的相交的交并比 if bbox_a.shape[1] != 4 or bbox_b.shape[1] != 4: raise IndexError #确保bbox第二维为bbox的四个坐标(ymin,xmin,ymax,xmax) tl = xp.maximum(bbox_a[:, None, :2], bbox_b[:, :2]) #tl为交叉部分框左上角坐标最大值,为了利用numpy的广播性质,bbox_a[:, None, :2]的shape是(N,1,2),bbox_b[:, :2]shape是(K,2),由numpy的广播性质,两个数组shape都变成(N,K,2),也就是对a里每个bbox都分别和b里的每个bbox求左上角点坐标最大值 br = xp.minimum(bbox_a[:, None, 2:], bbox_b[:, 2:]) #br为交叉部分框右下角坐标最小值 area_i = xp.prod(br - tl, axis=2) * (tl < br).all(axis=2) #所有坐标轴上tl area_a = xp.prod(bbox_a[:, 2:] - bbox_a[:, :2], axis=1) #计算bboxa的面积 area_b = xp.prod(bbox_b[:, 2:] - bbox_b[:, :2], axis=1) #计算bboxb的面积 return area_i / (area_a[:, None] + area_b - area_i) #计算IOU step2: AnchorTargetCreator 在原图中生成了近乎20000个anchors后,使用AnchorTargetCreator函数对它们进行标注以用于训练。代码的主要实现思想:针对于label的标注,首先剔除掉超过原图边界的anchors,剩下将近15000个。接着,计算每一个anchor与哪个bbox的iou最大以及这个iou值,IOU>0.7的anchor为pos_anchor,IOU<0.3的anchor为neg_anchor。同时,还需计算每个bbox与哪个anchor的iou最大(其实就是矩阵中行最大和列最大的区别),每个bbox对应的最大IOU的anchors也直接设为pos_anchor。但是最后还需要在pos和neg中各随机选128个,也就是128个正样本和128个负样本,将128个正样本的label设置为1,将128个负样本的label设置为0,剩下的(20000-256)个anchors的labels都设为0。针对4个回归框的参数标注,首先对超框的都设为(0,0,0,0),框内的近乎15000个anchors的4个参数就是它们与最大IOU对应的bbox的实际偏移量。具体代码见下: # 下面是AnchorTargetCreator()代码,作用是生成训练要用的anchor(与对应框iou值最大或者最小的各128个框的坐标和256个label(0或者1)) class AnchorTargetCreator(object): # 利用每张图中bbox的真实标签来为所有任务分配ground truth! # 为Faster-RCNN专有的RPN网络提供自我训练的样本,RPN网络正是利用AnchorTargetCreator产生的样本作为数据进行网络的训练和学习的,这样产生的预测anchor的类别和位置才更加精确,anchor变成真正的ROIS需要进行位置修正,而AnchorTargetCreator产生的带标签的样本就是给RPN网络进行训练学习用哒 def __call__(self, bbox, anchor, img_size): # anchor:(S,4),S为anchor数 img_H, img_W = img_size n_anchor = len(anchor) # 一般对应20000个左右anchor inside_index = _get_inside_index(anchor, img_H, img_W) # 将那些超出图片范围的anchor全部去掉,只保留位于图片内部的序号 anchor = anchor[inside_index] # 保留位于图片内部的anchor argmax_ious, label = self._create_label(inside_index, anchor, bbox) # 筛选出符合条件的正例128个负例128并给它们附上相应的label loc = bbox2loc(anchor, bbox[argmax_ious]) # 计算每一个anchor与对应bbox求得iou最大的bbox计算偏移量(注意这里是位于图片内部的每一个) label = _unmap(label, n_anchor, inside_index, fill=-1) # 将位于图片内部的框的label对应到所有生成的20000个框中(label原本为所有在图片中的框的) loc = _unmap(loc, n_anchor, inside_index, fill=0) # 将回归的框对应到所有生成的20000个框中(label原本为所有在图片中的框的) return loc, label # 下面为调用的_creat_label() 函数 def _create_label(self, inside_index, anchor, bbox): label = np.empty((len(inside_index),), dtype=np.int32) # inside_index为所有在图片范围内的anchor序号 label.fill(-1) # 全部填充-1 argmax_ious, max_ious, gt_argmax_ious = self._calc_ious(anchor, bbox, inside_index) 调用_calc_ious()函数得到每个anchor与哪个bbox的iou最大以及这个iou值、每个bbox与哪个anchor的iou最大(需要体会从行和列取最大值的区别) label[ max_ious < self.neg_iou_thresh] = 0 # 把每个anchor与对应的框求得的iou值与负样本阈值比较,若小于负样本阈值,则label设为0,pos_iou_thresh=0.7, neg_iou_thresh=0.3 label[gt_argmax_ious] = 1 # 把与每个bbox求得iou值最大的anchor的label设为1 label[max_ious >= self.pos_iou_thresh] = 1 ##把每个anchor与对应的框求得的iou值与正样本阈值比较,若大于正样本阈值,则label设为1 n_pos = int(self.pos_ratio * self.n_sample) # 按照比例计算出正样本数量,pos_ratio=0.5,n_sample=256 pos_index = np.where(label == 1)[0] # 得到所有正样本的索引 if len(pos_index) > n_pos: # 如果选取出来的正样本数多于预设定的正样本数,则随机抛弃,将那些抛弃的样本的label设为-1 disable_index = np.random.choice( pos_index, size=(len(pos_index) - n_pos), replace=False) label[disable_index] = -1 n_neg = self.n_sample - np.sum(label == 1) # 设定的负样本的数量 neg_index = np.where(label == 0)[0] # 负样本的索引 if len(neg_index) > n_neg: disable_index = np.random.choice( neg_index, size=(len(neg_index) - n_neg), replace=False) # 随机选择不要的负样本,个数为len(neg_index)-neg_index,label值设为-1 label[disable_index] = -1 return argmax_ious, label # 下面为调用的_calc_ious()函数 def _calc_ious(self, anchor, bbox, inside_index): ious = bbox_iou(anchor, bbox) # 调用bbox_iou函数计算anchor与bbox的IOU, ious:(N,K),N为anchor中第N个,K为bbox中第K个,N大概有15000个 argmax_ious = ious.argmax(axis=1) # 1代表行,0代表列 max_ious = ious[np.arange(len(inside_index)), argmax_ious] # 求出每个anchor与哪个bbox的iou最大,以及最大值,max_ious:[1,N] gt_argmax_ious = ious.argmax(axis=0) gt_max_ious = ious[gt_argmax_ious, np.arange(ious.shape[1])] # 求出每个bbox与哪个anchor的iou最大,以及最大值,gt_max_ious:[1,K] gt_argmax_ious = np.where(ious == gt_max_ious)[0] # 然后返回最大iou的索引(每个bbox与哪个anchor的iou最大),有K个 return argmax_ious, max_ious, gt_argmax_ious step3:训练RPN 生成并标注完了训练样本,终于来到了第一功能模块的训练环节。首先对Feature Map进行3×3卷积操作,而后分为两个分支,每一分支都先进行1×1卷积操作,目的是压缩channel。第一个分支的通道数压缩成9×2,9代表每一个锚点的9个anchors,2代表每一个anchor是前景或后景的概率。第二个分支的通道数压缩成9×4,9代表每一个锚点的9个anchors,4代表每一个anchor的4个位置参数预测值。每一个min-batch,只对128个负样本和128个正样本计算分类损失和回归损失( 实际上只对正样本进行回归损失计算)。损失函数如下: 分类损失函数选择的是传统的交叉熵损失函数,分类损失函数选择的是Smooth L1 Loss回归损失函数,如下: 由于在实际过程中,N c N_cN c = min_batch ,N r N_rN r = feature map的大小,两者差距过大,用参数λ平衡二者,使总的网络Loss计算过程中能够均匀考虑2种Loss。 【Module 2】 第二个模块则是根据第一个功能模块输出的得分和四个位置参数,得出ROI并选出合适的RP。该模块在ProposalCreator函数中完成,代码的核心思想:通过训练好的第一个模块输出的约20000个anchor的4个位置参数,微调原图中所有anchor,生成20000个ROI。接着,对ROI进行裁剪,并且剔除掉裁剪后长和宽小于设定阈值的ROI。然后,根据前景score对剩余ROI进行由大到小的排序,若用于RoiHead训练,则取前12000个ROI,经过NMS二次筛选后只取前2000个ROI作为最终的region proposals。若用于RoiHead测试,则取前2000个ROI,经过NMS二次筛选后只取前300个ROI作为最终的region proposals。具体代码实现如下: # 下面是ProposalCreator的代码: 这部分的操作不需要进行反向传播,因此可以利用numpy/tensor实现 class ProposalCreator: # 对于每张图片,利用它的feature map,计算(H/16)x(W/16)x9(大概20000)个anchor属于前景的概率,然后从中选取概率较大的12000张,利用位置回归参数,修正这12000个anchor的位置, 利用非极大值抑制,选出2000个ROIS以及对应的位置参数。 def __call__(self, loc, score, anchor, img_size, scale=1.): # 这里的loc和score是经过region_proposal_network中经过1x1卷积分类和回归得到的 if self.parent_model.training: n_pre_nms = self.n_train_pre_nms # 12000 n_post_nms = self.n_train_post_nms # 经过NMS后有2000个 else: n_pre_nms = self.n_test_pre_nms # 6000 n_post_nms = self.n_test_post_nms # 经过NMS后有300个 roi = loc2bbox(anchor, loc) # 将bbox转换为近似groudtruth的anchor(即rois) roi[:, slice(0, 4, 2)] = np.clip(roi[:, slice(0, 4, 2)], 0, img_size[0]) # 裁剪将rois的ymin,ymax限定在[0,H] roi[:, slice(1, 4, 2)] = np.clip(roi[:, slice(1, 4, 2)], 0, img_size[1]) # 裁剪将rois的xmin,xmax限定在[0,W] min_size = self.min_size * scale # 16 hs = roi[:, 2] - roi[:, 0] # rois的宽 ws = roi[:, 3] - roi[:, 1] # rois的长 keep = np.where((hs >= min_size) & (ws >= min_size))[0] # 确保rois的长宽大于最小阈值 roi = roi[keep, :] score = score[keep] # 对剩下的ROIs进行打分(根据region_proposal_network中rois的预测前景概率) order = score.ravel().argsort()[::-1] # 将score拉伸并逆序(从高到低)排序 if n_pre_nms > 0: order = order[:n_pre_nms] # train时从20000中取前12000个rois,test取前6000个 roi = roi[order, :] keep = non_maximum_suppression( cp.ascontiguousarray(cp.asarray(roi)), thresh=self.nms_thresh) # (具体需要看NMS的原理以及输入参数的作用)调用非极大值抑制函数,将重复的抑制掉,就可以将筛选后ROIS进行返回。经过NMS处理后Train数据集得到2000个框,Test数据集得到300个框 if n_post_nms > 0: keep = keep[:n_post_nms] roi = roi[keep] return roi 五:Semi-Fast R-CNN(RoiHead) 介绍完了RPN模块后,最重要的RP提取任务已经完成。接下来RoiHead只要将RPN输出的RP结果作为输入,来训练和测试。我将训练阶段和测试模块分开讲解: 【训练阶段】 step1:RP中标注训练样本 如果是在训练阶段,RPN会输出大约2000个region proposals。那么如何从中选取样本并标注呢?ProposalTargetCreator函数实现了这一任务,代码的核心思想是:首先,将2000个RP和M个Ground Truth拼接起来,也就是把所有的GT也都作为RP。为什么呢? 前方核能:其实答案很简单,现在是RoiHead的训练阶段,训练RoiHead的分类和二次回归能力。也就是说,需要给该网络输入带有类别标注和实际位置参数的训练数据,经过RPN选出的大范围包括实物的ROI可以作为训练数据,说实话也都是歪歪扭扭的,拿它们训练RoiHead其实主要是出于测试阶段的实际情况出发的,毕竟要从任务实际需要适应的情况出发,实际测试的时候都是RPN找出RP,RoiHead再对它们进行分类和bbox修正的,这些RP都是歪歪扭扭的。所以也就是说,拿这些样本进行分类本身就是不严谨的,不是说完完全全包裹住实物合格的类别样本。毕竟现在是训练阶段嘛,稍微偷偷喂给网络一点”优质碳水“,未尝不可,直接把最优质的GT给它训练去,货真价实的分类样本,舒不舒服,白用白不用。 回到正题,拼接好了RP和GT后,计算它们的最大IOU所对应的那个GT的label,将(label+1)作为每一个RP的类别标注(1~20)。然后将IOU>0.5的RP中选64个作为正样本,将IOU<0.5的RP中选出64个作为负样本并将负样本的label设为0,最后将共128个正负样本打包出来,作为RoiHead的训练输入。具体的实现代码如下: # 下面是ProposalTargetCreator代码:ProposalCreator产生2000个ROIS,但是这些ROIS并不都用于训练,经过本ProposalTargetCreator的筛选产生128个用于自身的训练 class ProposalTargetCreator(object): # 为2000个rois赋予ground truth!(严格讲挑出128个赋予ground truth!) # 输入:2000个rois、一个batch(一张图)中所有的bbox ground truth(R,4)、对应bbox所包含的label(R,1)(VOC2007来说20类0-19) # 输出:128个sample roi(128,4)、128个gt_roi_loc(128,4)、128个gt_roi_label(128,1) def __call__(self, roi, bbox, label, loc_normalize_mean=(0., 0., 0., 0.), loc_normalize_std=(0.1, 0.1, 0.2, 0.2)): # 因为这些数据是要放入到整个大网络里进行训练的,比如说位置数据,所以要对其位置坐标进行数据增强处理(归一化处理) n_bbox, _ = bbox.shape roi = np.concatenate((roi, bbox), axis=0) # 首先将2000个roi和m个bbox给concatenate了一下成为新的roi(2000+m,4)。 pos_roi_per_image = np.round( self.n_sample * self.pos_ratio) # n_sample = 128,pos_ratio=0.5,round 对传入的数据进行四舍五入 iou = bbox_iou(roi, bbox) # 计算每一个roi与每一个bbox的iou (2000+m,m) gt_assignment = iou.argmax(axis=1) # 按行找到最大值,返回最大值对应的序号以及其真正的IOU。返回的是每个roi与**哪个**bbox的最大,以及最大的iou值 max_iou = iou.max(axis=1) # 每个roi与对应bbox最大的iou gt_roi_label = label[gt_assignment] + 1 # 从1开始的类别序号,给每个类得到真正的label(将0-19变为1-20) pos_index = np.where(max_iou >= self.pos_iou_thresh)[0] # 同样的根据iou的最大值将正负样本找出来,pos_iou_thresh=0.5 pos_roi_per_this_image = int( min(pos_roi_per_image, pos_index.size)) # 需要保留的roi个数(满足大于pos_iou_thresh条件的roi与64之间较小的一个) if pos_index.size > 0: pos_index = np.random.choice( pos_index, size=pos_roi_per_this_image, replace=False) # 找出的样本数目过多就随机丢掉一些 neg_index = np.where((max_iou < self.neg_iou_thresh_hi) & (max_iou >= self.neg_iou_thresh_lo))[0] # neg_iou_thresh_hi=0.5,neg_iou_thresh_lo=0.0 neg_roi_per_this_image = self.n_sample - pos_roi_per_this_image # #需要保留的roi个数(满足大于0小于neg_iou_thresh_hi条件的roi与64之间较小的一个) neg_roi_per_this_image = int(min(neg_roi_per_this_image, neg_index.size)) if neg_index.size > 0: neg_index = np.random.choice( neg_index, size=neg_roi_per_this_image, replace=False) # 找出的样本数目过多就随机丢掉一些 keep_index = np.append(pos_index, neg_index) gt_roi_label = gt_roi_label[keep_index] gt_roi_label[pos_roi_per_this_image:] = 0 # 负样本label 设为0 sample_roi = roi[keep_index] # 那么此时输出的128*4的sample_roi就可以去扔到 RoIHead网络里去进行分类与回归了。同样, RoIHead网络利用这sample_roi+featue为输入,输出是分类(21类)和回归(进一步微调bbox)的预测值,那么分类回归的groud truth就是ProposalTargetCreator输出的gt_roi_label和gt_roi_loc。 gt_roi_loc = bbox2loc(sample_roi, bbox[gt_assignment[keep_index]]) # 求这128个样本的groundtruth gt_roi_loc = ((gt_roi_loc - np.array(loc_normalize_mean, np.float32) ) / np.array(loc_normalize_std, np.float32)) # ProposalTargetCreator首次用到了真实的21个类的label,且该类最后对loc进行了归一化处理,所以预测时要进行均值方差处理 return sample_roi, gt_roi_loc, gt_roi_label step2: 正式训练 将这128个标注好的训练样本,从原图中投影到Feature Map中对应的ROI区域中,然后进入RoiPooling层,将这些大小不一的ROI区域变为同一长度的向量,再经过两层 4096 FC层,分别得到softmax21分类打分和bbox的84个参数(21 * 4)的预测结果,放入损失函数中进行反向传播更新网络权重,其中只计算正样本的回归框损失。损失函数和RPN的类似,这里就不再赘述了,贴上损失函数核心代码: def _fast_rcnn_loc_loss(pred_loc, gt_loc, gt_label, sigma): #输入分别为rpn回归框的偏移量与anchor与bbox的偏移量以及label in_weight = t.zeros(gt_loc.shape).cuda() # Localization loss is calculated only for positive rois. # NOTE: unlike origin implementation, # we don't need inside_weight and outside_weight, they can calculate by gt_label in_weight[(gt_label > 0).view(-1, 1).expand_as(in_weight).cuda()] = 1 loc_loss = _smooth_l1_loss(pred_loc, gt_loc, in_weight.detach(), sigma) #sigma设置为1 # Normalize by total number of negtive and positive rois. loc_loss /= ((gt_label >= 0).sum().float()) # ignore gt_label==-1 for rpn_loss #除去背景类 return loc_loss roi_cls_loss = nn.CrossEntropyLoss()(roi_score, gt_roi_label.cuda())#求交叉熵损失 1 【测试阶段】 RoiHead的测试阶段就是将RPN中输出的300个RP,输入进网络中,最后会输出每个RP的类和4个回归框微调参数。剔除掉高于背景(0)阈值和最大类别(1~20)得分低于阈值的RP,最后根据回归参数,对筛选后剩下的RP框进行微调,得到最终的bounding box!至此,大功告成。 六:Faster R-CNN训练方法 Faster-RCNN有两种训练方式:四步交替迭代训练和联合训练。本文主要讲解四步交替迭代的训练方式,如下所示: 1、训练RPN,使用大型数据集预训练模型初始化共享卷积和RPN权重,端到端训练RPN,用于生成Region Proposals; 2、训练Fast R-CNN,使用相同的预训练模型初始化共享卷积【注意此处是初始化一个新的与第1步结构相同的共享卷积网络,而不是第1步中训练得到的】,锁住第1步训练好的RPN权重,结合RPN得到的Proposals训练RCNN网络; 3、调优RPN,使用第2步训练好的共享卷积和RCNN,固定共享卷积层,继续训练RPN,我认为这一步相当于对第1步训练好的RPN进行微调; 4、调优Fast R-CNN,使用第3步训练好的共享卷积和RPN(固定住共享卷积层),继续对RCNN进行训练微调 5、重复上述步骤3、4,进行迭代。(一般到步骤四其实已经够了,后面迭代训练后的效果几乎无提升) 下面是一张训练过程流程图,应该更加清晰: 七:Faster R-CNN测试方法 接下面讲解整个网络的测试过程,即将大功告成啦! step1:输入图像经过卷积层得到feature map step2:feature map经过RPN得到300个RP step3:将RP输入到RoiHead网络中 step4:得出每个RP的类别得分和bbox位置参数 step5:由得分阈值选出最终的ROI step6:结合位置参数微调ROI的bbox框 step7:经过NMS后画出最终检测框 八:总结 Fast R-CNN尽管速度和精度上都有了很大的提升,但仍然未能实现端到端(end-to-end)的目标检测,比如候选区域的获得不能同步进行,速度上还有提升空间。 最后附上一个超大原理流程图收尾,供大家参考: 至此我对Faster R-CNN全部流程与细节,进行了深度讲解,希望对大家有所帮助,有不懂的地方或者建议,欢迎大家在下方留言评论。(码字不易,各位看官点个赞,手留余香~谢谢!) 我是努力在CV泥潭中摸爬滚打的江南咸鱼,我们一起努力,不留遗憾! ———————————————— 版权声明:本文为CSDN博主「江南綿雨」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/weixin_43702653/article/details/124045469
-
请问这里一直报错请求参数无效是怎么回事,请求体的参数是用F12中的载荷找的
-
随着业务场景的不断丰富,批量计算也由传统的HPC逐渐扩展到大数据、AI等多种场景,但各领域独立发展将造成技术栈不兼容、资源使用率低等问题。在此背景下,云原生技术以其丰富的生态和灵活的扩展性受到各大厂商的青睐,云原生统一基础设施成为必然选择。Volcano作为CNCF首个云原生批量计算项目,支持几乎所有的主流计算框架,提供丰富的高级调度策略及多元算力支持,可在人工智能、大数据、基因测序等海量数据计算和分析场景得到快速应用,弥补了Kubernetes批处理调度能力的空缺 ,并已构建起完善的上下游生态。项目目前已有450+位来自全球各地的贡献者和50+落地案例。今年6月,Apache Spark 3.3版本正式发布,v3.3选择Volcano作为默认batch调度器,助力大数据平台云原生化。9月29日开始,Volcano即将全面开启技术分享课程,核心技术无保留传授——27节公开课,全网首发!13位批量计算行业大咖亲自教学一门职业技能,轻松get!课程研发团队 本次批量计算课程由CNCF 首个容器批量计算项目Volcano的核心贡献者联合研发。适合人群 计算机相关专业的学生群体云计算应用开发、运维工程师、产品经理云原生开源技术爱好者、Volcano社区用户、贡献者部分课表 课次课程名称1云原生批量计算公开课简介2云原生批量计算的先行者Volcano3Volcano安装部署快速入门4如何在AI场景下使用Volcano?5如何在大数据场景下使用Volcano?6如何在HPC场景下使用Volcano?7云原生批量计算的集大成者Volcano Job8Volcano Job插件机制及使用9手把手教你构建自己的Volcano Job插件10资源调度的基石Queue11Volcano的独门秘籍PodGroup12Volcano技术内幕:架构设计与实现原理13走近Action和Plugin机制14Action和Plugin之资源预匹配与SLA策略15Action和Plugin之资源分配与装箱策略扫码回复Volcano进群 免费领取全部27节课·课表不能错过的第一堂课 9月29日日开启,每周周四晚20:00开课CNCF 大使 Kevin WangVolcano 社区 Maintainer 王雷博为大家率先开启课程后续每周讲师信息,可添加k8s2222进学习群获取成为大家的批量计算导师带大家开启云原生批量计算的学习整个学习期间,你们的大佬级导师,也会在学习群内陪伴大家的课程,监督大家的课程进度哦。从入门到精通,万万不能错过第一课!(扫上方海报码回复Volcano进群)学批量计算,认识批量计算达人技能破局,需要1次体系化的洗礼27节课程规划,理论+实践提升战力每周1小时,轻松为个人职业加码不只是学习,一路结识更多批量计算达人添加助教微信领取开课礼包
-
一、简介 实现计算机视觉任务的过程中,不可避免地需要对图像进行读写操作以及图像预处理操作,下面介绍两个常用的Python图像处理库:OpenCV和Pillow。 OpenCV全称是由英特尔公司资助的开源计算机视觉库。 它由一系列C函数和少量C++类所组成,实现图像处理和计算机视觉方面的很多通用算法,例如特征检测与跟踪、运动分析、目标分割与识别以及3D重建等。 OpenCV作为基于C/C++语言编写的跨平台开源软件,可以运行在Linux、Windows、Android和MacOS操作系统上,同时提供了Python、Ruby、MATLAB等语言的接口,实现了图像处理和计算机视觉方面的很多通用算法。 1.1 图像处理-OpenCV OpenCV是模块结构的,有以下主要模块。 【core】–核心功能模块,包含内容有:OpenCV基本数据结构、动态数据结构、绘图函数、数组操作相关函数、辅助功能与系统函数和宏、与OpenGL的互操作。 【imgproc】–图像处理模块,包含内容有:线性和非线性的图像滤波、图像的几何变换、图像转换、直方图相关、结构分析和形状描述、运动分析和对象跟踪、特征检测、目标检测等内容。 【features2D】–2D功能模块,包含以下内容:特征检测和描述、特征检测器、描述符提取器等内容。 【highGUI】–高层GUI图形用户界面,包含:媒体的I/O输入输出、视频捕捉、图像和视频的编码解码、图形交互界面的接口等内容。 1.2 图像处理- PIL和Pillow 作为Python2的第三方图像处理库是Pillow的前身。随着Python3的更新,PIL移植到Python3更名为Pillow。与OpenCV一样,Pillow也是模块结构,主要包括以下结构。 【Image】–图像功能模块,包含内容有:读写图像、图像混合、图像放缩、图像裁切、图像旋转。 【ImageFilter】–图像滤波功能模块,包含内容有:各类图像滤波核。 【ImageEnhance】–图像增强功能模块,包含内容有:色彩增强、亮度增强、对比度增强、清晰度增强。 【ImageDraw】–图像绘画功能模块,包含内容有:绘制几何形状、绘制文字。 二、 常用图像类型 2.1 二值图像 二值图像只有黑白两种颜色,如图所示。图像中的每个像素只能是黑或白,没有中间的过渡。因此二值图像的像素值只能为0或1,0表示黑色,1表示白色。 2.2 灰度图像 灰度图像只表达图像的亮度信息没有颜色信息,如(a)图所示。 灰度图像的每个像素点上只包含一个量化的灰度级(即灰度值)。 像素点的亮度水平如(b)图所示,通常使用1字节(8位二进制数)来存储灰度值,因此用正整数表示灰度值的范围是0~255。 2.3 RGB图像 RGB(Red、Green、Blue)图像如(a)图,可以看成是由多个RGB像素点组成。 每个彩色像素点分别由R、G、B三种颜色空间组成如(b)图,本质是3维数组。 在RGB颜色空间中,任意色光都可以用R、G、B三色不同分量的相加混合而成。 2.4 常用颜色空间简介 实际应用中常用的颜色空间很多,通常使用3个独立的变量对颜色进行描述,例如RGB、HSV、YUV等。 一个图像的不同的颜色空间是可以转换的,cv2.cvtColor可以实现颜色的转换。 • cv2.COLOR_BGR2GRAY • cv2.COLOR_BGR2RGB • cv2.COLOR_BGR2HSV • cv2.COLOR_BGR2YUV • cv2.COLOR_BGR2HLS 三、OpenCV图像读写与显示 通过OpenCV库对数字图像进行处理时,涉及到的基础操作包括读取、显示、写出图像文件。 在OpenCV库中,图像数据是以NumPy数组的形式存在。 3.1 读入图像 在python的OpenCV中,通过cv2.imread()函数读入图像数据,其基本使用格式如下。 cv2.imread(filename, flags) 在默认情况下通过cv2.imread()函数读入图像数据为3通道的彩色图,像素值为8位的非负整数,图像数据以NumPy中ndarray的方式存在。 如果定义了cv2.imread()读入模式为cv2.IMREAD_GRAYSCALE那么读入图像为单通道的灰度图。 需要注意的是,通过OpenCV读入彩色图像数据的颜色通道顺序为BGR(蓝、绿、红)并非常用的RGB(红、绿、蓝)顺序。 在OpenCV中,读取到的图像数组维度可以表示为: (高,宽,通道数)即(height, width, channel)。OpenCV图像坐标与常规的数学坐标有区别,左上角位置为坐标原点。 • 在Windows【画图】工具中,帽檐红点坐标为(248, 102),但在OpenCV中像素位置应为(102, 248)。 • X轴 == 列位置 == 图像宽 • Y轴 == 行位置 == 图像高 3.2 显示图像 在OpenCV中,通过cv2.imshow()函数显示图像数据,其基本使用格式如下。 cv2.imshow(winname, img) 在OpenCV中,通过cv2.waitKey()函数设置图片窗口显示时长,其基本使用格式如下。 cv2.waitKey([,delay]) waitKey作用是图像显示时等待用户按键触发,如果用户按键触发或时间超过了设置的时间则退出图片展示。 cv2.waitKey(0)的作用是令程序一直停留在显示图像的状态。如果没有增加cv2.waitKey(0),那么程序运行完毕后,图像显示窗口会自动关闭,即一闪而逝。 3.3 写出图像 在OpenCV中,通过cv2.write()函数保存图像数据,其基本使用格式如下。 cv2.imwrite(filename, img) import cv2 import matplotlib.pyplot as plt import numpy # 读写图像 img = cv2.imread(filename='lena.jpg', flags=cv2.IMREAD_GRAYSCALE) img.shape # (377, 373) img.dtype # dtype('uint8') # 图像展示 cv2.imshow(winname='lena', mat=img) cv2.waitKey(0) # 图像保存 cv2.imwrite(filename='test_img.jpg', img=img) 四、图像几何变换 4.1 图像平移 图像平移变换将一幅图像中的所有像素点都按照给定的偏移量在水平方向(沿x轴方向)或垂直方向(沿y轴方向)移动,是图像几何变换中较为简单的一种变换。 图像平移原理示意图如下图所示。 假设对点P_0 (x_0,y_0 )进行平移后得到点P(x,y),其中x方向的平移量为∆x,y方向的平移量为∆y ,则点P(x,y)的坐标如下式。 利用齐次坐标表示图像平移变换前后点P_0 (x_0,y_0 )到点P(x,y)的关系如下式所示。 实现步骤: 1. 定义平移变换矩阵: 例如:np.float32([[1,0,50], [0,1,100]]) • [1,0,50]表示在x轴方向移动50个单位 • [0,1,100]表示在y轴方向移动100个单位 2. 执行转换:cv2.warpAffine (src, M, dsize) 通过OpenCV实现图像平移操作,结果如下图所示,(a)为原图和(b)为平移后图像。 import cv2 import matplotlib.pyplot as plt import numpy as np img = cv2.imread('lena.jpg') height, width, channel = img.shape # 图像平移 # 1、定义平移变换矩阵 M = np.float32([[1,0,50], [0,1,100]]) # 2、执行平移变换 img_tran = cv2.warpAffine(src=img, M=M, dsize=(height, width)) cv2.imshow('image translation', img_tran) cv2.waitKey(0) 4.2 图像旋转 图像旋转(Rotation)是指图像以某一点为中心旋转一定的角度形成一幅新的图像的过程。通常是以图像的中心为圆心旋转,将图像中的所有像素点都旋转一个相同的角度。 图像旋转原理如图所示,将点(x_0,y_0)绕原点o顺时针旋转至点(x_1,y_1 ),其中a为旋转角,r为点(x_0,y_0 )到原点的距离, b为原点o到点(x_0,y_0 )的线段与x轴之间的夹角。在旋转过程中, r保持不变。 设旋转前,x_0、y_0的坐标分别为x_0=r cosb、y_0=r sinb,当旋转a角度后,坐标x_1、y_1的值分别如下式所示。 上式的矩阵的形式如下式所示。 实现步骤: 1. 计算旋转变换矩阵: cv2.getRotationMatrix2D (center, angle, scale) 2. 执行转换:cv2.warpAffine (src, M, dsize) 通过OpenCV实现图像旋转,得到的效果如图所示。 # 图像旋转 # 1、定义旋转变换矩阵 M = cv2.getRotationMatrix2D(center=(height*0.5, width*0.5), # 旋转的中心位置坐标 angle=45, # 旋转的角度 scale=0.8 # 缩放比例 ) # 2、执行旋转变换 img_rotation = cv2.warpAffine(img, M, dsize=(height,width)) cv2.imshow('image_rotation', img_rotation) cv2.waitKey(0) 4.3 图像缩放 图像比例缩放是指将给定的图像在x轴方向按比例缩放f_x倍,在y轴方向按比例缩放f_y倍,从而获得一幅新的图像。如果f_x=f_y,即x轴方向和y轴方向缩放的比率相同,此比例缩放为图像的全比例缩放。如果f_x≠f_y,那么图像的比例缩放会改变原始图像的像素间的相对位置,产生几何畸变。 在OpenCV中cv2.resize(src, dsize, fx, fy, interpolation)可以实现图像缩放。 具体实现图像缩放有多种插值方法,OpenCV的resize函数提供了如下5种常见方法。 • 最邻近插值:cv2.INTER_NEAREST • 双线性插值:cv2.INTER_LINEAR • 区域插值:cv2.INTER_AREA • 三次样条插值:cv2.INTER_CUBIC • Lanczos插值:cv2.INTER_LANCZOS4 # 图像缩放 # 1、直接指定缩放大小 img_res = cv2.resize(img, dsize=(244,244)) img_res.shape # (244, 244, 3) cv2.imshow('image_resize', img_res) cv2.waitKey(0) # 2、最近邻插值 img_near = cv2.resize(img, dsize=None, fx=1.5, fy=1, interpolation=cv2.INTER_NEAREST) cv2.imshow('img_near', img_near) cv2.waitKey(0) ———————————————— 版权声明:本文为CSDN博主「明之季」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/u011125673/article/details/125240315
-
七、赛事交流 1、大赛官方交流论坛:比赛过程中参赛选手可以到ModelArts社区(ModelArts开发讨论板块)发帖,标题命名为:【广西AI+您的问题】,正文详细描述您遇到的问题(可附截图),大赛组委会工作人员将会在工作日工作时间通过社区回复,点击此处查看您提出的所有问题。 说明:为保证大赛公平公正原则,大赛官方仅针对ModelArts使用问题、赛题、数据、评分、大赛安排等问题进行答疑,参赛选手的代码调试问题不做解答。如没有得到及时回复,可以将社区帖链接发至交流群,反馈给工作人员。 2、参赛选手官方交流群(仅作为选手之间讨论和交流):请扫描如下华为云小助手微信二维码,请备注:广西AI大赛,经小助手邀请后进入相应交流群。大赛重要节点通知会在群内第一时间告知。 请与我联系周先生 19897922013 或陈女士
-
一:初识R-CNN R-CNN系列论文(R-CNN,fast-RCNN,faster-RCNN)是使用深度学习进行物体检测的鼻祖论文,其中fast-RCNN 以及faster-RCNN都是沿袭R-CNN的思路。 R-CNN全称region with CNN features,其实它的名字就是一个很好的解释。用CNN提取出Region Proposals中的featues,然后进行SVM分类与bbox的回归。 [网络结构] 接下来我从 训练阶段 和 测试阶段 分别讲解R-CNN中的核心思路。 二:训练步骤 1.RP的确定 首先介绍一下Selective Search算法,训练过程中用于从输入图像中搜索出2000个Region Proposal。Selective Search算法主要步骤: 使用一种过分割手段,将图像分割成小区域 (1k~2k 个) 计算所有邻近区域之间的相似性,包括颜色、纹理、尺度等 将相似度比较高的区域合并到一起 计算合并区域和临近区域的相似度 重复3、4过程,直到整个图片变成一个区域 在每次迭代中,形成更大的区域并将其添加到区域提议列表中。这种自下而上的方式可以创建从小到大的不同scale的Region Proposal,如图所示: 2.模型pre-training 在实际测试的时候,模型需要通过CNN提取出RP中的特征,用于后面的分类与回归。所以,如何训练好CNN成为重中之重。 由于物体标签训练数据少,如果要直接采用随机初始化CNN参数的方法是不足以从零开始训练出一个好的CNN模型。基于此,采用有监督的预训练,使用一个大的数据集(ImageNet ILSVC 2012)来训练AlexNet,得到一个1000分类的预训练(Pre-trained)模型。 3.Fine-Tunning 因为R-CNN模型实际测试时,是通过CNN对VOC测试集中每张输入图像上搜索到的2000个Region Proposal提取特征的。而RP大小都不相同,且AlexNet要求输入图像大小是227×227,所以需要对RP进行resize操作,将它们变形为227×227。变形之前,我们先在候选框周围加上16的padding,再进行各向异性缩放。 这种形变使得mAp提高了3到5个百分点。 而原CNN模型针对ImageNet数据集且无变形的图像来提取特征,现在却是针对VOC检测数据集且变形的图像来提取特征。所以,为了让我们的CNN适应新的任务(即检测任务)和新的领域(变形后的推荐窗口),需要对CNN做特定领域的参数调优,也就是fine-tunning。用的是从每张VOC训练图像中搜索到的Region Proposal进行微调的。 ( 备注:还有一个原因,如果不针对特定任务进行fine-tuning,而是把CNN当做特征提取器,卷积层所学到的特征其实就是基础的共享特征提取层,就类似于SIFT算法一样,可以用于提取各种图片的特征,而f6、f7所学习到的特征是用于针对特定任务的特征。打个比方:对于人脸性别识别来说,一个CNN模型前面的卷积层所学习到的特征就类似于学习人脸共性特征,然后全连接层所学习的特征就是针对性别分类的特征了) 首先对 PASCAL VOC数据集 进行Selective Search,搜索到2000个Region Proposal对Pre-trained模型进行fine-tuning。将原来预训练模型最后的1000-way的全连接层(分类层)换成21-way的分类层(20类物体+背景),然后计算每个region proposal和ground truth 的IoU,对于IoU>0.5的region proposal被视为正样本,否则为负样本(即背景)。另外,由于对于一张图片的多有候选区域来说,负样本是远远大于正样本数,所以需要将正样本进行上采样来保证样本分布均衡。在每次迭代的过程中,选择层次采样,每个mini-batch中采样两张图像,从中随机选取32个正样本和96个负样本组成一个mini-batch(128,正负比:1:3)。我们使用0.001的学习率和SGD来进行训练。 4.提取并保存RP的特征向量 提取特征的CNN网络经过了预训练和微调后不再训练,就固定不变了,只单纯的作为一个提特征的工具了。虽然文中训练了CNN网络对region proposal进行分类,但是实际中,这个CNN的作用只是提取每个region proposal的feature。 所以,我们输入VOC训练数据集,SS搜索出2000个RP后输入进CNN进行前向传播,然后保存AlexNet的FC7层4096维的features,以供后续的SVM分类使用。 5.SVM的训练 作者使用SVM进行分类。对于每一类都会训练一个SVM分类器,所以共有N(21)个分类器,我们来看一下是如何训练和使用SVM分类器的。 在SVM分类过程中,IOU<0.3被作为负例,ground-truth(即完全框住了物体,默认IOU>0.7时)是正例,其余的全部丢弃。然后SVM分类器也会输出一个预测的labels,然后用labels和truth labels比较,计算出loss,然后训练SVM。 其中,有一个细节,就是SVM由于是小样本训练,所以会存在负样本远多于正样本的情况。针对这种情况,作者使用了hard negative mining方法(初始时用所有样本训练,但是这样负样本可能远多王正样本,经过一轮训练后将score最高即最容易被误判的负样本加入新的样本训练集,进行训练,重复以上步骤至达到停止条件比如分类器性能不再提升),使得SVM适用于小样本训练,在样本不平衡时依然可以做到不会发生过拟合。 作者为什么要Hard Negatives?因为,负样本数目巨大,其中Pos样本数目占的比例特别低,负样本太多,直接导致优化过程很慢,因为很多负样本远离分界面对于优化几乎没有帮助(SVM分类最大间隔,只有支持向量比较有用)。Data-minig的作用就是去掉那些对优化作用很小的Easy-examples保留靠近分界面的Hard-examples。 前方核能: 有人会好奇为什么要专门使用SVM分类,而不是CNN最后的21层softmax层分类?这里我来重点讲解一下,仅个人理解: 细心的人会发现,之前在训练CNN提取特征时,设置的IOU是0.5以上为正样本,小于0.5的是负样本。但在SVM分类中,只有bbox完全包围了物体(也可以理解为IOU>0.7时)才是正样本,IOU小于0.3的是负样本。前者是大样本训练,后者是小样本训练。对于CNN的训练,需要大量的数据,不然容易过拟合,所以设置的阈值较低,比如一个bounding box可能只包含物体的一部分,那么我也把它标注为正样本,用于训练cnn;然而svm训练的时候,因为svm适用于少样本训练,所以对于训练样本数据的IOU要求比较严格,我们只有当bounding box把整个物体都包含进去了(IOU>0.7),我们才把它标注为物体类别,IOU<0.3的标注为负样本,然后训练svm。就因为这个特点,只能用低IOU来softmax分类,那么很可能导致最后的bbox框位置不准确(如果bbox和GT差距太大,通过线性回归会无法收敛),同时类识别精度也不高,根据实验MAP会下降几个百分点。如果硬要提高IOU,又会导致训练数据样本太少,发生过拟合。这就是真正的"鱼和熊掌不可得兼"啊,其实罪魁祸首就是Small VOC的训练量太少了,限制了太多优化操作。故最后选择了SVM完成分类,CNN只用来提取特征。 这样做后,精度也会有很大的提升,同时对于后面的bbox回归准确度也提高了。 6.bbox regression的训练 与GT的IOU>0.6的RP作为正样本,做回归训练。具体做法请看這篇文章 其实就是训练 d dd 矩阵向 t tt 矩阵靠齐的过程。 三:测试步骤 讲解完了R-CNN的训练过程,现在我们来讲一讲测试过程。我分以下五个步骤依次进行讲解: step1:Region proposal的确定 VOC测试图像输入后,利用SS搜索方法,根据相似度从大到小排序,筛选出2000个region proposals 。 step2:RP的Features提取 将RP通过resize成227×227,然后分别输入进CNN特征提取网络,得到了2000个4096维features。 step3:SVM分类 将(2000,4096)维矩阵输入进SVM分类器中,最终得到(2000,21)矩阵。每一行的21个列值,分别代表了这个RP属于每一个类的可能性。通过提前设置好的backgroud阈值 α αα 和所属于类的阈值 β ββ,筛选出满足条件的 m mm 个RP区域。 step4:BoundingBox-Regression 将(2000,4096)维矩阵输入进 (4096,4)的回归矩阵 d dd 中,最后输出(2000,4)偏移矩阵。代表RP中心点的位置偏移 和 bbox的尺寸变换。 将SVM筛选出的 m mm 个RP区域对应的特征向量,组成(m,4096)矩阵 代入 (4096,4)的回归矩阵 d dd 中,最后输出(m,4)偏移矩阵。 step5:Non-maximum suppression处理 只画出SVM筛选出的 m mm 个RP区域的修正后的检测框。考虑到bbox的大量累赘重叠,进行非极大值抑制(NMS),得到最终检测结果。 附上每一步处理的示例图,如下: 四:R-CNN存在的问题 训练时间长:主要原因是分阶段多次训练,而且对于每个region proposal都要单独计算一次feature map,导致整体的时间变长。 占用空间大:每个region proposal的feature map都要写入硬盘中保存,以供后续的步骤使用。 multi-stage:文章中提出的模型包括多个模块,每个模块都是相互独立的,训练也是分开的。这会导致精度不高,因为整体没有一个训练联动性,都是不共享分割训练的,自然最重要的CNN特征提取也不会做的太好。 测试时间长,由于不共享计算,所以对于test image,也要为每个proposal单独计算一次feature map,因此测试时间也很长。 至此我对R-CNN全部流程与细节,进行了深度讲解,希望对大家有所帮助,有不懂的地方或者建议,欢迎大家在下方留言评论。 我是努力在CV泥潭中摸爬滚打的江南咸鱼,我们一起努力,不留遗憾! ———————————————— 版权声明:本文为CSDN博主「江南綿雨」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/weixin_43702653/article/details/123973629
-
一、系统自带的快捷改名 1、打开对应的图片文件 2、全选后右击重命名 3、输入:Cat 注意: 默认你输入的Cat与(1)之间有一个空格 二、用Python按要求改名 1、代码如下: import os.path def rename(img_folder,num): for img_name in os.listdir(img_folder): # os.listdir(): 列出路径下所有的文件 #os.path.join() 拼接文件路径 src = os.path.join(img_folder, img_name) #src:要修改的目录名 dst = os.path.join(img_folder, 'Cat'+ str(num) + '.jpg') #dst: 修改后的目录名 注意此处str(num)将num转化为字符串,继而拼接 num= num+1 os.rename(src, dst) #用dst替代src def main(): img_folder0 = 'D:/Picture' #图片的文件夹路径 直接放你的文件夹路径即可 num=1 rename(img_folder0,num) if __name__=="__main__": main() 2、结果如下 三、在原有的图片上进行修改 例如将二中改后的图片名字:“Cat+序号”改为“Cat+Cute+序号” 1、代码如下: import os.path def rename(img_folder,num): for img_name in os.listdir(img_folder): # os.listdir(): 列出路径下所有的文件 #os.path.join() 拼接文件路径 src = os.path.join(img_folder, img_name) #src:要修改的目录名 dst = os.path.join(img_folder, img_name.split('t')[0]+'tCute'+str(num) + '.jpg') #dst: 修改后的目录名 注意此处str(num)将num转化为字符串,继而拼接 num= num+1 os.rename(src, dst) #用dst替代src def main(): img_folder0 = 'D:/Picture' #图片的文件夹路径 直接放你的文件夹路径即可 num=1 rename(img_folder0,num) if __name__=="__main__": main() 2、结果如下 3、关键代码:split() 仅修改了dst的代码: dst = os.path.join(img_folder, img_name.split('t')[0]+'tCute'+str(num) + '.jpg')' 在二中修改后 img_name=Cat+序号 例如:Cat1 img_name.split('t')作用:以’t’为分隔符分割img_name 即 img_name.split('t')[0]=Ca img_name.split('t')[1]=序号 t作为分割符被舍弃了 所以修改后的代码中是'tCute' 总结 学会了以上的方法和操作, 你就会随心所欲的批量修改图片名字了,也可推广至其它文件命名,即将最后的.jpg改为其它文件格式的后缀即可。快去试试吧! ❤️❤️❤️忙碌的敲代码也不要忘了浪漫鸭! 👻五一小长假你有什么有趣的事情要分享呢? ———————————————— 版权声明:本文为CSDN博主「怪&」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qq_21471309/article/details/124537319
-
chrome扩展后显示如下错误,请指导。
yd_280475890
发表于2022-10-02 12:09:02
2022-10-02 12:09:02
最后回复
This is WeAutomate
2022-10-07 19:40:07
85 1
上滑加载中
推荐直播
-
华为云码道Agent集成与鸿蒙实战2026/08/11 周二 19:00-21:00
王一男-华为云码道产品规划专家;李炎-华为云码道产品专家;彭江敏-华为云鸿蒙端云一体化开发专家
本次直播带你解读华为云码道7月份产品新特性、新功能。更有专家演示码道Agent Space × 钉钉机器集成实战,从0到1打通消息通道;码道鸿蒙端云一体化实战,快速搭建员工签到系统。
回顾中 -
华为云开发者AI素养直播课·第五期2026/09/04 周五 16:00-18:00
林华鼎-华为云AI开发者运营负责人;蒋春阳-华为云AI开发者案例开发专家
本期直播内容: AI工具体验营 · 第5-8课连讲。Agent-Team 多智能体协作完成毕业设计实践
回顾中 -
华为云开发者AI素养ClassRoom·第六期2026/09/08 周二 19:00-20:00
樊渊-2026华为软件挑战赛冠军
高手来了:看软挑高手解析二维排样问题—从工业难题到算法突破
回顾中
热门标签