- title: 虚拟画笔 - 基于OpenCV
- date: 2022-02-27
- categories: - python - OpenCV
- tags: [OpenCV, 虚拟画笔,计算机视觉]
- index_img: https://pic1.zhimg.com/v2-32c4194eadd450868842042b010d3efd_720w.jpg?source=172ae18bt
本项目用到的核心技术是颜色检查以及轮廓/形状检查
对于这个项目,我们首先需要找到我们摄像头画面中的颜色,将RGB通道转换为HSV颜色模型并获取图像的轮廓。然后我们可以在图像轮廓中心放置绘制点,从而进行绘制。不论是哪一种颜色,都可以找到其轮廓,并放置绘制点进行图案的绘制。
因此, 我们首先需要的是通过OpenCV库函数获取连接笔记本的摄像头,并将获取的画面进行三通道分离,从而检测出特定颜色的轮廓.
获取摄像头画面的代码
cap = cv2.VideoCapture(0) # 这里将视频的路径填入摄像头ID即可, 0使用默认摄像头(笔记本/内置)
cap.set(3,640) #宽, 编号为3 3:宽度, 4:高度 10:亮度, 11:对比度
cap.set(4, 480) #高, 编号为4
cap.set(10,100) #亮度100, 编号为10
while True:
success, img = cap.read()
cv2.imshow("Video", img)
if(cv2.waitKey(1) & 0xFF == ord('q')):
break
获取画面之后,接下来我们要做的便是找到画面中特定的颜色(期望识别的颜色,通过设置操纵杆自己调)
定义一个函数来找到特定的颜色
def findColor(img):
imgHSV = cv2.cvtColor(img,cv2.COLOR_BGR2HSV)
lower = np.array([h_min,s_min,v_min])
upper = np.array([h_max,s_max,v_max])
mask = cv2.inRange(imgHSV,lower,upper)
cv2.imshow("img",mask)
这里我们可以定义一个数组来存放不同颜色的HSV值以及灰度。再改写并调用此函数,从而可以实现多个颜色的画笔进行绘制。
首先通过颜色检测来获取不同的颜色HSV值:
import cv2
import numpy as np
frameWidth = 640*0.5
frameHeight = 480*0.5
cap = cv2.VideoCapture(0)
cap.set(3, frameWidth)
cap.set(4, frameHeight)
cap.set(10,150)
def empty(a):
pass
cv2.namedWindow("HSV") ########## 初始化
cv2.resizeWindow("HSV",640,240)
cv2.createTrackbar("HUE Min","HSV",0,179,empty)
cv2.createTrackbar("SAT Min","HSV",0,255,empty)
cv2.createTrackbar("VALUE Min","HSV",0,255,empty)
cv2.createTrackbar("HUE Max","HSV",179,179,empty)
cv2.createTrackbar("SAT Max","HSV",255,255,empty)
cv2.createTrackbar("VALUE Max","HSV",255,255,empty)
while True:
o, img = cap.read()
imgHsv = cv2.cvtColor(img,cv2.COLOR_BGR2HSV)
# 添加数值杆, 让希望的颜色变为白色(以所期望的颜色作为笔头) 颜色越鲜艳越好
h_min = cv2.getTrackbarPos("HUE Min","HSV")
h_max = cv2.getTrackbarPos("HUE Max", "HSV")
s_min = cv2.getTrackbarPos("SAT Min", "HSV")
s_max = cv2.getTrackbarPos("SAT Max", "HSV")
v_min = cv2.getTrackbarPos("VALUE Min", "HSV")
v_max = cv2.getTrackbarPos("VALUE Max", "HSV")
print(h_min)
lower = np.array([h_min,s_min,v_min])
upper = np.array([h_max,s_max,v_max])
mask = cv2.inRange(imgHsv,lower,upper)
result = cv2.bitwise_and(img,img, mask = mask)
mask = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR)
hStack = np.hstack([img,mask,result])
#cv2.imshow('Original', img)
#cv2.imshow('HSV Color Space', imgHsv)
#cv2.imshow('Mask', mask)
#cv2.imshow('Result', result)
cv2.imshow('Horizontal Stacking', hStack)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
以上代码运行后就可以通过调节HSV操作杆来识别镜头中的颜色(期望识别的颜色)
如果颜色识别很多噪点,是因为光线太暗。也不要在有颜色的灯光下进行识别,否则只能在调节的时候所处的环境下才能够成功识别。当然,最好是在特定的灯光下,如果是变化的光线比如白天,调节得到的值下次用还得调(亲身经历)
识别好颜色后, 通过记录操作杆的数值,并将其放入myColors
数组中,进行后续操作。
myColors = [[30,108,114,179,255,255] # 绿色
] # 颜色数组,如果有其他颜色,可以添加
# 画笔的颜色
myColorValues = [[51,153,255], ##BGR not RGB
[255,0,255],
[0,255,0]]
通过获取到特定的颜色HSV值,接下来可以进行颜色检测以及轮廓的获取了
注意:为了固定识别颜色,这里是通过获取到的HSV值,来识别新开镜头中的颜色,从而对其轮廓进行识别,读者切勿混淆上述手动获取的值和接下来的自动检测
# 定义一个检测颜色的函数
def findColor(img,myColors,myColorValues):
imgHSV = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
count = 0 # 用count来记录 当前颜色
newPoints = []
# 循环检测myColor数组中的颜色
for color in myColors:
lower = np.array(color[0:3])
upper = np.array(color[3:6])
mask = cv2.inRange(imgHSV,lower,upper)
x, y = getContours(mask) # 获取轮廓,然后得到中心最高点
cv2.circle(imgResult,(x,y),10, myColorValues[count], cv2.FILLED) # 绘制圆点
if x!= 0 and y != 0:
newPoints.append([x,y,count])# 这种条件下添加到我们的点集
count += 1
#cv2.imshow(str(color[0]),mask)
return newPoints
# 定义一个函数来获取轮廓
def getContours(img):
contours, hierarchy = cv2.findContours(img,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE) # 检索获取极端的外部轮廓
x,y,w,h = 0,0,0,0
for cnt in contours: # 遍历每一个图形
area = cv2.contourArea(cnt)
# print(area)
#给像素点大于500的绘制轮廓
if area>500: # 500像素
# cv2.drawContours(imgResult,cnt,-1,(255,0,0),3) # 蓝色, 厚度3 绘制轮廓
peri = cv2.arcLength(cnt,True)#求周长
# print(peri)
approx = cv2.approxPolyDP(cnt,0.02*peri,True) # 拟合计算拐点 0.02分辨率
# print(len(approx))
# objCor = len(approx)
# #检测边框
x, y, w, h = cv2.boundingRect(approx) # x,y是起点, w,h 是宽高
return x+w//2,y # 返回中心点的最高点
获取轮廓之后,可以根据实际情况设置特定的一个点来作为笔尖。比如这里可以设置矩形的中心点来作为虚拟画笔的笔尖。为了便于处理,我设置的是图案的顶点作为笔尖
总所周知,任意的曲线都可以看作是无数的点连接而成。因此在获得图像中的一个特殊点之后,便可以将这个点记录到一个数组中,通过不断绘制这个数组中的点,就可以达到绘制线条的效果,从而实现虚拟画笔的效果
# 定义绘制画布
def drawOnCanvas(myPoints,myColorValues):
for point in myPoints:
cv2.circle(imgResult, (point[0], point[1]), 10, myColorValues[point[2]], cv2.FILLED) # 绘制圆点选择画笔颜色
while True:
success, img = cap.read()
img = cv2.flip(img,240)
imgResult = img.copy()
newPoints = findColor(img,myColors, myColorValues)
if len(newPoints) != 0: # 拆分后放入mypints
for newP in newPoints:
myPoints.append(newP)
if len(myPoints) != 0:
drawOnCanvas(myPoints,myColorValues)
cv2.imshow("Result", imgResult)
if(cv2.waitKey(1) & 0xFF == ord('q')):
break
通过以上的操作,虽然能够实现虚拟画笔的效果,但是仍然有许多不足之处。比如绘制点的频率还没有调节,当画笔移动速度过快,将会导致线条不连续,形成一个个小点。比如下述结果:
此外,只要画笔出现在镜头中,就能够绘制。这样会导致控制何时停何时开始,让绘制图形的操作变得难于控制。并且能够识别多个颜色点,这就导致同意画面能够有多个画笔。因此后续还应该改进这些缺陷,让画笔能够更加人性化。
cap = cv2.VideoCapture(0) # 这里将视频的路径填入摄像头ID即可, 0使用默认摄像头(笔记本)
cap.set(3,640) #宽, 编号为3 3:宽度, 4:高度 10:亮度, 11:对比度
cap.set(4, 480) #高, 编号为4
cap.set(10,150) #亮度100, 编号为10
# 通过colorPicker.py 找出颜色
# 下面是蓝色,紫色 [5,107,0,19,255,255],黄色,
# myColors = [ [0,103,118,120,255,255], # 绿色
# [133,56,0,159,156,255],
# [57,76,0,100,255,255]] # 这些是需要检测的颜色
myColors = [[30,108,114,179,255,255]]
# 下面是画笔的颜色
myColorValues = [[51,153,255], ##BGR not RGB
[255,0,255],
[0,255,0]]
#
myPoints = [] ## [x,y,colorId]
# 定义一个检测颜色的函数
def findColor(img,myColors,myColorValues):
imgHSV = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
count = 0 # 用count来记录 当前颜色
newPoints = []
# 循环检测mycolor中的颜色数值HSV
for color in myColors:
lower = np.array(color[0:3])
upper = np.array(color[3:6])
mask = cv2.inRange(imgHSV,lower,upper)
x, y = getContours(mask) # 获取轮廓,然后得到中心最高点
cv2.circle(imgResult,(x,y),5, myColorValues[count], cv2.FILLED) # 绘制圆点
if x!= 0 and y != 0:
newPoints.append([x,y,count])# 这种条件下添加到我们的点集
count += 1
#cv2.imshow(str(color[0]),mask)
return newPoints
# 定义一个函数来获取轮廓
def getContours(img):
contours, hierarchy = cv2.findContours(img,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE) # 检索获取极端的外部轮廓
x,y,w,h = 0,0,0,0
for cnt in contours: # 遍历每一个图形
area = cv2.contourArea(cnt)
# print(area)
#给像素点大于500的绘制轮廓
if area>500: # 500像素
# cv2.drawContours(imgResult,cnt,-1,(255,0,0),3) # 蓝色, 厚度3 绘制轮廓
peri = cv2.arcLength(cnt,True)#求周长
# print(peri)
approx = cv2.approxPolyDP(cnt,0.02*peri,True) # 拟合计算拐点 0.02分辨率
# print(len(approx))
# objCor = len(approx)
# #检测边框
x, y, w, h = cv2.boundingRect(approx) # x,y是起点, w,h 是宽高
return x+w//2,y # 返回中心点的最高点
# 定义绘制画布
def drawOnCanvas(myPoints,myColorValues):
for point in myPoints:
cv2.circle(imgResult, (point[0], point[1]), 10, myColorValues[point[2]], cv2.FILLED) # 绘制圆点
while True:
success, img = cap.read()
img = cv2.flip(img,240)
imgResult = img.copy()
newPoints = findColor(img,myColors, myColorValues)
if len(newPoints) != 0: # 拆分后放入mypints
for newP in newPoints:
myPoints.append(newP)
if len(myPoints) != 0:
drawOnCanvas(myPoints,myColorValues)
cv2.imshow("Result", imgResult)
if(cv2.waitKey(1) & 0xFF == ord('q')):
break
以上便是本文的全部内容,希望在以后的学习中还能够再不断完善这个项目。