python开发之HighGUI上位机开发(可变色画布和自制绘图板)
字数可能有点多,但都是干货,请慢慢耐心学习观看,相信你肯定会有所收获
从最开始带你从创建一个特定灰度的灰度图, 到终端输入灰度的值生成特定灰度的图片, 然后我们引入了一个新的HighGui组件:Trackbar 。你还会接触到**回调函数(callback)**这个概念, 用于响应Trackbar 拖动带来的影响。你可以拖动滑条, 动态的看到色块颜色的改变。最后做出功能丰富的绘图板出来。
python开发之HighGUI上位机开发(三)——项目实战:可变色画布和自制绘图板
项目实战-可变色画布
1. 创建统一灰度画布的函数
我们创建一个函数createGrayscaleCanvas
用于创建灰度图的画布.
import numpy as np import 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()
2. 终端输入灰度 生成灰度图
接下来, 如果想通过终端输入一个特定的数值,然后生成对应值的灰度图,那我们如何来实现呢?
在python中, 类似C语言中的scanf
语句的函数是input
,input
中传入的字符串是提示。
读入字符串赋值给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()
效果展示:
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()
效果展示:
5. 灰度图调色板 - V2
我们首先创建一个名字叫做gray_value
的trackbar
。 这个trackbar
在image
窗口上。
最小取值是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()
效果展示:
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()
项目实战-自制绘图板
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)画红点:
(255,0,0)画蓝点:
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是这么用的。“
看我灵魂画手:
2.2 存在问题
- 如果鼠标运动快了, 就会变成散点, 不连续。
- 只支持单个颜色, 不美丽。
- 不支持调节笔刷粗细。
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 博主的灵魂画作
本文地址:https://blog.csdn.net/ywsydwsbn/article/details/108262483