github地址(更新中):https://github.com/SunnyWuYang/Paint-Bucket-with-Gaps

参考:
[1] opencv实现画板效果:
https://github.com/aapsi/Paint-Application

版本三目的与功能:

  1. 画板工具增加功能(橡皮擦、笔刷大小更改、笔刷跟随效果、种子点选择)
  2. 将floodfill算法颜色判断由1改为255
  3. 画板与floodfill算法整合

算法实现

floodfill.py

import cv2
import numpy as np"""
TODO list in future versions:
1. implement floodfill algorithm without opencv
2. adjust algorithm to all colors
"""def erode(src, radius):result = src.copy()w = src.shape[1]h = src.shape[0]rr = radius*radiusfor x in range(1, h-1):for y in range(1, w-1):# 边界条件:四邻域内至少有一个与自己的值不相等is_edge = (src[x][y] != 255) and (src[x-1][y] == 255or src[x+1][y] == 255or src[x][y-1] == 255 or src[x][y+1] == 255)if is_edge:for dx in range(-radius, radius+1):for dy in range(-radius, radius+1):if dx*dx + dy*dy < rr:x1 = x + dxy1 = y + dyif 0 <= x1 < h and 0 <= y1 < w:result[x1][y1] = 0return resultdef dilate(src, radius):result = src.copy()w = src.shape[1]h = src.shape[0]rr = radius*radiusfor x in range(1, h-1):for y in range(1, w-1):# 边界条件:四邻域内至少有一个与自己的值不相等is_edge = (src[x][y] == 255) and (src[x-1][y] == 0or src[x+1][y] == 0or src[x][y-1] == 0 or src[x][y+1] == 0)if is_edge:for dx in range(-radius, radius+1):for dy in range(-radius, radius+1):if dx*dx + dy*dy < rr:x1 = x + dxy1 = y + dyif 0 <= x1 < h and 0 <= y1 < w:result[x1][y1] = 255return resultdef floodfill_with_gap(img, seedX, seedY, gap):normalFilled = np.zeros((img.shape[0]+2, img.shape[1]+2), dtype=np.uint8)flood_fill_flags = (4 | cv2.FLOODFILL_FIXED_RANGE | cv2.FLOODFILL_MASK_ONLY | 255 << 8)cv2.floodFill(img, normalFilled, (seedY, seedX),0, (0,)*3, (0,)*3, flood_fill_flags)radius = int(round(gap/2))eroded = normalFilled.copy()eroded = eroded[1:-1, 1:-1]eroded = erode(eroded, radius)insideEroded = np.zeros((img.shape[0]+2, img.shape[1]+2), dtype=np.uint8)for dx in range(-radius, radius+1):  # 行for dy in range(-radius, radius+1):  # 列x = seedX + dxy = seedY + dyif 0 <= x < img.shape[0] and 0 <= y < img.shape[1]:if eroded[x][y] == 0:continuecv2.floodFill(eroded, insideEroded, (y, x),1, (0,)*3, (0,)*3, flood_fill_flags)eroded = eroded - insideEroded[1:-1, 1:-1]outside = dilate(eroded, radius)normalFilled = normalFilled[1:-1, 1:-1]-outsidefinalResult = np.zeros((img.shape[0]+2, img.shape[1]+2), dtype=np.uint8)cv2.floodFill(normalFilled, finalResult, (seedY, seedX),0, (0,)*3, (0,)*3, flood_fill_flags)return finalResult[1:-1, 1:-1]

simplePainter

