canvas画布会黑屏吗_python开发之HighGUI上位机开发(三)——项目实战:可变色画布和自制绘图板...

 

从最开始带你从创建一个特定灰度的灰度图, 到终端输入灰度的值生成特定灰度的图片, 然后我们引入了一个新的HighGui组件:Trackbar 。你还会接触到回调函数(callback)这个概念, 用于响应Trackbar 拖动带来的影响。你可以拖动滑条, 动态的看到色块颜色的改变。最后做出功能丰富的绘图板出来。

keywords:highgui 可变色 画布 RGB Trackbar 绘图板 Painter 鼠标事件监听

01

项目实战-可变色画布

1. 创建统一灰度画布的函数

我们创建一个函数createGrayscaleCanvas用于创建灰度图的画布.

import numpy as npimport cv2# 初始化灰度图的画布def createGrayscaleCanvas(width, height, color=255):    canvas = np.ones((height, width), dtype="uint8")    canvas[:] = color    return canvas# 创建一个颜色为125的灰度图canvas = createGrayscaleCanvas(500, 500, color=125)# 展示画布cv2.imshow("canvas", canvas)# 中断cv2.waitKey(0)# 关闭窗口cv2.destroyAllWindows()

canvas画布会黑屏吗_python开发之HighGUI上位机开发(三)——项目实战:可变色画布和自制绘图板..._第1张图片

2. 终端输入灰度 生成灰度图

接下来, 如果想通过终端输入一个特定的数值,然后生成对应值的灰度图,那我们如何来实现呢?

在python中, 类似C语言中的scanf语句的函数是inputinput 中传入的字符串是提示。

读入字符串赋值给gray_value. 注意这里读入的是字符串, 不是数值。

gvalue_str  = input("请输入灰度值: ")

如果我们想得到整数的数值的话, 需要对其进行强制类型转换. 使用int() 函数.

gvalue = int(gvalue_str)

接下来, 我们还要判断, 数值范围是否合法, 在[0, 255] 区间之内.

我们使用函数is_gvalue_legal来判断灰度值是否合法:

# 判断灰度值是否合法
def is_gvalue_legal(gvalue):

return not (gvalue < 0 or gvalue > 255)

如果要写的完整一点的话,版本就是这样的:

# 判断灰度值是否合法
def is_gvalue_legal_balabala(gvalue):
if gvalue < 0 or gvalue > 255:
return False
else:
return True

读入gvalue , 如果符合要求的话, 就生成对应的背景. 不合法就要求重新输入.

#  读入灰度值
def read_gvalue():
# 是否读取成功
read_done = False
gvalue = None

while not read_done:

gvalue_str = input("请输入灰度值: ")
gvalue = int(gvalue_str)
read_done = is_gvalue_legal(gvalue)
# 添加一个温馨小提示
if not read_done:
print("温馨提示, 数值范围越界, 灰度图取值范围在0到255区间")
return gvalue

完整版本的程序见:

import numpy as np
import cv2

# 初始化灰度图的画布
def createGrayscaleCanvas(width, height, color=255):
canvas = np.ones((height, width), dtype="uint8")
canvas[:] = color
return canvas

# 判断灰度值是否合法
def is_gvalue_legal(gvalue):

return not (gvalue < 0 or gvalue > 255)

# 读入灰度值
# 如果符合要求的话, 就生成对应的背景. 不合法就要求重新输入.
def read_gvalue():
# 是否读取成功
read_done = False
gvalue = None

while not read_done:

gvalue_str = input("请输入灰度值: ")
gvalue = int(gvalue_str)
read_done = is_gvalue_legal(gvalue)

if not read_done:
print("温馨提示, 数值范围越界, 灰度图取值范围在0到255区间")
return gvalue

gvalue = read_gvalue()

canvas = createGrayscaleCanvas(500, 500, color=gvalue)

cv2.imshow("canvas", canvas)

print("按任意按键结束程序")
cv2.waitKey(0)

cv2.destroyAllWindows()

效果展示:


canvas画布会黑屏吗_python开发之HighGUI上位机开发(三)——项目实战:可变色画布和自制绘图板..._第2张图片

