基于opencv库和numpy库开发的绘画板

一.本程序由于可扩展性极大,故采用面向对象编程。大概思路如下:

一.创建一个主流程类(核心操控整个程序,因为此程序可扩展性极强,所以用面向对象编程非常合适):
初始化:传入保存图片的地址;白色背景板或者传入绘画对象;存储image的列表;默认参数

方法一:返回默认参数。(如果修改过了参数就返回修改过的参数)
方法二用户选择更改参数,如画笔颜色,画笔尺寸,字体。(使用trackbar的回调函数)
方法三:存储绘画类返回的image。(采用栈的思想)注意:要深拷贝图像!不要直接以赋值的方式复制,这是浅拷贝。
方法四:得到上一个image。
方法五:保存image到本地。

二.定义回调函数:
用来记录用户行为,以此为基础作图。(不放在类里面,觉得没必要,而且放在外面也方便调用)

三.绘画函数:
绘制直线,椭圆,多边形,文字。

四.主函数:
循环使用界面,用户通过进行输入一个或多键进行操作。

二.整体代码:

'''使用opencv和numpy制作绘图板'''

import cv2
import numpy as np


class MainProcess:
    '''主流程类,包含6个方法,定义一些用户常用操作'''

    # 建立画笔颜色,尺寸,绘制字体字典用来供用户索引
    dict_pencolor = {
        'blue': (255, 0, 0), 'green': (0, 255, 0), 'red': (0, 0, 255), 'white': (255, 255, 255), 'black': (0, 0, 0),
        'springgreen': (127, 255, 0), 'gold': (0, 215, 255), 'purple': (240, 32, 160), 'yellow': (0, 255, 255)
    }
    list_pen_size = [1, 2, 3, 4]
    list_font = [1, 2, 3, 4]

    def __init__(self, initial_image, download_address):
        '''初始化类:初始画布,保存图片的地址,存储image的列表画笔的一些参数:黑色;1号尺寸;1号字体'''

        self.initial_image = initial_image
        self.download_address = download_address
        self.list_save_image = [self.initial_image.copy()]
        self.pen_color = self.dict_pencolor['black']
        self.pen_size = self.list_pen_size[0]
        self.pen_font = self.list_font[0]

    def get_params(self):
        '''获得参数'''
        return self.pen_color, self.pen_size, self.pen_font

    def change_params(self):
        '''更改参数'''
        # 列出可供选项
        list_color_kinds = list(self.dict_pencolor.keys())
        print('可供选择的画笔颜色有以下几种:{}\n可供选择的画笔尺寸有{}种(从1开始计数)\n可供选择的字体有{}种(从1开始计数)'.format(','.join(list_color_kinds),
                                                                                     len(self.list_pen_size),
                                                                                     len(self.list_font)))
        # 建立更改字典,简化用户输入
        dict_menu = {
            'c': self.dict_pencolor, 's': self.list_pen_size, 'f': self.list_font
        }
        # 用户进行操作
        while True:
            try:
                list_user_input = input('输入c代表更改画笔颜色,输入s代表更改画笔尺寸,输入f代表更改绘画字体.\n请输入至少一个字母,至多三个字母,字母之间用空格隔开:\n').split(
                    ' ')
                if 1 <= len(list_user_input) <= 3:
                    if 'c' in list_user_input:
                        self.pen_color = self.dict_pencolor[input('请输入更改画笔的颜色:')]
                    if 's' in list_user_input:
                        self.pen_size = self.list_pen_size[eval(input('请输入更改画笔的尺寸:')) - 1]
                    if 'f' in list_user_input:
                        self.pen_font = self.list_font[eval(input('请输入更改画笔的字体:')) - 1]
                    break
                else:
                    print('请按照要求输入!')
            except KeyError:
                print('请按照要求输入!')

    def save_image(self, image):
        '''实时保存操作:添加当前image到列表中(入栈)'''
        self.list_save_image.append(image.copy())

    def get_previous_image(self):
        '''撤销操作:返回上一个image'''
        return self.list_save_image.pop()

    def download_image(self, image):
        '''持久化存储image'''
        cv2.imwrite(f'{self.download_address}\\image.jpg', image)
        print('保存图片成功!')


def get_point_list():
    '''获得绘图点等相关信息'''

    def mouse_callback(event, x, y, flags, point_list):
        # 记录用户行为
        point_list.append((event, x, y))

    # 获得用户操作的一系列行为事件
    point_list = []
    # 添加鼠标回调功能
    cv2.setMouseCallback('window', mouse_callback, point_list)

    # 设置小循环
    while True:
        key = cv2.waitKey(1)
        if key == ord('q'):
            break

    return point_list


def draw_line(image, pen_color, pen_size):
    # 获得绘图点坐标
    point_list = get_point_list()

    # 查询前两个事件为1的元组,并且将其存储到列表points里面
    points = []
    point_num = 0
    for i in point_list:
        if i[0] == 1 and point_num < 2:
            points.append((i[1], i[2]))
            point_num += 1

    # 两点确定一条直线
    cv2.line(image, points[0], points[1], pen_color, pen_size)


def draw_ellipse(image, pen_color, pen_size):
    # 获得绘图点坐标
    point_list = get_point_list()

    # 查询前三个事件为1的元组,并且将其存储到列表points里面
    points = []
    point_num = 0
    for i in point_list:
        if i[0] == 1 and point_num < 3:
            points.append((i[1], i[2]))
            point_num += 1

    # 确定椭圆圆心
    point = points[0]
    # 确定椭圆长短半轴,由于python3.8才支持欧氏距离公式,故此处造个该公式的小轮子
    long_axis = ((points[0][0] - points[1][0]) ** 2 + (points[0][1] - points[1][1]) ** 2) ** 0.5
    short_axis = ((points[0][0] - points[2][0]) ** 2 + (points[0][1] - points[2][1]) ** 2) ** 0.5
    # 画椭圆
    cv2.ellipse(image, point, (int(long_axis), int(short_axis)), 0, 0, 360, pen_color, pen_size)