import cv2
import numpy as np
from floodfill import floodfill_with_gapclass Painter:"""默认情况下,程序为绘画模式,即按下鼠标左键后并拖动可以进行绘制。-进入笔刷模式按'b', 在该模式下按下鼠标左键进行拖动可产生线条-进入橡皮擦模式按'e',在该模式下按下鼠标左键进行拖动可进行擦除-进入种子点选择模式按's‘,在该模式下点击鼠标左键即运行floodfill算法,并保存结果-点击'+'或'-',增粗笔刷或减细笔刷-退出按'q'   """def __init__(self, img_size=(600, 800, 3), circle_radius=8, win_name='my Drawing Board', save_path=None, color=(1, 1, 255), gap=10):""":param img_size:画板大小,若指定background则忽略此参数:param circle_radius:‘circle'模式下圆形的半径:param win_name:窗口名:param save_path:绘图结果保存地址:param color:画笔颜色"""self.win_name = win_nameself.save_path = save_pathself.brush_color = colorself.circle_radius = circle_radiusself.gap = gapself.erase_color = (255, 255, 255)self.img_size = img_sizeself.layer_back = np.zeros(img_size, dtype=np.uint8)self.layer_tmp = np.zeros(img_size, dtype=np.uint8)  # 临时显示画笔层self.layer_show = np.ones(img_size, dtype=np.uint8)*255self.brushed = False  # 当前是否为笔刷模式self.erased = False  # 当前是否为橡皮模式self.pressed = False  # 当前鼠标是否为按下模式self.seed = False  # 当前是否为种子点选择模式self.last_x = -1self.last_y = -1def mouseEvent(self, event, x, y, flag, param):if self.brushed == True:if event == cv2.EVENT_LBUTTONDOWN:self.pressed = Truecv2.circle(self.layer_back, (x, y),self.circle_radius, self.brush_color, -1)  # 实心圆if event == cv2.EVENT_MOUSEMOVE and self.pressed:cv2.circle(self.layer_back, (x, y),self.circle_radius, self.brush_color, -1)  # 实心圆if event == cv2.EVENT_LBUTTONUP:self.pressed = Falseif event == cv2.EVENT_MOUSEMOVE and self.pressed == False:  # 临时显示画笔层cv2.circle(self.layer_tmp, (self.last_x, self.last_y),self.circle_radius, self.erase_color, -1)# 每执行一步这个操作,imgsMerge、imshow就会立马执行,因此之前的消去不会起作用cv2.circle(self.layer_tmp, (x, y),self.circle_radius, self.brush_color, -1)self.last_x = xself.last_y = yelif self.erased == True:if event == cv2.EVENT_LBUTTONDOWN:self.pressed = Truecv2.circle(self.layer_back, (x, y),self.circle_radius, self.erase_color, -1)elif event == cv2.EVENT_MOUSEMOVE and self.pressed == True:cv2.circle(self.layer_back, (x, y),self.circle_radius, self.erase_color, -1)elif event == cv2.EVENT_LBUTTONUP:self.pressed = Falseelif self.seed == True:if event == cv2.EVENT_LBUTTONUP:result = floodfill_with_gap(self.layer_show, y, x, 10)cv2.imwrite(self.save_path, result)cv2.displayStatusBar(self.win_name, "Result has been saved!")@staticmethoddef imgsMerge(img1, img2, color_list):mask = np.zeros(shape=img1.shape, dtype=bool)for color in color_list:curr_mask = (img2[:, :] == np.array(color)).all(axis=2)  # shape(w,h)curr_mask = curr_mask[..., np.newaxis]  # shape(w,h,1)curr_mask = np.repeat(curr_mask, repeats=3,axis=-1)  # shape(w,h,3)mask = mask | curr_masknp.copyto(dst=img1, src=img2, where=mask)def main(self):cv2.namedWindow(self.win_name, cv2.WINDOW_NORMAL)cv2.setMouseCallback(self.win_name, self.mouseEvent)  # 鼠标事件回调函数while True:cv2.imshow(self.win_name, self.layer_show)key = cv2.waitKey(1)# 这两个顺序一定不要弄反!!否则临时画笔层无法显示在擦除层上!self.imgsMerge(self.layer_show, self.layer_back,  # 永久绘画与擦除层color_list=[self.brush_color, self.erase_color])self.imgsMerge(self.layer_show, self.layer_tmp,  # 临时显示画笔层color_list=[self.brush_color, self.erase_color])# 一定要注意,layer_back不能清空,否则临时显示画笔便成了橡皮擦的效果self.layer_tmp = np.zeros(self.img_size, dtype=np.uint8)if key == ord('b'):  # brushself.brushed = not self.brushedself.erased = Falseself.seed = Falseelif key == ord('e'):  # erase# 需要去除临时显示画笔层 最后鼠标停留时留下的圆圈cv2.circle(self.layer_tmp, (self.last_x, self.last_y),self.circle_radius, (255, 255, 255), -1)self.erased = not self.erasedself.brushed = Falseself.seed = Falseelif key == ord('-'):  # decrease brush sizeself.circle_radius = self.circle_radius - 2if self.circle_radius < 2:self.circle_radius = 2print('minus', self.circle_radius)elif key == ord('+'):  # increase brush sizeself.circle_radius = self.circle_radius + 2print('increase', self.circle_radius)elif key == ord('s'):# 需要去除临时显示画笔层 最后鼠标停留时留下的圆圈cv2.circle(self.layer_tmp, (self.last_x, self.last_y),self.circle_radius, (255, 255, 255), -1)self.seed = not self.seedself.brushed = Falseself.erased = Falseelif key == ord('q'):print("The windows are destroyed")breakcv2.destroyAllWindows()if __name__ == '__main__':painter = Painter(img_size=(150, 200, 3),circle_radius=4,save_path='result.png',color=(0, 0, 255),gap=10)painter.main()

左:画板效果 右:floodfill运行结果(种子点选择为圆圈内部时)