3. 创建滑条-ceateTrackBar

用终端的方式, 也有它的弊端, 主要是我没办法从交互界面上去规范它输入的值.

于是我就用到了我们的TrackBar 组件.

首先我们需要创建一个Trackbar , 调用createTrackbar 这个函数

cv2.createTrackbar(trackbar_name,window_name,min_value,max_value,callback_func)

依次传入的函数

  • trackbar_name 滑条的名称,获取这个滑条的数值也是通过名称

  • window_name 滑条所在窗口 (window) 的名称

  • min_value 滑条最小值

  • max_value 滑条最大值

  • callback_func 回调函数,这个参数其实类似C语言中的函数指针,我传入的是函数名称,每次滑条被拖动的时候,都会执行这个函数.

例如:

# 这个nothing的意思就是啥也不做。
def nothing(x):
pass
cv2.createTrackbar('gray_value','image',0,255,nothing)

这里的nothing(x) , 被传入的x 实际上是滑条的当前取值。

你也可以改成这样, 看一下x 的值。

# 这个nothing的意思就是啥也不做。
def nothing(x):
print(x)
cv2.createTrackbar('gray_value','image',0,255,nothing)

x 是我命名的值, 你可以命名为任意名称。

4. 灰度图调色板 - V1

这里, 我们来演示, 不用回调函数的解决方法。

定时每隔1ms刷新画面, 这种方式比较低效, 即便值没有被修改1s也会修改1000次。

'''
滑块调色板 - v1 比较傻的版本
'''
import cv2
import numpy as np

# 初始化灰度图的画布
def createGrayscaleCanvas(width, height, color=255):
canvas = np.ones((height, width), dtype="uint8")
canvas[:] = color
return canvas

cv2.namedWindow('image')

# 函数原型
# createTrackbar(trackbarName, windowName, value, count, onChange) -> None
# 解释
# 在window‘iamge’ 上创建一个滑动条,起名为Channel_XXX, 设定滑动范围为0-255,
# onChange事件回调 啥也不做
def nothing(x):
pass

cv2.createTrackbar('gray_value','image',0,255,nothing)

print("进入Grayscale滑块实验, 键盘按e退出程序")

img = None

# 每隔1ms检查更新一次。
while(True):

# 程序跳出判断 最多等待1毫秒
k = cv2.waitKey(1)
# 如果key是e键就退出程序
if k == ord('e'):
break

# 获取当前滑条的值
gvalue = cv2.getTrackbarPos('gray_value','image')
# 创建新的画布
img = createGrayscaleCanvas(500, 500, color=gvalue)
# 显示更新后的图片
cv2.imshow('image',img)

cv2.destroyAllWindows()

效果展示:

canvas画布会黑屏吗_python开发之HighGUI上位机开发(三)——项目实战:可变色画布和自制绘图板..._第3张图片

5. 灰度图调色板 - V2

我们首先创建一个名字叫做gray_value 的trackbar。这个trackbarimage 窗口上。

最小取值是0, 最大取值是255, 修改时候的回调函数是updateImg

cv2.createTrackbar('gray_value','image',0,255,updateImg)

那我们来看一下updateImg 图像更新的函数。每次trackbar修改的时候, 就会给函数updateImg 传入当前的值gvalue

然后我们创建一个新的图片, 并在image 窗口展示。

# 更新画布
def updateImg(gvalue):

img = createGrayscaleCanvas(500, 500, color=gvalue)
# 显示更新后的图片
cv2.imshow('image',img)

完整的程序:

'''
滑块调色板 - v2 回调函数
'''
import cv2
import numpy as np

# 初始化灰度图的画布
def createGrayscaleCanvas(width, height, color=255):
canvas = np.ones((height, width), dtype="uint8")
canvas[:] = color
return canvas

# 更新画布
def updateImg(gvalue):

img = createGrayscaleCanvas(500, 500, color=gvalue)
# 显示更新后的图片
cv2.imshow('image',img)

cv2.namedWindow('image')
# 初始化画布
updateImg(0)