def draw_polylines(image, pen_color, pen_size):
    # 获得绘图点坐标
    point_list = get_point_list()

    # 查询所有事件为1的元组,并且将其存储到列表points里面
    points = []
    for i in point_list:
        if i[0] == 1:
            points.append((i[1], i[2]))
    # 将点坐标转化成数组形式
    plt = np.array(points, np.int32)

    # 用户选择绘制多边形种类(开环或者闭环)
    k = None
    while True:
        user_input = input('输入t绘制闭环多边形,输入f绘制开环多边形:')
        if user_input == 't':
            k = True
            break
        elif user_input == 'f':
            k = False
            break
        else:
            continue

    # 绘制多边形
    cv2.polylines(image, [plt], k, pen_color, pen_size)


def draw_text(image, pen_color, pen_size, pen_font):
    # 获得绘图点坐标
    point_list = get_point_list()

    # 查询第一个事件为1的元组,并且将其存储到列表points里面
    points = []
    for i in point_list:
        if i[0] == 1:
            points.append((i[1], i[2]))
            break

    # 获取起始坐标点,需要绘制的字符串
    point = points[0]
    text = input('请输入想要绘制的字符串,注意一定要是英文:')
    # 绘制字符串,默认字体大小为1
    cv2.putText(image, text, point, pen_font, 1, pen_color, pen_size)


def main():
    '''主函数,耦合MainProcess类和众多函数,并结合while循环构成整个程序'''

    # 加载图片,给出保存图片位置
    file_path = input('请输入图片位置(要带有.jpg):')
    org_image = cv2.imread(rf'{file_path}')
    # 深拷贝图片,留着原图片给MainProcess.clear_drawingboard()用
    image = org_image.copy()
    # 图片保存至本地的地址
    save_path = input('请输入保存图片的地址(不要含有.jpg只是输入位置):')
    save_address = rf'{save_path}'

    # 实例化MainProcess类
    mainprocess = MainProcess(image, save_address)

    # 设置大循环
    while True:
        try:
            # 展示图片
            cv2.imshow('window', image)
            # 获得绘画参数
            pen_color, pen_size, pen_font = mainprocess.get_params()
            # 用户输入指令
            key = cv2.waitKey(1)

            # 更改画笔参数
            if key == ord('c'):
                mainprocess.change_params()
            # 画直线
            if key == ord('l'):
                draw_line(image, pen_color, pen_size)
            # 画椭圆
            if key == ord('e'):
                draw_ellipse(image, pen_color, pen_size)
            # 画多边形
            if key == ord('p'):
                draw_polylines(image, pen_color, pen_size)
            # 画文字,只支持英文
            if key == ord('t'):
                draw_text(image, pen_color, pen_size, pen_font)
            # 保存此刻画布状态
            if key == ord('s'):
                mainprocess.save_image(image)
            # 画布回到上一个保存的状态
            if key == ord('g'):
                image = mainprocess.get_previous_image().copy()
            # 保存图片至本地
            if key == ord('d'):
                mainprocess.download_image(image)
            # 清空修改的痕迹,即将此刻的图片状态修改为原始图片
            if key == ord('o'):
                image = org_image.copy()
            # 退出程序
            if key == ord('q'):
                break

        except IndexError:
            print('索引错误!')


if __name__ == '__main__':
    '''使用main函数可以解决传递一些麻烦参数等问题'''
    # 新建画图板窗口
    cv2.namedWindow('window', cv2.WINDOW_NORMAL)
    cv2.resizeWindow('window', 640, 480)

    # 执行绘图功能
    main()

    # 销毁所有窗口
    cv2.destroyAllWindows()

实测可用,效果不错。

三.使用者阅读文档:

一.使用前的注意事项:
    1.用户提供给程序的图片路径中切记不要含有中文字符!否则程序会报错:AttributeError。
    2.用户在调用draw_text()函数时,根据提示输入的字符串中不要含有中文字符。
    3.总之,抱歉,本程序暂不支持中文字符。

二.功能说明书:
    1.键c,按后可根据相关提示更改画笔颜色,画笔尺寸,画布上出现的字体的种类。
    2.键l,按后请用鼠标点击画布上的两个点,点击结束后按q键,画布上的两点会连结成一条直线。
    3.键e,按后请用鼠标点击画布上三个点,点击结束后按q键,画布上会以第一个点为椭圆中心,剩下两个点到此点的距离为长短半轴,绘制出椭圆。
    4.键p,按后请用鼠标点击画布上多个点,点击结束后按q键,根据提示选择绘制多边形的种类(闭环或者开环),选择结束后,画布上的点会以点击的先后次序连接形成多边形。
    5.键t,按后请用鼠标点击画布上一个点作为绘制文字的起始点,点击结束后按q键,根据提示进行操作即可。画布上会出现输入的字符串。
    6.键s,按后此刻的画布状态会被记录到一个列表中。
    7.键g,按后画布状态会返回到上次按键s存储时的状态。(存储画布状态的列表默认首项是原画)
    8.键d,按后会把此时画布以.jpg的格式保存到用户指定的路径下。
    9.键o,按后画布的状态会立刻被还原为原画。
    10.键q,按后结束本次程序的使用。(其实在本程序中键q可以被理解为退出当前循环)

四.总结:

开发这个程序虽然代码量较少,但是实用性很高。本文章仅供学习交流使用。

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值