opencv 实现ps油漆桶【三】相关推荐

  1. opencv 实现ps油漆桶【一】

    前言(可忽略):前些日子遇到个需求,需要首先检测出网格面边缘(不是完全规则的),然后将网格面内部填充为另一种颜色.但是由于前景背景较为相似,检测出的网格面边缘并没有完全闭合,导致用普通的floodfi ...

  2. opencv 实现ps油漆桶【二】

    github地址(更新中):https://github.com/SunnyWuYang/Paint-Bucket-with-Gaps 参考: [1] opencv实现画板效果: https://bl ...

  3. OpenCv中实现了三种立体匹配算法:

    OpenCv中实现了三种立体匹配算法: BM算法 SGBM算法 Stereo Processing by Semiglobal Matching and Mutual Information GC算法 ...

  4. OpenCV学习笔记(三十六)——Kalman滤波做运动目标跟踪 OpenCV学习笔记(三十七)——实用函数、系统函数、宏core OpenCV学习笔记(三十八)——显示当前FPS OpenC

    OpenCV学习笔记(三十六)--Kalman滤波做运动目标跟踪 kalman滤波大家都很熟悉,其基本思想就是先不考虑输入信号和观测噪声的影响,得到状态变量和输出信号的估计值,再用输出信号的估计误差加 ...

  5. OpenCV学习笔记(三十一)——让demo在他人电脑跑起来 OpenCV学习笔记(三十二)——制作静态库的demo,没有dll也能hold住 OpenCV学习笔记(三十三)——用haar特征训练自己

    OpenCV学习笔记(三十一)--让demo在他人电脑跑起来 这一节的内容感觉比较土鳖.这从来就是一个老生常谈的问题.学MFC的时候就知道这个事情了,那时候记得老师强调多次,如果写的demo想在人家那 ...

  6. OpenCV学习笔记(三):图像对比度、亮度调整源码

    OpenCV学习笔记(三):图像对比度.亮度调整源码 主函数: #include <opencv2/opencv.hpp>using namespace cv;using namespac ...

  7. OpenCV学习笔记(三):多通道图像分离、混合算子:split(),merge()

    OpenCV学习笔记(三):多通道图像分离.混合算子:split(),merge() #include <opencv2/opencv.hpp>#define BRG_BLUE_CHANN ...

  8. OpenCV与图像处理学习三——线段、矩形、圆、椭圆、多边形的绘制以及文字的添加

    OpenCV与图像处理学习三--线段.矩形.圆.椭圆.多边形的绘制以及文字的添加 一.OpenCV中的绘图函数 1.1 线段绘制 1.2 矩形绘制 1.3 圆绘制 1.4 椭圆的绘制 1.5 多边形绘 ...

  9. 基于深度学习的人脸识别系统(Caffe+OpenCV+Dlib)【三】VGG网络进行特征提取

    前言 基于深度学习的人脸识别系统,一共用到了5个开源库:OpenCV(计算机视觉库).Caffe(深度学习库).Dlib(机器学习库).libfacedetection(人脸检测库).cudnn(gp ...

最新文章

  1. 【ACM】杭电OJ 2012。
  2. 二叉查找树Java实现代码
  3. linux环境下安装gcc
  4. ARMA模型性质之平稳AR模型得统计性质
  5. 电磁波考试中可以用计算机吗,计算机考试试题库带答案(8页)-原创力文档
  6. DMA(direct memory access)直接内存访问
  7. 21世纪高等专业教材21 CENTURY HIGHER PROFESSIONAL TEXTBOOKS RESUME WRITING METHOD PRINCIPLES AND RULES
  8. 工业相机基本参数及选型
  9. 【Vue五分钟】五分钟了解vue的常用实例方法
  10. ConstraintLayout实现左中右布局
  11. tensorflow构建神经网络回归分析可视化
  12. 首都经贸计算机考研怎么样,【考研心路历程】首都经贸大学考研的回忆与感悟...
  13. [STM32]jlink RTT使用详解
  14. 青少年python一级考试试题,青少年python一级考试
  15. 关于点击微信图文信息直接跳转至外部链接
  16. 使用ArchR分析单细胞ATAC-seq数据(第四章)
  17. 岁月是把杀猪刀,程序员刚实习VS几年后,从小鲜肉到老司机...
  18. 使用OSGeo4W安装配置QGIS
  19. abp 打包部署到ubuntu_如何通过宝塔运维面板进行部署?
  20. Docker多主机管理Docker Machine

热门文章

  1. RabbitMQ-8-其他知识点
  2. 解决方案:ppt打不开,显示发现文件中的内容有问题。可尝试修复此演示文稿...
  3. 使用 userdel 命令删除 Linux 中的用户
  4. 静态资源放置于独立域名之下的好处
  5. 基于python快速简便地实时计算金融技术指标
  6. Java事件模型与Android事件模型的比较
  7. Linux文件夹执行权限不够如何处理?
  8. JavaScript-垃圾回收机制(GC)
  9. echart,响应式布局
  10. 计算机安装调整原因,详解电脑分辨率调不过来怎么办