我自己的原文哦~ https://blog.51cto.com/whaosoft/12863288
一、OpenCV4图像分割算法-AlphaMatting infoFlow使用演示与应用
主要介绍OpenCV Alpha Matting中Info Flow图像分割算法的使用与演示。
背景介绍
Information Flow Alpha Matting算法是来源于Google Summer of Code 2019,该算法在OpenCV4.3版本中被加入。
详细介绍可参考OpenCV官方文档介绍:
https://docs.opencv.org/4.x/dd/d0e/tutorial_alphamat.html
https://docs.opencv.org/4.3.0/d4/d40/group__alphamat.html#gad599f98a151eda56ab241b296aec4810
这个算法能实现什么功能或者效果?以下图为例说明:
输入图像:
标记的掩码图像(其中白色为确定的前景区域,黑色为确定的背景区域,灰色为不确定区域):
通过此算法可以得到下面的分割结果:
以上面分割结果作为掩码,可以很容易地完成类似下面替换背景的效果:
OpenCV中使用演示
OpenCV中如何使用此算法?下面以OpenCV4.7.0为例做演示。
如上所述,要使用AlphaMatting infoFlow首先要用cmake编译源码,因为alphamat是在contrib模块中,所以cmake时也需要编译contrib模块。同时,要成功编译alphamat模块,必须编译Eigen包,具体步骤可参考下面链接:
https://blog.csdn.net/haronchou/article/details/128753566
编译完成后,生成opencv_world470.lib和opencv_world470.dll还有头文件,必须要有下面的头文件才能使用:
【1】VS2017配置OpenCV4.7.0(上面CMake生成的)
① 包含目录:
D:\OpenCV4.7.0_CMake\opencv-4.7.0\build\install\include
D:\OpenCV4.7.0_CMake\opencv-4.7.0\build\install\include\opencv2
② 库目录:
D:\OpenCV4.7.0_CMake\opencv-4.7.0\build\install\x64\vc15\lib
连接器--》输入--》附加依赖项:
opencv_world470.lib
【2】准备测试图片和掩码图
【3】编写代码测试
// 公众号:OpenCV与AI深度学习
// 作 者:Color Space
#include <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/alphamat.hpp>
using namespace std;
using namespace cv;
using namespace alphamat;
int main()
{
Mat src = imread("src.png");
Mat mask = imread("mask.png", 0);
Mat bg = imread("bg2.jpg");
if (src.empty() || mask.empty() || bg.empty())
{
cout << "please check input images!" << endl;
return -1;
}
imshow("src", src);
imshow("mask", mask);
Mat segment;
infoFlow(src, mask, segment);
imshow("segment", segment);
Mat ROI = bg(Rect(0, 0, src.cols, src.rows));
for (int i = 0; i < segment.rows; i++)
{
uchar *ptr = segment.ptr<uchar>(i);
uchar *ptrColor = ROI.ptr<uchar>(i);
uchar *ptrSrc = src.ptr<uchar>(i);
for (int j = 0; j < src.cols; j++)
{
float alpha = ptr[j] / 255.0;
float beta = 1 - alpha;
ptrColor[j * 3] = ptrSrc[j * 3] * alpha + ptrColor[j * 3] * beta;
ptrColor[j * 3 + 1] = ptrSrc[j * 3 + 1] * alpha + ptrColor[j * 3 + 1] * beta;
ptrColor[j * 3 + 2] = ptrSrc[j * 3 + 2] * alpha + ptrColor[j * 3 + 2] * beta;
}
}
imshow("result", bg);
waitKey();
}
【4】运行结果与背景替换效果
延伸扩展
上面演示的分割效果好像还行,但是掩码图像都是我们提前制作的,有没有办法自动生成mask来引导算法得到好的分割结果呢?
以下图为例做说明和演示:
我们希望分割出花朵,但是不想手动去制作mask,怎么操作?
【1】通过特定的特征先提取部分目标物体轮廓,比如颜色、形态等。
【2】通过形态学或其他操作获取未知待定区域轮廓。
【3】调用infoFlow分割算法。
二、OpenCV使用CUDA加速资料汇总
关于OpenCV中使用GPU加速以及CUDA版本的编译使用,下面文章已详细介绍,可以点击链接查看:
OpenCV4.8 GPU版本CMake编译详细步骤 与CUDA代码演示
下面是一些相关学习资料,分享给大家:
【1】背景介绍相关pdf
【2】OpenCV CUDA开发教程--Hands-On GPU-Accelerated Computer Vision with OpenCV and CUDAHands-On GPU-Accelerated Computer Vision with OpenCV and CUDA
本书主要内容
第1章,CUDA简介和CUDA入门,介绍了CUDA体系结构以及它如何重新定义GPU的并行处理功能。讨论了CUDA架构在现实场景中的应用。介绍了用于CUDA的开发环境以及如何在所有操作系统上安装它。
第2章,并行编程使用CUDA C,教会读者使用CUDA为GPU编写程序。它从一个简单的Hello World程序开始,然后逐步构建到CUDA C中的复杂示例。它还介绍了内核的工作原理以及如何使用设备属性,并讨论了与CUDA编程相关的术语。
第3章,线程,同步和内存,向读者讲授如何从CUDA程序调用线程以及多个线程如何相互通信。它描述了多个线程并行工作时如何同步。它还详细描述了常量内存和纹理内存。
第4章,CUDA中的高级概念,涵盖了CUDA流和CUDA事件等高级概念。它描述了如何使用CUDA加速排序算法,并着眼于使用CUDA加速简单的图像处理功能。
第5章,带有CUDA支持的OpenCV入门,介绍了在所有操作系统中安装支持CUDA的OpenCV库。它解释了如何使用简单的程序测试此安装。本章探讨了在有和没有CUDA支持的情况下执行的图像处理程序之间的性能比较。
第6章,基本计算机视觉操作使用OpenCV和CUDA,教会读者如何使用OpenCV编写基本的计算机视觉操作,如图像上的像素级操作,过滤和形态操作。
第7章,使用OpenCV和CUDA进行对象检测和跟踪,了解使用OpenCV和CUDA加速一些真实计算机视觉应用程序的步骤
。它描述了用于对象检测的特征检测和描述算法。本章还介绍了使用Haar级联和视频分析技术加速人脸检测,例如用于对象跟踪的背景减法。
第8章,Jetson Tx1开发板简介和在Jetson TX1上安装OpenCV,介绍了Jetson TX1嵌入式平台及其如何用于加速和部署计算机视觉应用程序。它描述了使用Jetpack在Jetson TX1上安装OpenCV for Tegra。
第9章,在Jetson TX1上部署计算机视觉应用程序,包括在Jetson Tx1上部署计算机视觉应用程序。它教会读者如何构建不同的计算机视觉应用程序以及如何将相机与Jetson Tx1连接以用于视频处理应用程序。
第10章,PyCUDA入门,介绍了PyCUDA,它是一个用于GPU加速的Python库。它描述了所有操作系统上的安装过程。
第11章,使用PyCUDA,教会读者如何使用PyCUDA编写程序。它详细描述了从主机到设备和内核执行的数据传输概念。它介绍了如何在PyCUDA中使用数组并开发复杂的算法。
第12章,使用PyCUDA的基本计算机视觉应用程序,介绍使用PyCUDA开发和加速基本计算机视觉应用程序。它描述了颜色空间转换操作,直方图计算和不同的算术运算,作为计算机视觉应用的例子。
源码链接:https://github.com/Culturenotes/Hands-On-GPU-Accelerated-Computer-Vision-with-OpenCV-and-CUDA
三、Python+OpenCV实现滑块验证码
效果展示
核心步骤是提取滑动块目标位置,如下是效果展示:54
目标滑动块定位步骤与演示:
实现步骤:
【1】截取验证图片,颜色通道转换为HSV,取V通道分析
原图:
V通道效果:
B,G,R=cv2.split(img)
hsv_img=cv2.cvtColor(img,cv2.COLOR_BGR2HSV)
H,S,V=cv2.split(hsv_img)
ret1, thres= cv2.threshold(V, 200, 255, cv2.THRESH_BINARY_INV)
cv2.imshow('thres', thres)
【2】二值化 + 形态学处理
二值化效果:
开运算+闭运算效果:
k1=np.ones((5,5), np.uint8)
thres = cv2.morphologyEx(thres, cv2.MORPH_OPEN, k1)#闭运算
cv2.imshow('MORPH_OPEN', thres)
k2=np.ones((5,5), np.uint8)
thres = cv2.morphologyEx(thres, cv2.MORPH_CLOSE, k2)#闭运算
cv2.imshow('MORPH_CLOSE', thres)
【3】轮廓提取 + 宽高/面积比筛选
其他图片测试效果(稳定性验证):
自动验证完整步骤
实现步骤:
【1】通过模板匹配定位箭头位置,作为鼠标滑动起点;
【2】定位模板滑动块位置;
【3】控制鼠标拖动,直到与目标滑动块完全重合;
这里提供两种思路:
① 笔者发现这个网站的起始滑动块x位置都是10,那么可以计算目标滑动块与起始滑动块X坐标差值,控制鼠标移动对应的像素量;
② 截取目标滑动块的ROI位置,实时计算ROI被覆盖后剩余像素数量,当剩余像素数量最小时认为被覆盖完全,松开鼠标。
四、OpenCV测量图像中物体之间的距离
本文来自光头哥哥的博客【Measuring distance between objects in an image with OpenCV】,仅做学习分享。
原文链接:https://www.pyimagesearch.com/2016/04/04/measuring-distance-between-objects-in-an-image-with-opencv/
计算物体之间的距离与计算图像中物体的大小算法思路非常相似——都是从参考对象开始的。我们将使用0.25美分作为我们的参考对象,它的宽度为0.955英寸。
并且我们还将0.25美分总是放在图片最左侧使其容易识别。这样它就满足了我们上面提到的参考对象的两个特征。
我们的目标是找到0.25美分,然后利用0.25美分的尺寸来测量0.25美分硬币与所有其他物体之间的距离。
定义参考对象并计算距离
打开一个新文件,将其命名为distance_between.py,插入以下代码:
# import the necessary packages
from scipy.spatial import distance as dist
from imutils import perspective
from imutils import contours
import numpy as np
import argparse
import imutils
import cv2
def midpoint(ptA, ptB):
return ((ptA[0] + ptB[0]) * 0.5, (ptA[1] + ptB[1]) * 0.5)
# construct the argument parse and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", required=True,
help="path to the input image")
ap.add_argument("-w", "--width", type=float, required=True,
help="width of the left-most object in the image (in inches)")
args = vars(ap.parse_args())
我们这里的代码与上周的代码几乎相同。我们从在第2-8行上导入所需的Python包开始。
第12-17行解析命令行参数。这里我们需要两个参数:——image,它是包含我们想要测量的对象的输入图像的路径,以及——width,为我们参考对象的宽度(单位为英寸)。接下来,我们需要对图像进行预处理:
# load the image, convert it to grayscale, and blur it slightly
image = cv2.imread(args["image"])
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (7, 7), 0)
# perform edge detection, then perform a dilation + erosion to
# close gaps in between object edges
edged = cv2.Canny(gray, 50, 100)
edged = cv2.dilate(edged, None, iteratinotallow=1)
edged = cv2.erode(edged, None, iteratinotallow=1)
# find contours in the edge map
cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
# sort the contours from left-to-right and, then initialize the
# distance colors and reference object
(cnts, _) = contours.sort_contours(cnts)
colors = ((0, 0, 255), (240, 0, 159), (0, 165, 255), (255, 255, 0),
(255, 0, 255))
refObj = None
第2-4行从磁盘加载图像,将其转换为灰度图,然后使用7 x 7内核的高斯滤波器对其进行模糊降噪。
当我们的图像被模糊后,我们应用Canny边缘检测器来检测图像中的边缘,然后进行膨胀+腐蚀来缩小边缘图中的缝隙(第7-9行)。
调用cv2.findContours检测边缘图中对象的轮廓(第11-13行),而第16行从左到右对轮廓进行排序。由于我们知道0.25美分(即参考对象)将始终是图像中最左边,因此从左到右对轮廓进行排序可以确保与参考对象对应的轮廓始终是cnts列表中的第一个。
然后,我们初始化用于绘制距离的colors列表以及refObj变量,该变量将存储参考对象的边界框、质心和pixels-per-metric值(看上一篇就明白pixels-per-metric的具体定义,其实就是参考对象的实际大小(单位英寸)与图片中的宽度(单位为像素)的比值)。
# loop over the contours individually
for c in cnts:
# if the contour is not sufficiently large, ignore it
if cv2.contourArea(c) < 100:
continue
# compute the rotated bounding box of the contour
box = cv2.minAreaRect(c)
box = cv2.cv.BoxPoints(box) if imutils.is_cv2() else cv2.boxPoints(box)
box = np.array(box, dtype="int")
# order the points in the contour such that they appear
# in top-left, top-right, bottom-right, and bottom-left
# order, then draw the outline of the rotated bounding
# box
box = perspective.order_points(box)
# compute the center of the bounding box
cX = np.average(box[:, 0])
cY = np.average(box[:, 1])
在第2行,我们开始对cnts列表中的每个轮廓进行循环。如果轮廓比较小(第4和5行),我们认为是噪声并忽略它。
然后,第7-9行计算当前对象的最小旋转包围框。
第14行上调用order_points函数(此系列第一篇定义的函数)来对矩形框四个顶点以左上角、右上角、右下角和左下角的顺序排列,我们将看到,在计算物体之间的距离时,这一点非常重要。
第16行和第17行通过取边界框在x和y方向上的平均值来计算旋转后的边界框的中心(x, y)坐标。
下一步是校准我们的refObj:
# if this is the first contour we are examining (i.e.,
# the left-most contour), we presume this is the
# reference object
if refObj is None:
# unpack the ordered bounding box, then compute the
# midpoint between the top-left and top-right points,
# followed by the midpoint between the top-right and
# bottom-right
(tl, tr, br, bl) = box
(tlblX, tlblY) = midpoint(tl, bl)
(trbrX, trbrY) = midpoint(tr, br)
# compute the Euclidean distance between the midpoints,
# then construct the reference object
D = dist.euclidean((tlblX, tlblY), (trbrX, trbrY))
refObj = (box, (cX, cY), D / args["width"])
continue
如果refObj为None(第4行),则需要对其进行初始化。
我们首先获取(排序后的)最小旋转边界框坐标,并分别计算四个顶点之间的中点(第10-15行)。
然后计算中点之间的欧氏距离,给出我们的“像素/尺寸”比例,来确定一英寸为多少像素宽度。
最后,我们将refObj实例化为一个3元组,包括:
- 物体对象的最小旋转矩形对象box
- 参考对象的质心。
- 像素/宽度比例,我们将用其来结合物体之间的像素距离来确定物体之间的实际距离。
下一个代码块负责绘制参考对象和当前检查对象的轮廓,然后定义变量refCoords和objCoords,这样(1)最小包围矩阵坐标和(2)质心的(x, y)坐标都包含在同一个数组中:
# draw the contours on the image
orig = image.copy()
cv2.drawContours(orig, [box.astype("int")], -1, (0, 255, 0), 2)
cv2.drawContours(orig, [refObj[0].astype("int")], -1, (0, 255, 0), 2)
# stack the reference coordinates and the object coordinates
# to include the object center
refCoords = np.vstack([refObj[0], refObj[1]])
objCoords = np.vstack([box, (cX, cY)])
现在我们可以开始计算图像中各个物体的质心和质心之间的距离了:
# loop over the original points
for ((xA, yA), (xB, yB), color) in zip(refCoords, objCoords, colors):
# draw circles corresponding to the current points and
# connect them with a line
cv2.circle(orig, (int(xA), int(yA)), 5, color, -1)
cv2.circle(orig, (int(xB), int(yB)), 5, color, -1)
cv2.line(orig, (int(xA), int(yA)), (int(xB), int(yB)),
color, 2)
# compute the Euclidean distance between the coordinates,
# and then convert the distance in pixels to distance in
# units
D = dist.euclidean((xA, yA), (xB, yB)) / refObj[2]
(mX, mY) = midpoint((xA, yA), (xB, yB))
cv2.putText(orig, "{:.1f}in".format(D), (int(mX), int(mY - 10)),
cv2.FONT_HERSHEY_SIMPLEX, 0.55, color, 2)
# show the output image
cv2.imshow("Image", orig)
cv2.waitKey(0)
在第2行,我们开始对图片中物体对象的顶点(x, y)坐标进行循环。
然后我们画一个圆表示我们正在计算距离的当前点坐标,并画一条线连接这些点(第5-7条线)。
然后,第12行计算参考位置和对象位置之间的欧式距离,然后除以“像素/度量”,得到两个对象之间的实际距离(以英寸为单位)。然后在图像上标识出计算的距离(第13-15行)。
距离测量结果
下面是一个GIF动画,演示了我们的程序运行效果:
在每种情况下,我们的脚本都匹配左上(红色)、右上(紫色)、右下(橙色)、左下(蓝绿色)和质心(粉色)坐标,然后计算参考对象和当前对象之间的距离(以英寸为单位)。
注意图像中的两个0.25美分完全平行,这意味着所有五个顶点之间的距离均为6.1英寸。
下面是第二个例子,这次计算的是参考对象和药丸之间的距离:
这个例子可以作为药片分类机器人的输入,自动获取一组药片,并根据它们的大小和与药片容器的距离来组织它们。
最后一个例子计算了我们的参考对象(一张3.5英寸x 2英寸的名片)和一组7英寸的黑胶唱片和信封之间的距离:
五、火焰检测
完整项目源码下载:
https://github.com/mushfiq1998/fire-detection-python-opencv?source=post_page-----e55c8fc6fa54--------------------------------
项目结构:
fireDetection.py
import cv2 # Library for openCV
import threading # Library for threading -- which allows code to run in backend
import playsound # Library for alarm sound
import smtplib # Library for email sending
# To access xml file which includes positive and negative images of fire.
# (Trained images) File is also provided with the code.
fire_cascade = cv2.CascadeClassifier('fire_detection_cascade_model.xml')
vid = cv2.VideoCapture("videos\\fire2.mp4")
runOnce = False # created boolean
# defined function to play alarm post fire detection using threading
def play_alarm_sound_function():
# to play alarm # mp3 audio file is also provided with the code.
playsound.playsound('fire_alarm.mp3',True)
print("Fire alarm end") # to print in consol
# Defined function to send mail post fire detection using threading
def send_mail_function():
recipientmail = "add recipients mail" # recipients mail
recipientmail = recipientmail.lower() # To lower case mail
try:
server = smtplib.SMTP('smtp.gmail.com', 587)
server.ehlo()
server.starttls()
# Senders mail ID and password
server.login("add senders mail", 'add senders password')
# recipients mail with mail message
server.sendmail('add recipients mail', recipientmail, "Warning fire accident has been reported")
# to print in consol to whome mail is sent
print("Alert mail sent sucesfully to {}".format(recipientmail))
server.close() ## To close server
except Exception as e:
print(e) # To print error if any
while(True):
Alarm_Status = False
# Value in ret is True # To read video frame
ret, frame = vid.read()
# To convert frame into gray color
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# to provide frame resolution
fire = fire_cascade.detectMultiScale(frame, 1.2, 5)
## to highlight fire with square
for (x,y,w,h) in fire:
cv2.rectangle(frame,(x-20,y-20),(x+w+20,y+h+20),(255,0,0),2)
roi_gray = gray[y:y+h, x:x+w]
roi_color = frame[y:y+h, x:x+w]
print("Fire alarm initiated")
# To call alarm thread
threading.Thread(target=play_alarm_sound_function).start()
if runOnce == False:
print("Mail send initiated")
# To call alarm thread
threading.Thread(target=send_mail_function).start()
runOnce = True
if runOnce == True:
print("Mail is already sent once")
runOnce = True
cv2.imshow('frame', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
要运行该项目,请完成以下步骤:
创建虚拟环境:
python -m venv myenv
激活虚拟环境:
myenv\Scripts\activate
安装playsound:
pip install playsound
更新pip版本:
安装OpenCV:
pip install opencv-python
运行脚本fireDetection.py:
现在,我们将从网络摄像头捕获视频。
在下图中,我们可以看到系统正在检测打火机火焰绘制边界框的效果:
我们的系统从上述视频中检测到火灾,绘制带有警报的矩形框
fireDetection.py文件中的代码说明:
上面代码的解释
此 Python 代码是使用 OpenCV、线程、声音和电子邮件功能的火灾探测系统的简单示例。以下是它的功能的简单描述:
1. 导入库:代码首先导入必要的库:
cv2:用于图像和视频处理,特别是用于检测火灾。
threading:用于同时运行代码的某些部分(在后台)。
playsound:用于播放报警声音。
smtplib:用于发送电子邮件。
2. 加载训练模型:代码加载预训练的机器学习模型(XML 文件),该模型可以检测图像中的火灾。
3. 设置视频源:设置视频输入源,可以是笔记本电脑内置摄像头,也可以是外接USB 摄像头。该代码当前配置为从名为“fire2.mp4”的文件中读取视频。
4. play_alarm_sound_function()4. 播放报警声音:定义播放报警声音的函数。该函数在后台运行(线程)并播放名为“fire_alarm.mp3”的警报声音文件。
5. 发送电子邮件:send_mail_function()定义了另一个函数来发送电子邮件。它使用 Gmail 的 SMTP 服务器向指定收件人发送有关火灾检测的警告电子邮件。代码中需要提供发件人的电子邮件和密码。
6. 主循环:主循环处理视频的每一帧。它执行以下操作:
- 将帧转换为灰度以便于处理。
- 使用加载的模型检测框架中的火灾。
- 如果检测到火灾,它会用蓝色矩形突出显示该区域。
- 如果第一次检测到火灾(由 控制runOnce),则会触发警报声并使用线程发送电子邮件。警报和电子邮件功能在后台运行。
- 一旦警报和电子邮件被触发一次,系统就不会为后续发生火灾的帧重复此过程。
7. 显示视频:代码显示处理后的帧,并在检测到的火灾周围绘制矩形。视频将一直显示,直到您按“q”键。
简而言之,此代码读取视频帧,在帧中查找火灾,如果检测到火灾,它会播放警报声音并发送电子邮件警报。它使用单独的线程来播放警报和发送电子邮件,因此这些任务不会阻塞主视频处理循环。
请注意,这是一个基本示例,可能需要调整和改进才能形成完整且强大的火灾探测系统。
六、指定目标颜色改变
OpenCV中使用colorChange实现图像中指定目标颜色改变的效果
介绍
colorChange与seamlessClone同属于Seamless Cloning部分,算法均来自下面这篇论文:
https://www.cs.jhu.edu/~misha/Fall07/Papers/Perez03.pdf
百度网盘下载:
链接:https://pan.baidu.com/s/1Ma_9ZF4r0SgNmfygHe3kgQ
提取码:0857
算法解读可参考下面链接:
https://blog.csdn.net/zhaoyin214/article/details/88196575
使用colorChange函数可以轻松将一幅图像中的指定目标颜色改变并尽可能保留其边缘信息,自然融合。函数说明:
参数:
src | 输入8位3通道图像(截取目标的大图) |
mask | 输入8位1或3通道图像(待改变颜色目标掩码区域图像) |
dst | 输出结果图(要求和src相同的大小和类型) |
red_mul | 红色通道乘积因子(建议值0.5~2.5) |
green_mul | 绿色通道乘积因子(建议值0.5~2.5) |
blue_mul | 蓝色通道乘积因子(建议值0.5~2.5) |
效果展示
手动框选左图中的目标,然后调整滑动条动态查看颜色改变效果:
,时长01:59
实现步骤与源码
程序实现步骤:
(1) 使用selectROI函数框选指定目标;
(2) 使用三个滑动条动态改变red_mul,green_mul,blue_mul参数值;
(3) 滑动条回调函数中使用colorChange函数完成颜色改变。
src图:
框选ROI区域设定mask与参数设置(red_mul=1.0, green_mul=1.78, bule_mul=2.35)以及运行结果:
src图:
框选ROI区域设定mask与参数设置(red_mul=1.17, green_mul=0.47, bule_mul=1.23)以及运行结果:
src图:
框选ROI区域设定mask与参数设置(red_mul=0.18, green_mul=1.17, bule_mul=2.35)以及运行结果:
(red_mul=0.17, green_mul=1.18, bule_mul=0)运行结果:
效果见开头效果视频,C++源码如下:
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
Mat src;
int r = 100, g = 100, b = 100;
Rect rect;
void colorChange_Callback(int, void *)
{
Mat dst;
Mat mask = Mat::zeros(src.size(), src.type());
rectangle(mask, rect, Scalar::all(255), -1);
colorChange(src, mask, dst, r/100.0, g/100.0, b/100.0);
imshow("colorChange", dst);
}
int main()
{
src = imread("4.jpg");
rect = selectROI(src, true, false);
namedWindow("colorChange", WINDOW_NORMAL);
createTrackbar("R", "colorChange", &r, 250, colorChange_Callback);
createTrackbar("G", "colorChange", &g, 250, colorChange_Callback);
createTrackbar("B", "colorChange", &b, 250, colorChange_Callback);
colorChange_Callback(0, 0);
waitKey();
return 0;
}
注意:如果希望得到更准确的结果,可以用提取轮廓的方法精确设置mask,这样颜色改变后不会更改其他区域。比如唇色替换,可以先通过人脸关键点提取后,裁剪出嘴唇部分轮廓作为mask,这样结果也会更准确。
七、读取仪表中的指针刻度
如何读取仪表中的指针指向的刻度
解决方法有多种,比如,方案一:模板匹配+边缘检测+霍夫直线检测,方案二:神将网络(CNN)目标定位等,
其中CNN就有点麻烦了,需要一定数量的训练样本,太麻烦,而方案一太普通,最后我采用了方案三,
方案三:模板匹配+k-means+直线拟合
具体做法如下:
首先说一下模板匹配,它是OpenCV自带的一个算法,可以根据一个模板图到目标图上去寻找对应位置,如果模板找的比较好那么效果显著,这里说一下寻找模板的技巧,模板一定要标准、精准且特征明显。
第一次的模板选取如下:
匹配的效果如下:
根据模板选取的原则我们,必须进行两次匹配才能的到精确和更高准确率的结果
第二次的模板如下:
然后在第一次结果的的基础上也就是蓝色矩形框区域进行第二次匹配,结果如下:
下面对上图进行k-means二值化,由于途中的阴影,所以只截取原图的0.6(从中心)作为k-means聚类的样本点,然后将聚类结果应用至上图并重新二值化(聚类结果为2,求中值,根据中值二值化),同时只保留内切圆部分,效果如下:
接下来就是拟合直线,拟合直线我采用旋转虚拟直线法,假设一条直线从右边0度位置顺时针绕中心旋转当它转到指针指向的位置时重合的最多,此时记录下角度,最后根据角度计算刻度值。效果图如下:
最后就读取到了数值:
聚类结果:
[[31.99054054 23.04324324 14.89054054]
[62.69068323 53.56024845 40.05652174]]
重合数量和对应角度:(1566, 158)
对应刻度:36.005082940886126
源码如下:
八、用 OpenCV 在几秒内处理 1万 张图像
如何在几秒内处理 10k 张图像
手动、重复性任务是我们都讨厌的事情,特别是当知道它们可以自动化的时候。想象一下,你需要用相同的裁剪和调整大小操作编辑一堆图像。对于几张图片,你可能只需打开一个图像编辑器并手动执行。但是,如果对数千或数万张图像执行相同的操作呢?让我们看看如何使用Python和OpenCV自动执行这样的图像处理任务,以及如何优化此数据处理管道以在大量数据集上高效运行。
数据集
本文中我们使用录制的海滩随机视频中提取了 10,000 帧,目标是将图像裁剪为围绕中心的正方形宽高比,然后将图像调整为固定尺寸224x224。
先决条件
如果您想继续,请确保安装以下软件包,例如uv 。源码下载链接:
https://github.com/trflorian/image-processing
uv add opencv-python tqdm
数据加载
让我们首先使用OpenCV逐一加载图像。所有图像都在子文件夹中,并将使用pathlib glob 方法查找此文件夹中的所有png文件。为了显示进度,我使用了tqdm库。通过使用sorted方法,我确保路径已排序,并将glob调用返回的生成器转换为列表。这样,tqdm就知道显示进度条的迭代长度。
from pathlib import Path
from tqdm import tqdm
img_paths = Path("images").glob("*.png")
for img_path in tqdm(sorted(img_paths)):
pass
现在我们还可以准备输出目录,并确保它存在。这是我们处理后的图像将存储的地方。
output_path = Path("output")
output_path.mkdir(exist_ok=True, parents=True)
图像处理
为了处理图像,我们定义一个函数。它将输入和输出图像路径作为参数。
def process_image(input_path: Path, output_path: Path) -> None:
"""
Image processing pipeline:
- Center crop to square aspect ratio
- Resize to 224x224
Args:
input_path (Path): Path to input image
output_path (Path): Path to save processed image
"""
为了实现此功能,我们首先需要用OpenCV加载图像。确保在文件开头导入 opencv 包。
...
import cv2
def process_image(input_path: Path, output_path: Path) -> None:
...
# Read image
img = cv2.imread(str(input_path))
要裁剪图像,我们可以直接在 x 轴上对图像数组进行切片。请记住,OpenCV 图像数组以YXC形状存储:X/Y是从左上角开始的图像的 2D 轴,C是颜色通道。因此 x 轴是图像的第二个索引。为简单起见,我假设图像为横向格式,其宽度 > 高度。
height, width, _ = img.shape
img = img[:, (width - height) // 2 : (width + height) // 2, :]
要调整图像大小,我们可以简单地使用OpenCV 中的resize函数。如果我们不指定插值方法,它将使用双线性插值,这对于这个项目来说很好。
target_size = 224
img = cv2.resize(img, (target_size, target_size))
最后,使用imwrite函数将图像保存到输出文件。
cv2.imwrite(str(output_path), img)
现在我们可以简单地在图像路径循环中调用 process_image 函数。
for img_path in tqdm(sorted(img_paths)):
process_image(input_path=img_path, output_path=output_path / img_path.name)
如果我在我的计算机上运行这个程序,处理 10,000 张图像需要一分钟多一点的时间。
4%|█████▏ | 441/10000 [00:02<01:01,154.34it/s]
现在,虽然对于这个数据集大小,等待一分钟仍然是可行的,但对于 10 倍大的数据集,您已经等待了 10 分钟。我们可以通过并行化此过程做得更好。如果您在运行当前程序时查看资源使用情况,您会注意到只有一个核心的利用率为 100%。该程序仅使用一个核心!
跨多核并行
为了让我们的程序使用更多可用内核,我们需要使用 Python 中的一项功能,称为多处理。由于全局解释器锁 (GIL),单个 Python 进程无法真正并行运行任务(除非禁用 GIL,这可以在 Python≥3.13 中完成)。我们需要做的是生成由主 Python 程序管理的多个 Python 进程(因此称为多处理)。
为了实现这一点,我们可以使用内置的 Python 模块multiprocessing和parallel。理论上,我们可以手动生成 Python 进程,同时确保提交的进程数不超过我们的核心数。由于我们的进程受 CPU 限制,因此增加进程并不会带来速度提升,因为它们必须等待。事实上,在某一时刻,进程间切换的开销会超过并行化的优势。
为了管理 Python 进程,我们可以使用ProcessPoolExecutor。这将保留一个 Python 进程池,而不是为每个提交的任务完全销毁并重新启动每个进程。默认情况下,它将使用与可用逻辑 CPU数量一样多的池,该数量是从os.process_cpu_count()检索的。因此,默认情况下,它将为我的 CPU 的每个核心生成一个进程,在我的情况下是 20 个。您还可以提供max_workers参数来指定要在池中生成的进程数。
from concurrent.futures import ProcessPoolExecutor
...
output_paths = [output_path / img_path.name for img_path in img_paths]
with ProcessPoolExecutor() as executor:
all_processes = executor.map(
process_image,
img_paths,
output_paths,
)
for _ in tqdm(all_processes, total=len(img_paths)):
pass
我们使用上下文管理器(with语句)创建进程池执行器,这将确保即使在执行过程中发生异常,进程也会被清理。然后我们使用map函数为每个输入img_paths和output_paths创建一个进程。最后通过使用tqdm包装all_processes的迭代,我们可以获得已完成进程的进度条。
18% |█████|1760/10000[00:00<00:04,1857.23it/s]
现在,如果您运行该程序并再次检查 CPU 利用率,您将看到所有核心都已使用!进度条还显示了我们的迭代速度如何提高。
比较
为了快速检查完整性,我绘制了使用不同数量的并行化处理 1000 张图像的时间图,从单个工作器场景开始,然后将工作器数量增加到我机器内核数量的两倍。下图表明最佳情况接近 CPU 内核数量。从 1 个工作器到多个工作器,性能急剧上升,而当工作器数量超过 CPU 内核数量时,性能略有下降。
完整代码下载:
https://github.com/trflorian/image-processing