cv2.createTrackbar('gray_value','image',0,255,updateImg)

print("进入Grayscale滑块实验, 键盘按e退出程序")

img = None
# 接收按键事件, 判断是否退出
while cv2.waitKey(0) != ord('e'):
continue

cv2.destroyAllWindows()

效果展示:

canvas画布会黑屏吗_python开发之HighGUI上位机开发(三)——项目实战:可变色画布和自制绘图板..._第4张图片

6. 自制:RGB三通道的带Trackbar的调色板

看到这里, 相信你已经掌握了如何创建画布与HighGUI组件中的Trackbar的使用。接下来就是如何创建一个可以调RGB三通道的调色板。

思路:你需要创建三个TrackBar,分别记录三个通道的值。然后你拖动TrackBar触发更新当前color的事件,接下来,重新渲染画布。

我这提供一个代码模板:可以自己尝试一些如何自制:

'''
滑块调色板 - V2 利用回调更新窗口图像
'''

import cv2
import numpy as np

# 创建一个空白画布
canvas = np.zeros((300,512,3), np.uint8)
# 色块的颜色
color = (0, 0, 0)

# 更新图像,并且刷新windows
def updateImage():
'''
请填入你的代码
'''

# 更新颜色
def updateColor(x):
'''
请填入代码
'''

cv2.namedWindow('image')

# 函数原型
# createTrackbar(trackbarName, windowName, value, count, onChange) -> None
# 解释
# 在window‘iamge’ 上创建一个滑动条,起名为Channel_XXX, 设定滑动范围为0-255,
# onChange事件回调 啥也不做
cv2.createTrackbar('Channel_Red','image',0,255,updateColor)
cv2.createTrackbar('Channel_Green','image',0,255,updateColor)
cv2.createTrackbar('Channel_Blue','image',0,255,updateColor)

print("进入RGB滑块实验, 键盘按e退出程序")

# 首次初始化窗口的色块
# 后面的更新 都是由getTrackbarPos产生变化而触发
updateImage()

while cv2.waitKey(0) != ord('e'):
continue

cv2.destroyAllWindows()

02

项目实战-自制绘图板

1. 实战:绘图板v1-圆形点点

我们首先设定一个鼠标回调事件,我们需要指定窗口名称windowName , 只在这个指定窗口下触发事件.

然后我们需要指定一下,当Mouse事件触发时, 对应相应的回调函数onMouse

# 设置鼠标事件回调
cv2.setMouseCallback(windowName,onMouse)

使用举例

# 设置鼠标事件回调
cv2.setMouseCallback('image',draw_circle)

当每次鼠标事件产生的时候,例如鼠标移动鼠标点击等,就会触发draw_circle这个函数。

完整代码:

'''
鼠标 每次双击,触发回调函数, 在点击处绘制一个圆圈
'''
import cv2
import numpy as np

# 鼠标回调函数
# x, y 都是相对于窗口内的图像的位置

def draw_circle(event,x,y,flags,param):
# 判断事件是否为 Left Button Double Clicck
if event == cv2.EVENT_LBUTTONDBLCLK:
cv2.circle(img,(x,y),20,(255,0,0),-1)

# 创建一个黑色图像,并绑定窗口和鼠标回调函数
img = np.zeros((512,512,3), np.uint8)
cv2.namedWindow('image')
# 设置鼠标事件回调
cv2.setMouseCallback('image',draw_circle)

while(True):
cv2.imshow('image',img)
if cv2.waitKey(20) == ord('q'):
break
cv2.destroyAllWindows()

# 保存图片
cv2.imwrite("MousePaint01.png", img)

(0,0,255)画红点:

canvas画布会黑屏吗_python开发之HighGUI上位机开发(三)——项目实战:可变色画布和自制绘图板..._第5张图片

(255,0,0)画蓝点:

canvas画布会黑屏吗_python开发之HighGUI上位机开发(三)——项目实战:可变色画布和自制绘图板..._第6张图片

2. 实战:绘图板v2-线条绘制

有了单个圆圈的绘制, 我们想一下, 如何才能绘制一条曲线呢?

如果我移动鼠标的时候,一直绘制圆圈, 鼠标慢慢移动,这个轨迹不就是一个有宽度的曲线嘛~~

关键点在于, 我们需要判断,鼠标是否按下, 以此来判定是否需要绘制图片。

2.1 绘图板v2源码

我们先来看一下,不用flags参数我们需要如何实现。

我们使用isMouseLBDown 这个布尔值,记录当前鼠标的状态。

'''
鼠标按下绘制线条
'''
import cv2
import numpy as np

# 鼠标回调函数
# x, y 都是相对于窗口内的图像的位置
isMouseLBDown = False

def draw_circle(event,x,y,flags,param):
# 判断事件是否为 Left Button Double Clicck
print(event)
global isMouseLBDown

if event == cv2.EVENT_LBUTTONDOWN:
# 检测到鼠标左键按下
print("mouse down")
isMouseLBDown = True
cv2.circle(img,(x,y),5,(255,0,0),-1)
elif event == cv2.EVENT_LBUTTONUP:
# 检测到鼠标左键抬起
isMouseLBDown = False
print("mouse up")
elif event == cv2.EVENT_MOUSEMOVE:
if isMouseLBDown:
print("drawing")
cv2.circle(img,(x,y),5,(255,0,0),-1)

# 创建一个黑色图像,并绑定窗口和鼠标回调函数
img = np.zeros((512,512,3), np.uint8)
cv2.namedWindow('image')
# 设置鼠标事件回调
cv2.setMouseCallback('image',draw_circle)

while(True):
cv2.imshow('image',img)
if cv2.waitKey(1) == ord('q'):
break
cv2.destroyAllWindows()

cv2.imwrite("MousePaint02.png", img)

当然, 我们有了flags参数, EVENT_FLAG_LBUTTON 本身就可以判定鼠标左键是否按下, 所以改写一下:

'''
鼠标按下绘制线条
'''
import cv2
import numpy as np

# 鼠标回调函数
# x, y 都是相对于窗口内的图像的位置

def draw_circle(event,x,y,flags,param):

if flags == cv2.EVENT_FLAG_LBUTTON:
cv2.circle(img,(x,y),5,(255,0,0),-1)

# 创建一个黑色图像,并绑定窗口和鼠标回调函数
img = np.zeros((512,512,3), np.uint8)
cv2.namedWindow('image')
# 设置鼠标事件回调
cv2.setMouseCallback('image',draw_circle)

while(True):
cv2.imshow('image',img)
if cv2.waitKey(1) == ord('q'):
break
cv2.destroyAllWindows()

cv2.imwrite("MousePaint03.png", img)

看, 是不是通过flags参数, 大大减少了你的代码复杂度

我猜你的内心独白可能是这样的:“”哦,原来flags是这么用的。“

看我灵魂画手:

canvas画布会黑屏吗_python开发之HighGUI上位机开发(三)——项目实战:可变色画布和自制绘图板..._第7张图片

2.2 存在问题

  1. 如果鼠标运动快了, 就会变成散点, 不连续。

  2. 只支持单个颜色, 不美丽。

  3. 不支持调节笔刷粗细。

3. 实战:绘图板v3-彩色线条笔触调节

3.1 绘图板v3源码

'''
鼠标按下绘制线条 可以调整线条粗细 变换颜色
线条也更流畅
'''
import cv2
import numpy as np

isMouseLBDown = False # 判断鼠标是否摁下的标志
circleColor = (0, 0, 0) # 画笔的颜色
circleRadius = 5 # 画笔的粗细
lastPoint = (0, 0)

# 鼠标回调函数 绘制图像
# x, y 都是相对于窗口内的图像的位置
def draw_circle(event,x,y,flags,param):
# 判断事件是否为 Left Button Double Clicck
# print(event)
global img
global isMouseLBDown
global color
global lastPoint

if event == cv2.EVENT_LBUTTONDOWN:
# 检测到鼠标左键按下
# print("mouse down")
isMouseLBDown = True
cv2.circle(img,(x,y), int(circleRadius/2), circleColor,-1)
lastPoint = (x, y)
elif event == cv2.EVENT_LBUTTONUP:
# 检测到鼠标左键抬起
isMouseLBDown = False
# print("mouse up")
elif event == cv2.EVENT_MOUSEMOVE:
if isMouseLBDown:
# print("drawing")
cv2.line(img, pt1=lastPoint, pt2=(x, y), color=circleColor, thickness=circleRadius)
lastPoint = (x, y)
# cv2.circle(img,(x,y), circleRadius, circleColor,-1)

# 更新颜色
def updateCircleColor(x):
global circleColor
global colorPreviewImg

r = cv2.getTrackbarPos('Channel_Red','image')
g = cv2.getTrackbarPos('Channel_Green','image')
b = cv2.getTrackbarPos('Channel_Blue','image')

circleColor = (b, g, r)
colorPreviewImg[:] = circleColor

# 更新画笔的宽度
def updateCircleRadius(x):
global circleRadius
global radiusPreview

circleRadius = cv2.getTrackbarPos('Circle_Radius', 'image')
# 重置画布
radiusPreview[:] = (255, 255, 255)
# 绘制圆形
cv2.circle(radiusPreview, center=(50, 50), radius=int(circleRadius / 2), color=(0, 0, 0), thickness=-1)

# 创建一个画布,并绑定窗口和鼠标回调函数

img = np.ones((512,512,3), np.uint8)
img[:] = (255, 255, 255)

# 用于预览画笔的颜色
colorPreviewImg = np.ones((100, 100, 3), np.uint8)
colorPreviewImg[:] = (0, 0, 0)
# 用于预览画笔的粗细
radiusPreview = np.ones((100, 100, 3), np.uint8)
radiusPreview[:] = (255, 255, 255)

# 用于展示绘图区域的窗口
cv2.namedWindow('image')
# 用于预览颜色的窗口
cv2.namedWindow('colorPreview')
# 用于预览画笔宽度的窗口
cv2.namedWindow('radiusPreview')

# 在window‘image’ 上创建一个滑动条,起名为Channel_XXX, 设定滑动范围为0-255,
# onChange事件回调 啥也不做
cv2.createTrackbar('Channel_Red','image',0,255,updateCircleColor)
cv2.createTrackbar('Channel_Green','image',0,255,updateCircleColor)
cv2.createTrackbar('Channel_Blue','image',0,255,updateCircleColor)

cv2.createTrackbar('Circle_Radius','image',1,20,updateCircleRadius)
# 设置鼠标事件回调
cv2.setMouseCallback('image',draw_circle)

while(True):
cv2.imshow('colorPreview', colorPreviewImg)
cv2.imshow('radiusPreview', radiusPreview)
cv2.imshow('image',img)
if cv2.waitKey(1) == ord('q'):
break

cv2.destroyAllWindows()
cv2.imwrite("MousePaint04.png", img)

3.2 博主的灵魂画作

canvas画布会黑屏吗_python开发之HighGUI上位机开发(三)——项目实战:可变色画布和自制绘图板..._第8张图片

03

备注

  1. 创作不易,各位老铁来个三连击鼓励鼓励一下博主啥。

  2. 博文中有自己动手去做的一个小项目,大家可以自己尝试的去做,也不能全靠开源,是吧,还是需要自己多多动手,将bug一个个扫除,fighting。。。。。

f32e9590c536d679c8880a10c098efee.pngf32e9590c536d679c8880a10c098efee.pngf32e9590c536d679c8880a10c098efee.pngf32e9590c536d679c8880a10c098efee.png欢迎各位小可爱们关注公众号f32e9590c536d679c8880a10c098efee.pngf32e9590c536d679c8880a10c098efee.pngf32e9590c536d679c8880a10c098efee.pngf32e9590c536d679c8880a10c098efee.png

canvas画布会黑屏吗_python开发之HighGUI上位机开发(三)——项目实战:可变色画布和自制绘图板..._第9张图片

你可能感兴趣的:(canvas画布会黑屏吗)