机器学习 | 实现图像加密解密与数字水印处理

目录

实现窗口可视化

数字图像加密

窗口布局设置

基于混沌Logistic的图像加密

基于三重DES的图像加密

数字图像解密

窗口布局设置

基于混沌Logistic的图像解密

基于三重DES的图像解密

基于LSB的数字水印提取

窗口布局设置

水印的嵌入与提取


实现窗口可视化

这里我们使用python自带的tkinter标准库实现图形用户界面(GUI)应用程序,其提供了一组工具和组件,使开发者能够创建窗口、按钮、文本框、标签等各种 GUI 元素,并通过编写代码来处理用户的输入和操作。

首先我们先导入tkinter库及其对应要使用的函数:

python 复制代码
from tkinter import *
# 导入tkinter中的对话框函数messagebox并将其重命名为 mbox
import tkinter.messagebox as mbox

通过主函数main()创建窗口,并设计其对应的样式:

python 复制代码
def main():
    # 创建主窗体布局
    root = Tk()
    # 设置窗口大小为 300x200 像素,并将其放置在屏幕位置 (600, 200)
    root.geometry('500x300+600+200')
    # 设置标题
    root.title('图像加密及数字水印处理系统')
    """
       创建一个标签组件
       参数: root表示该组件父容器即窗口,text参数表示显示文本内容,bg为标签背景颜色
       pack() 方法将标签添加到父容器中,fill=X 表示填充方向为水平方向(X 轴方向)
    """
    Label(root, text='功能选择', bg='#008c8c').pack(fill=X)
    """
        创建一个按钮组件
        参数: root是该组件的父容器,text参数表示按钮上显示的文本内容,font参数设置按钮文本的字体和大小,command参数设置按钮的回调函数
        这里使用一个lambda表达式来定义回调函数。表达式实际上是一个匿名函数,当用户点击按钮时,会调用该函数,然后函数会调用goto()函数并将参数传递给它。
    """

接下来继续在main()函数下给窗口添加内容,这里添加四个按钮,并给每个按钮设置回调函数:

python 复制代码
    # 进入子窗体回调函数
    def goto(num):
        # 关闭主窗口
        root.destroy()
        # 使用字典来映射数字和对应的窗口函数,然后在 goto 函数中进行查找和调用,如果找到则调用该函数,否则不执行任何操作。
        windows = {
            1: One,
            2: Two,
            3: Three
        }
        func = windows.get(num, lambda: None)
        if func:
            func()

    # 退出系统
    def LogDown():
        if mbox.askokcancel("退出", "确认退出系统吗?"):
            root.destroy()

    # 设置窗口布局
    btn1 = Button(root, text="数字图像加密", font=('Arial', 12), command=lambda: goto(1))
    # pack()方法将按钮组件添加到父容器中。参数用于设置垂直方向上的外边距(即上下边距),其值为 15。
    btn1.pack(pady=15)

    btn2 = Button(root, text="数字图像解密", font=('Arial', 12), command=lambda: goto(2))
    btn2.pack(pady=15)
    btn3 = Button(root, text="数字水印处理", font=('Arial', 12), command=lambda: goto(3))
    btn3.pack(pady=15)
    btn4 = Button(root, text="退出系统", font=('Arial', 12), command=LogDown)
    btn4.pack(pady=15)

    # 监听并处理用户的事件,直到程序被显式关闭或者窗口被销毁, 只有当主事件循环结束(即窗口被关闭或销毁)时,才会继续执行 mainloop() 后面的代码。
    root.mainloop()

接下来给字典映射的函数名称放置在和main()函数平级的位置,最后调用主函数

python 复制代码
# 第一个窗口
def One():
    print(1)

# 第二个窗口
def Two():
    print(1)

# 第三个窗口
def Three():
    print(1)
    
# 执行 main() 函数
if __name__ == '__main__':
    main()

最终执行的效果如下:

感觉画面有点小丑,加个图片试试吧, 需要导入 ImageTk 和 Image 类,它们位于 PIL(Python Imaging Library)库中,终端执行如下命令进行安装:

python 复制代码
pip install pillow

然后设置背景图片,根据自己设备的图片,调整按钮的上边距内容:

python 复制代码
# 加载背景图片
bg_image = Image.open("bg.jpg")
bg_image = bg_image.resize((800, 500), Image.BILINEAR)  # 调整图片大小
bg_photo = ImageTk.PhotoImage(bg_image)
# 创建一个标签组件作为背景
bg_label = Label(root, image=bg_photo)
bg_label.place(x=0, y=0, relwidth=1, relheight=1)

画面如下(效果还行,暂时先这样吧,后期有新功能再接着改):

数字图像加密

数字图像加密按钮,当我们点击之后也是呈现一个窗口,这里撰写两种对图像进行加密的方式,ok接下来我们首先实现基本窗口的设置:

窗口布局设置

图像加密窗口的布局样式与主窗口的布局样式一致,这里就不再赘述了,具体的代码如下:

python 复制代码
# 第一个窗口
def One():
    # 函数内部使用全局变量encryption
    global encryption
    # 将全局变量encryption的值设置为True,以表示已经进行了加密操作。
    encryption = True

    # 创建子窗口布局
    root1 = Tk()
    # 设置窗口大小为 300x200 像素,并将其放置在屏幕位置 (600, 200)
    root1.geometry('800x500+600+200')
    # 设置标题
    root1.title('数字图像加密')
    Label(root1, text='功能选择', bg='#008c8c').pack(fill=X)

    # 进入当前窗口的子窗体回调函数
    def goto(num):
        # 关闭主窗口
        root1.destroy()
        # 使用字典来映射数字和对应的窗口函数,然后在 goto 函数中进行查找和调用,如果找到则调用该函数,否则不执行任何操作。
        windows = {
            1: One_one,
            2: One_two,
        }
        func = windows.get(num, lambda: None)
        if func:
            func()
    # 设置窗口布局
    btn1 = Button(root1, text="返回主窗体", command=lambda: gotoMain(root1))
    btn1.pack(side="bottom", pady=10)
    btn2 = Button(root1, text="基于混沌Logistic的图像加密", font=('Arial', 12), command=lambda: goto(1))
    btn2.pack(pady=(115, 15))
    btn3 = Button(root1, text="基于三重DES的图像加密", font=('Arial', 12), command=lambda: goto(2))
    btn3.pack(pady=15)
    btn4 = Button(root1, text="退出系统", font=('Arial', 12), command=lambda: LogDown(root1))
    btn4.pack(pady=15)
    root1.mainloop()

从上面的代码中,我们设置了一个全局变量用于表达图像是否处在加密状态中:

python 复制代码
# 定义全局变量
encryption = False  # 表示未加密状态

最终呈现的结果如下:

基于混沌Logistic的图像加密

当我们点击该加密算法的按钮之后,通过回调函数进入到该加密算法界面:

然后接下来我们开始设置进入到该图像加密的UI界面的设置,代码如下:

python 复制代码
def One_one():
    # 页面布局
    root1_1 = Tk()
    root1_1.geometry('800x500+600+200')
    root1_1.title('数字图像加密')
    Label(root1_1, text='基于混沌Logistic的图像加密', bg='#008c8c').pack(fill=X)

    # 回调函数
    def goOne():
        root1_1.destroy()
        One()

    # 设置窗口布局
    btn1 = Button(root1_1, text="返回上一窗体", command=goOne)
    btn1.pack(side="bottom", pady=10)
    btn2 = Button(root1_1, text="选择图像", font=('Arial', 12), command=Open_img)
    btn2.pack(pady=(115, 15))
    btn3 = Button(root1_1, text="加密图像", font=('Arial', 12), command=Logistical)
    btn3.pack(pady=15)
    btn4 = Button(root1_1, text="另存图像", font=('Arial', 12), command=SaveEncryptionImg)
    btn4.pack(pady=15)
    btn5 = Button(root1_1, text="重置图像", font=('Arial', 12), command=ResetEncryptionImg)
    btn5.pack(pady=15)
    root1_1.mainloop()

呈现的界面如下:

接下来我们开始写选择图像的回调函数进行书写,这里需要先下载opencv-python库,对于opencv-python库,它使用PEP 517来构建wheels,但是可能缺少一些必要的编译工具,解决此问题的方法是升级pip到最新版本,并确保已安装所有必要的编译工具。建议您也将pip升级到最新版本,可以通过运行以下命令来升级:

python 复制代码
python -m pip install --upgrade pip

opencv-python这个库内存比较大,建议临时换源来进行安装

python 复制代码
pip install opencv-python -i https://pypi.tuna.tsinghua.edu.cn/simple/

具体代码如下:

python 复制代码
# 可以进行图像读取、显示、保存、转换、滤波、特征提取等操作。
import cv2
# 可以进行矩阵运算、数组操作、数值计算等,常用于图像数据的处理和分析。
import numpy as np
# 提供了丰富的图像读取、写入、裁剪、旋转、缩放等功能。
from PIL import ImageTk, Image

global_img = ""  # 表示文件路径
panelA = None  # 也是一个用于显示图像的容器,可以将其添加到tkinter窗口中以显示图像。
panelB = None  # 也是一个用于显示图像的容器,可以将其添加到tkinter窗口中以显示图像。

# 定义图像处理的相关信息的字典
encryption_img = {  # 加密
    "encryption": np.array([]), "EncryptionImg": np.array([]), "ImgPath": "", "width": None, "height": None
}
decrypt_img = {"decrypt": None, "DecryptionImg": None, "ImgPath": "", "width": None, "height": None}  # 解密

# 上传图像
def Open_img():
    global global_img, panelA
    # 让用户选择一个文件,并返回所选文件的路径,并赋值给 global_img
    global_img = filedialog.askopenfilename(title='open')
    if global_img.endswith('jpg') or global_img.endswith('png') or global_img.endswith('bmp'):
        img = cv2.imdecode(np.fromfile(global_img, dtype=np.uint8), 1)
        rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
        size = img.shape
        new_width = 320
        scale_factor = new_width / rgb.shape[1]
        new_height = int(rgb.shape[0] * scale_factor)
        resize_image0 = cv2.resize(rgb, dsize=(new_width, new_height), interpolation=cv2.INTER_LINEAR)
        resize_image1 = Image.fromarray(resize_image0)
        img1 = ImageTk.PhotoImage(resize_image1)
        panelA = Label(image=img1)
        panelA.image = img1
        panelA.place(x=10, y=(500 - panelA.winfo_reqheight()) / 2)
        if encryption:
            encryption_img["encryption"] = img
            encryption_img["ImgPath"] = global_img
            encryption_img["width"] = size[1]
            encryption_img["height"] = size[0]
        else:
            print(1)
        print("----------图像已打开----------")
    else:
        mbox.showinfo("错误", "上传的文件不是图片格式,请重新上传")

完成上传图像之后,接下来我们基于混沌Logistic的加密图像算法对图像进行加密:

python 复制代码
def int2bin8(x):  # 整型转8位二进制
    return bin(x)[2:].zfill(8)

def int2bin16(x):  # 整型转16位二进制
    return bin(x)[2:].zfill(16)

# 基于混沌Logistic的加密图像
def Logistical():
    global panelB

    img = encryption_img["encryption"]
    encryptionImg = np.zeros(img.shape, np.uint8)
    x0 = 0.15624562
    g0 = 164
    j0 = 10

    x = img.shape[0]
    y = img.shape[1]
    c = img.shape[2]
    g0 = int2bin16(g0)

    for s in range(x):
        for n in range(y):
            for z in range(c):
                m = int2bin8(img[s][n][z])  # 像素值转八位二进制
                ans = ""

                for i in range(8):
                    ri = int(g0[-1])  # 取手摇密码机最后一位ri
                    qi = int(m[i]) ^ ri  # 与像素值异或得qi
                    xi = 1 - math.sqrt(abs(2 * x0 - 1))  # f1(x)混沌迭代

                    if qi == 0:  # 如果qi=0,则运用x0i+x1i=1;
                        xi = 1 - xi
                    x0 = xi  # xi迭代
                    t = int(g0[0]) ^ int(g0[12]) ^ int(g0[15])  # 本源多项式x^15+x^3+1
                    g0 = str(t) + g0[0:-1]  # gi迭代
                    ci = math.floor(xi * (2 ** j0)) % 2  # 非线性转换算子
                    ans += str(ci)

                re = int(ans, 2)
                encryptionImg[s][n][z] = re  # 写入新图像

    encryption_img["EncryptionImg"] = encryptionImg
    rgb = cv2.cvtColor(encryptionImg, cv2.COLOR_BGR2RGB)
    new_width = 320
    scale_factor = new_width / rgb.shape[1]
    new_height = int(rgb.shape[0] * scale_factor)
    resize_image0 = cv2.resize(rgb, dsize=(new_width, new_height), interpolation=cv2.INTER_LINEAR)
    arrayImage = Image.fromarray(np.uint8(resize_image0))
    encryptionImg = ImageTk.PhotoImage(arrayImage)
    panelB = Label(image=encryptionImg)
    panelB.image = encryptionImg
    panelB.place(x=(800 - panelB.winfo_reqwidth() - 10), y=(500 - panelB.winfo_reqheight()) / 2)
    print("-----加密完成-----")

接下来就是将加密后的图片进行保存以及对图片进行重置两个函数的实现:

python 复制代码
# 保存图片
def SaveEncryptionImg():
    filePath = filedialog.askdirectory(title='open')
    cv2.imencode(".jpg", encryption_img["EncryptionImg"])[1].tofile(filePath+'/EncryptionImg.jpg')
    print("-----保存完成-----")

# 重置图片
def ResetEncryptionImg():
    global panelA, panelB
    encryption_img["encryption"] = np.array([])
    encryption_img["EncryptionImg"] = np.array([])
    if panelA:
        panelA.place_forget()
    if panelB:
        panelB.place_forget()
    print("-----重置完成-----")

最终呈现的结果如下:

基于三重DES的图像加密

当我们点击该加密算法的按钮之后,通过回调函数进入到该加密算法界面:

然后接下来我们开始设置进入到该图像加密的UI界面的设置,代码如下:

python 复制代码
# 第1_2个子窗口
def One_two():
    # 页面布局
    root1_2 = Tk()
    root1_2.geometry('800x500+600+200')
    root1_2.title('数字图像加密')
    Label(root1_2, text='基于三重DES的图像加密', bg='#008c8c').pack(fill=X)

    # 回调函数
    def goOne():
        root1_2.destroy()
        One()

    # 设置窗口布局
    btn1 = Button(root1_2, text="返回上一窗体", command=goOne)
    btn1.pack(side="bottom", pady=10)
    btn2 = Button(root1_2, text="选择图像", font=('Arial', 12), command=Open_img)
    btn2.pack(pady=(115, 15))
    btn3 = Button(root1_2, text="加密图像", font=('Arial', 12), command=DES3_encryption)
    btn3.pack(pady=15)
    btn4 = Button(root1_2, text="另存图像", font=('Arial', 12), command=SaveDES3Img)
    btn4.pack(pady=15)
    btn5 = Button(root1_2, text="重置图像", font=('Arial', 12), command=ResetEncryptionImg)
    btn5.pack(pady=15)
    root1_2.mainloop()

呈现的界面如下:

基于三重数据加密标准(Triple Data Encryption Standard,简称3DES)的图像加密是一种使用3DES算法对图像进行保护和加密的方法。
3DES是一种对称加密算法,它是对经典DES算法的改进和扩展。经典DES使用56位密钥对64位的数据块进行加密,但随着计算能力的提升,DES算法的安全性逐渐降低。为了增强安全性,3DES采用了多次DES加密操作,通常使用两个或三个不同的密钥,对数据进行多轮加密。

基于三重DES的图像加密过程如下:

密钥选择:选择两个或三个不同的密钥作为加密算法的输入。

分组操作:将待加密的图像数据分为固定大小的数据块(例如64位),每个数据块进行独立的加密操作。

加密操作:对每个数据块进行多轮的DES加密操作,通常采用加密-解密-加密(EDE)的模式。具体步骤如下:

使用第一个密钥进行DES加密;使用第二个密钥进行DES解密;(可选)使用第三个密钥再次进行DES加密。

合并结果:将加密后的数据块重新组合成加密图像。

基于三重DES的图像加密能够提供较高的安全性,通过多次加密操作增加了破解的难度。同时,由于每个数据块是独立加密的,因此即使出现部分数据损坏或丢失,也不会影响整个图像的解密。这使得基于三重DES的图像加密在保护敏感图像数据方面具有一定的优势。

具体实现代码如下:

python 复制代码
"""
基于三重DES的加密图像与解密图像代码
"""

# 图片像素二进制值获取
def get_pixel(img):
    img = cv2.imread(img)
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    img_width = img.shape[1]
    img_height = img.shape[0]
    bin_data = ''
    for i in range(img_width):
        for j in range(img_height):
            r, g, b = img[j, i]  # 获取像素点的rgb值
            bin_data += '{:08b}'.format(r)
            bin_data += '{:08b}'.format(g)
            bin_data += '{:08b}'.format(b)
    return bin_data, img_width, img_height

# DES明文分组处理函数
def set_group(bin_data):
    print("***   正在进行明文分组   ***")
    bin_data_list = []
    data_length = len(bin_data)
    i = 0
    while data_length >= 64:  # 按照
        # print("正在划分第"+str(i)+"个明文分组  ",data_length)
        bin_data_list.append(bin_data[:64])
        data_length -= 64
        bin_data = bin_data[64:]
        i += 1
    print("正在处理最后一个明文分组   ", data_length)
    if data_length == 0:  # 若恰好能够完全分组
        return bin_data_list
    else:
        padding_length = 64 - data_length
        padding_byte_size = padding_length # 8
        # 填充采用 PKCS#5填充标准
        padding_data = ('{:08b}'.format(padding_byte_size)) * padding_byte_size
        last_group_data = bin_data + padding_data
        bin_data_list.append(last_group_data)  # 填加最后填充的内容
        return bin_data_list

# 将明文分组信息储存在txt文件中,便于2组内加密中获取相应的明文数据
def save_group_data(bin_data_list):
    f = open('group.txt', 'w')
    for i in bin_data_list:
        f.write(i + '\n')
    f.close()

# 获取明文的全部分组
def get_group():
    group_list = []
    group_file_name = 'group.txt'  # 明文分组文件名
    f = open(group_file_name, 'r')
    for line in f:
        group_list.append(line.strip('\n'))
    f.close()
    return group_list

# 获取初始IV向量
def get_IV():
    iv_seed = os.urandom(8)  # 随机获取8个字符
    iv_bin_str = ''
    for i in iv_seed:
        iv_bin_str += '{:08b}'.format(i)
    return iv_bin_str

# 随机生成满足奇偶校验的64位密钥
def get_rand_key(p):
    key_seed = os.urandom(8)  # 随机获取8个字符
    KEY_bin_str = ''
    for i in key_seed:
        binstr = '{:07b}'.format(i)  # 将每个字符转成7位二进制,第8位用于表示奇偶校验位
        xor = int(binstr[0]) ^ int(binstr[1]) ^ int(binstr[2]) ^ int(binstr[3]) ^ int(binstr[4]) ^ int(binstr[5]) ^ int(binstr[6])
        for ix in range(7):
            KEY_bin_str += str(binstr[ix])
        KEY_bin_str += str(xor)
    print('随机生成的密钥', p, '为: ', KEY_bin_str, '  ', len(KEY_bin_str), '位')
    return KEY_bin_str

# 检测64位密钥的奇偶校验是否通过
def check_key(key_bin):
    for i in range(0, 64, 8):
        xor = int(key_bin[i]) ^ int(key_bin[i + 1]) ^ int(key_bin[i + 2]) ^ int(key_bin[i + 3]) ^ int(
            key_bin[i + 4]) ^ int(key_bin[i + 5]) ^ int(key_bin[i + 6])
        if xor != int(key_bin[i + 7]):
            return False
    return True

# 密钥置换选择 1
key_table1 = [
    56, 48, 40, 32, 24, 16, 8, 0, 57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35, 62, 54,
    46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 60, 52, 44, 36, 28, 20, 12, 4, 27, 19, 11, 3
]
# 密钥置换选择 2
key_table2 = [
    13, 16, 10, 23, 0, 4, 2, 27, 14, 5, 20, 9, 22, 18, 11, 3, 25, 7, 15, 6, 26, 19, 12, 1, 40, 51, 30, 36, 46, 54, 29,
    39, 50, 44, 32, 47, 43, 48, 38, 55, 33, 52, 45, 41, 49, 35, 28, 31
]
# 初始置换 IP
IP = [
    57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 3, 61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31,
    23, 15, 7, 56, 48, 40, 32, 24, 16, 8, 0, 58, 50, 42, 34, 26, 18, 10, 2, 60, 52, 44, 36, 28, 20, 12, 4, 62, 54,
    46, 38, 30, 22, 14, 6
]
# 逆初始置换
IP_1 = [39, 7, 47, 15, 55, 23, 63, 31, 38, 6, 46, 14, 54, 22, 62, 30, 37, 5, 45, 13, 53, 21, 61, 29, 36, 4, 44, 12, 52,
        20, 60, 28, 35, 3, 43, 11, 51, 19, 59, 27, 34, 2, 42, 10, 50, 18, 58, 26, 33, 1, 41, 9, 49, 17, 57, 25, 32, 0,
        40, 8, 48, 16, 56, 24]
# 选择扩展运算 E
E = [31, 0, 1, 2, 3, 4, 3, 4, 5, 6, 7, 8, 7, 8, 9, 10, 11, 12, 11, 12, 13, 14, 15, 16, 15, 16, 17, 18, 19, 20, 19, 20,
     21, 22, 23, 24, 23, 24, 25, 26, 27, 28, 27, 28, 29, 30, 31, 0]
# 置换运算 P
P = [15, 6, 19, 20, 28, 11, 27, 16, 0, 14, 22, 25, 4, 17, 30, 9, 1, 7, 23, 13, 31, 26, 2, 8, 18, 12, 29, 5, 21, 10, 3,
     24]
# S盒
sbox = [
    # S1
    [14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7, 0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8,
     4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0, 15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13],
    # S2
    [15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10, 3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5,
     0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15, 13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9],
    # S3
    [10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8, 13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,
     13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7, 1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12],
    # S4
    [7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15, 13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,
     10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4, 3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14],
    # S5
    [2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9, 14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,
     4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14, 11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3],
    # S6
    [12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11, 10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,
     9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6, 4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13],
    # S7
    [4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1, 13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,
     1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2, 6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12],
    # S8
    [13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7, 1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,
     7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8, 2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11],
]

l = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1]  # 循环左移位数

# 密钥置换选择1
def key_ds1(key):
    s = ''
    for i in key_table1:
        s += key[i]
    return s

# 密钥置换选择2
def key_ds2(key):
    s = ''
    for i in key_table2:
        s += key[i]
    return s

# 密钥循环左移
def key_move(key, r):
    s = key
    for i in range(l[r]):
        s = s[1:] + s[:1]
    return s

# 通过64位密钥获取全部的子密钥
def get_subkey(key_bin):
    # 首先进行key_bin的奇偶校验检查
    if not check_key(key_bin):
        print('密钥奇偶校验不通过!')
        return
    key = []
    # 密钥置换选择1
    key1_res = key_ds1(key_bin)
    L = key1_res[:28]
    R = key1_res[28:]
    for i in range(16):
        # 循环左移
        L = key_move(L, i)
        R = key_move(R, i)
        # 密钥置换选择2
        key.append(key_ds2(L + R))
    return key

# 扩展置换E
def extend_E(R):
    r = ''
    for i in E:
        r += R[i]
    return r

# 代换选择S盒
def alter_s(t):
    j = 0
    res = ''
    for i in range(0, 48, 6):
        c = int(t[i + 1:i + 5], 2)
        r = int(t[i] + t[i + 5], 2)
        res += '{:04b}'.format((sbox[j][r * 16 + c]))
        j += 1
    # print(res)
    return res

# P置换
def p_repl(s):
    p = ''
    for i in P:
        p += s[i]
    return p

# DES加密基本函数,输入64位明文和密钥
def DES(M, key):
    # 首先将明文进行初始置换IP
    m = ''
    for i in IP:
        m += M[i]
    L = []
    R = []
    # print('m=',m)
    L.append(m[:32])
    R.append(m[32:])
    # 16轮feistel结构
    for i in range(16):
        L.append(R[i])
        # 将R进行扩展置换E
        R_extend = extend_E(R[i])
        # 异或子密钥 K(i)
        t = '{:048b}'.format(int(R_extend, 2) ^ int(key[i], 2))
        # 代换选择S盒
        s = alter_s(t)
        # P置换
        p = p_repl(s)
        # 异或L(i-1)
        r = '{:032b}'.format(int(p, 2) ^ int(L[i], 2))
        R.append(r)
    # 左右交换
    c = R[16] + L[16]
    # 逆初始置换
    cipher = ''
    for i in IP_1:
        cipher += c[i]
    return cipher

# DES加密函数
def DES_encrypt(message_bin_data, sub_key, i):
    print('正在利用密钥', i, '对数据进行DES加密')
    ciphertext = DES(message_bin_data, sub_key)
    # print('加密后得到的二进制流为:', ciphertext, '   ', len(ciphertext), '位')
    return ciphertext

# DES解密函数
def DES_decrypt(ciphertext, key, i):
    print('正在利用密钥', i, '对数据进行DES解密')
    plainbin = DES(ciphertext, key)
    return plainbin

# 三重DES加密
def DES_3_encrypt(s, sub_key1, sub_key2, sub_key3):
    sub_key2 = sub_key2[::-1]  # 解密密钥
    # print("***   正在进行三重DES加密   ***")
    ciphertext = DES_encrypt(s, sub_key1, 1)
    ciphertext = DES_decrypt(ciphertext, sub_key2, 2)
    ciphertext = DES_encrypt(ciphertext, sub_key3, 3)
    return ciphertext

# 三重DES解密
def DES_3_decrypt(ciphertext, sub_key1, sub_key2, sub_key3):
    # print("***   正在进行三重DES解密   ***")
    sub_key1 = sub_key1[::-1]
    sub_key2 = sub_key2  # 加密密钥
    sub_key3 = sub_key3[::-1]
    ciphertext = DES_decrypt(ciphertext, sub_key3, 3)
    ciphertext = DES_encrypt(ciphertext, sub_key2, 2)
    plaintext = DES_decrypt(ciphertext, sub_key1, 1)
    print("***    三重DES解密成功   ***")
    return plaintext


# CBC加密
def CBC_encrypt(group_list, iv, key_bin1, key_bin2, key_bin3):
    print("***   正在获取子密钥   ***")
    # 获取子密钥
    sub_key1 = get_subkey(key_bin1)
    sub_key2 = get_subkey(key_bin2)
    sub_key3 = get_subkey(key_bin3)
    C_list = []
    Iv = iv
    print("***   正在进行CBC加密   ***")
    # 进行CBC加密
    for group in group_list:
        xor_res = '{:064b}'.format(int(group, 2) ^ int(Iv, 2))
        C = DES_3_encrypt(xor_res, sub_key1, sub_key2, sub_key3)
        C_list.append(C)
        Iv = C
    return C_list

# 存储加密后的分组信息
def save_img_data(list_data):
    filename = 'encrypt_data.txt'
    f = open(filename, 'w')
    for i in list_data:
        f.write(i + '\n')
    f.close()
def save_iv_data(iv):
    filename = 'iv.txt'
    f = open(filename, 'w')
    f.write(iv + '\n')
    f.close()
def save_keys(key_bin1, key_bin2, key_bin3):
    filename = 'keys.txt'
    f = open(filename, 'w')
    f.write(key_bin1 + '\n')
    f.write(key_bin2 + '\n')
    f.write(key_bin3 + '\n')
    f.close()

# 获取加密图像的像素信息
def get_img_data():
    img_data = ''
    filename = 'encrypt_data.txt'
    f = open(filename, 'r')
    for line in f:
        img_data += (line.strip('\n'))
    f.close()
    return img_data

# 基于三重DES的加密图像
def DES3_encryption():
    global panelB
    # 获取图像信息
    # img = encryption_img["encryption"]
    # encryptionImg = np.zeros(img.shape, np.uint8)
    path = encryption_img["ImgPath"]
    print("***   正在获取图像信息   ***")
    bin_data, img_width, img_height = get_pixel(path)
    bin_data_list = set_group(bin_data)
    # 保存明文分组信息,存储在group.txt文件中
    print("*** 保存明文分组信息成功 ***")
    print("*** 明文分组信息存储在group.txt文件中 ***")
    save_group_data(bin_data_list)
    group_list = get_group()  # 获取分组列表
    iv = get_IV()  # 获取初始向量IV
    # 随机生成3个DES密钥
    key_bin1 = get_rand_key(1)
    key_bin2 = get_rand_key(2)
    key_bin3 = get_rand_key(3)
    c_list = CBC_encrypt(group_list, iv, key_bin1, key_bin2, key_bin3)  # 进行CBC模型下的三重DES加密
    # CBC模型下的三重DES解密
    save_img_data(c_list)  # 保存加密后分组数据便于 4密文图像显示
    save_iv_data(iv)  # 保存初始向量IV信息用于解密
    save_iv_data(iv)  # 保存初始向量IV信息用于解密
    # 保存解密数据
    save_keys(key_bin1, key_bin2, key_bin3)  # 保存密钥信息

    img_data = get_img_data()
    img = Image.new('RGB', (img_width, img_height))
    c = 0
    for i in range(img_width):
        for j in range(img_height):
            r = int(img_data[c:c + 8], 2)
            g = int(img_data[c + 8:c + 16], 2)
            b = int(img_data[c + 16:c + 24], 2)
            # print('绘制加密图片',i,j,r,g,b,sep='  ')
            img.putpixel((i, j), (r, g, b))
            c += 24
    arrayImage = img.resize((320, 230), Image.BILINEAR)
    encryptionImg = ImageTk.PhotoImage(arrayImage)
    panelB = Label(image=encryptionImg)
    panelB.image = encryptionImg
    panelB.place(x=(800 - panelB.winfo_reqwidth() - 10), y=(500 - panelB.winfo_reqheight()) / 2)
    print("-----加密完成-----")

结果如下:

数字图像解密

数字图像解密按钮,当我们点击之后也是呈现一个窗口,这里撰写两种对图像进行解密的方式,ok接下来我们首先实现基本窗口的设置:

窗口布局设置

图像加密窗口的布局样式与主窗口的布局样式一致,这里就不再赘述了,具体的代码如下:

python 复制代码
# 第二个窗口
def Two():
    # 函数内部使用全局变量decrypt
    global decrypt
    # 将全局变量decrypt的值设置为True,以表示已经进行了加密操作
    decrypt = True
    # 创建子窗口布局
    root2 = Tk()
    # 设置窗口大小为 300x200 像素,并将其放置在屏幕位置 (600, 200)
    root2.geometry('800x500+600+200')
    # 设置标题
    root2.title('数字图像解密')
    Label(root2, text='功能选择', bg='#008c8c').pack(fill=X)

    # 进入当前窗口的子窗体回调函数
    def goto(num):
        # 关闭主窗口
        root2.destroy()
        # 使用字典来映射数字和对应的窗口函数,然后在 goto 函数中进行查找和调用,如果找到则调用该函数,否则不执行任何操作。
        windows = {
            1: Two_one,
            2: Two_two,
        }
        func = windows.get(num, lambda: None)
        if func:
            func()
    # 设置窗口布局
    btn1 = Button(root2, text="返回主窗体", command=lambda: gotoMain(root2))
    btn1.pack(side="bottom", pady=10)
    btn2 = Button(root2, text="基于混沌Logistic的图像解密", font=('Arial', 12), command=lambda: goto(1))
    btn2.pack(pady=(115, 15))
    btn3 = Button(root2, text="基于三重DES的图像解密", font=('Arial', 12), command=lambda: goto(2))
    btn3.pack(pady=15)
    btn4 = Button(root2, text="退出系统", font=('Arial', 12), command=lambda: LogDown(root2))
    btn4.pack(pady=15)
    root2.mainloop()

从上面的代码中,我们设置了一个全局变量用于表达图像是否处在加密状态中:

python 复制代码
# 定义全局变量
decrypt = False  # 表示未解密状态

最终呈现的结果如下:

基于混沌Logistic的图像解密

我们需要在全局声明变量用于存储解密的相关图片信息:

python 复制代码
decrypt_img = { # 解密
    "decrypt": np.array([]), "DecryptionImg": np.array([]), "ImgPath": "", "width": None, "height": None
}

当我们点击上传图像上传我们之前用该算法加密后的图片,然后点击解密图像即可:

这里给出具体的代码:

python 复制代码
# 基于混沌Logistic的解密图像
def Logistical_decrypt():
    global panelB
    encryptionImg = decrypt_img["decrypt"]
    decryptionImg = np.zeros(encryptionImg.shape, np.uint8)
    x0 = 0.15624562
    g0 = 164
    j0 = 10
    x = encryptionImg.shape[0]
    y = encryptionImg.shape[1]
    c = encryptionImg.shape[2]
    g0 = int2bin16(g0)
    for s in range(x):
        for n in range(y):
            for z in range(c):
                cc = int2bin8(encryptionImg[s][n][z])
                ans = ""
                for i in range(8):
                    xi = 1 - math.sqrt(abs(2 * x0 - 1))
                    x0 = xi
                    ssi = math.floor(xi * (2 ** j0)) % 2
                    qi = 1 - (ssi ^ int(cc[i]))
                    ri = int(g0[-1])
                    mi = ri ^ qi
                    t = int(g0[0]) ^ int(g0[12]) ^ int(g0[15])
                    g0 = str(t) + g0[0:-1]
                    ans += str(mi)
                re = int(ans, 2)
                # 写回16位二进制数
                decryptionImg[s][n][z] = re
    decrypt_img["DecryptionImg"] = decryptionImg
    rgb = cv2.cvtColor(decryptionImg, cv2.COLOR_BGR2RGB)
    new_width = 320
    scale_factor = new_width / rgb.shape[1]
    new_height = int(rgb.shape[0] * scale_factor)
    resize_image0 = cv2.resize(rgb, dsize=(new_width, new_height), interpolation=cv2.INTER_LINEAR)
    arrayImage = Image.fromarray(np.uint8(resize_image0))
    decryptionImg = ImageTk.PhotoImage(arrayImage)
    panelB = Label(image=decryptionImg)
    panelB.image = decryptionImg
    panelB.place(x=(800 - panelB.winfo_reqwidth() - 10), y=(500 - panelB.winfo_reqheight()) / 2)
    print("-----解密完成-----")

最终得到的效果如下:

然后另存图像进行保存即可,这里不再赘述,功能和上面类似。

基于三重DES的图像解密

我们需要在全局声明变量用于存储解密的相关图片信息:

python 复制代码
decrypt_img = { # 解密
    "decrypt": np.array([]), "DecryptionImg": np.array([]), "ImgPath": "", "width": None, "height": None
}

当我们点击上传图像上传我们之前用该算法加密后的图片,然后点击解密图像即可:

具体代码如下:

python 复制代码
# 获取密钥信息函数
def get_keys():
    filename = 'keys.txt'  # 密钥文件
    f = open(filename, 'r')
    keys = []
    for line in f:
        keys.append(line.strip('\n'))
    return keys[0], keys[1], keys[2]

# 获取iv信息
def get_iv():
    filename = 'iv.txt'  # iv文件
    f = open(filename, 'r')
    return f.readline().strip('\n')

# CBC解密
def CBC_decrypt(C_list, iv, key_bin1, key_bin2, key_bin3):
    sub_key1 = get_subkey(key_bin1)
    sub_key2 = get_subkey(key_bin2)
    sub_key3 = get_subkey(key_bin3)
    P_list = []
    Iv = iv
    for c in C_list:
        res = DES_3_decrypt(c, sub_key1, sub_key2, sub_key3)
        xor_res = '{:064b}'.format(int(res, 2) ^ int(Iv, 2))
        P_list.append(xor_res)
        Iv = c
    img_data = ''.join(P_list)
    return img_data

# 基于三重DES的解密图像
def DES3_decrypt():
    global decrypt_img, panelB
    path = decrypt_img["ImgPath"]
    # print('***   正在进行密文图像解密   ***')
    bin_data, img_width, img_height = get_pixel(path)  # 获取加密后图像的像素值和宽高
    # print('***   获取加密图像数据成功!   ***')
    key_bin1, key_bin2, key_bin3 = get_keys()  # 获取密钥信息
    # print('***   获取密钥信息成功!   ****')
    iv = get_iv()  # 获取初始向量IV
    bin_data_list = set_group(bin_data)  # 对加密数据进行分组
    img_data = CBC_decrypt(bin_data_list, iv, key_bin1, key_bin2, key_bin3)  # 获取解密的分组信息
    decrypt_img["DecryptionImg"] = img_data

    img = Image.new('RGB', (img_width, img_height))
    c = 0
    for i in range(img_width):
        for j in range(img_height):
            r = int(img_data[c:c + 8], 2)
            g = int(img_data[c + 8:c + 16], 2)
            b = int(img_data[c + 16:c + 24], 2)
            # print('绘制加密图片',i,j,r,g,b,sep='  ')
            img.putpixel((i, j), (r, g, b))
            c += 24
    arrayImage = img.resize((320, 230), Image.BILINEAR)
    decryptionImg = ImageTk.PhotoImage(arrayImage)
    panelB = Label(image=decryptionImg)
    panelB.image = decryptionImg
    panelB.place(x=(800 - panelB.winfo_reqwidth() - 10), y=(500 - panelB.winfo_reqheight()) / 2)
    print('-------解密成功---------')

最终呈现的效果如下:

最后对两个图像解密操作进行图片保存处理:

python 复制代码
# 绘制解密图像信息
def draw_image(img_data, img_filename, img_width, img_height):
    img = Image.new('RGB', (img_width, img_height))
    c = 0
    for i in range(img_width):
        for j in range(img_height):
            r = int(img_data[c:c + 8], 2)
            g = int(img_data[c + 8:c + 16], 2)
            b = int(img_data[c + 16:c + 24], 2)
            # print('绘制加密图片',i,j,r,g,b,sep='  ')
            img.putpixel((i, j), (r, g, b))
            c += 24
    img.save(img_filename)

# 保存加密图片
def SaveDES3Img():
    # 通过加密后的分组信息绘制密文图像
    img_data = get_img_data()
    filePath = filedialog.askdirectory(title='open')
    draw_image(img_data, filePath + '/encrypted_img.png', encryption_img["width"], encryption_img["height"])
    print("-----保存完成-----")

# 保存解密图片
def SaveDES3DecryptImg():
    filePath = filedialog.askdirectory(title='open')
    draw_image(decrypt_img["DecryptionImg"], filePath+'/decrypted_img.png', decrypt_img["width"], decrypt_img["height"])
    print("-------保存成功------")

基于LSB的数字水印提取

LSB数字水印是一种常见的数字水印技术,通过将隐藏信息嵌入到图像的最低有效位中,实现对图像进行隐秘性的信息嵌入和提取。

用户可以在界面上方便地输入待提取的水印信息,并通过点击按钮来执行水印提取操作,从而实现基于LSB的数字水印提取功能。这种方法简单有效,适用于一些对水印提取速度要求不高、对图像质量要求不严格的应用场景。以下是数字水印实现的具体步骤:

窗口布局设置

这里我们在这里创建第三个窗口用于展示数字水印的功能,窗口布局的代码如下:

python 复制代码
# 第三个窗口
def Three():
    root3 = Tk()
    root3.geometry('800x500+600+200')
    root3.title('数字水印处理')
    Label(root3, text='功能选择', bg='pink').pack(fill=X)

    # 进入当前窗口的子窗体回调函数
    def goto(num):
        # 关闭主窗口
        root3.destroy()
        # 使用字典来映射数字和对应的窗口函数,然后在 goto 函数中进行查找和调用,如果找到则调用该函数,否则不执行任何操作。
        windows = {
            1: Three_one,
            2: Three_two,
        }
        func = windows.get(num, lambda: None)
        if func:
            func()
    but1 = Button(root3, text="返回主窗体", command=lambda: gotoMain(root3))
    but1.pack(side="bottom", pady=10)
    but2 = Button(root3, text="嵌入数字水印", font=('Arial', 12), command=lambda: goto(1))  # 进入窗口3-1
    but2.pack(pady=(115, 15))
    but3 = Button(root3, text="提取数字水印", font=('Arial', 12), command=lambda: goto(2))  # 进入窗口3-2
    but3.pack(pady=15)
    but4 = Button(root3, text="退出系统", font=('Arial', 12), command=lambda: LogDown(root3))
    but4.pack(pady=15)

再创建一些子窗口用于图片水印的嵌入与提取:

最终展示的效果如下所示:

水印的嵌入与提取

下面这段代码实现了一个简单的基于LSB(Least Significant Bit,最低有效位)的数字水印嵌入和提取功能。下面是代码的实现思路:

嵌入水印回调函数 embed_watermark:

1)用户在输入框中输入待嵌入的水印信息。

2)用户选择一张要嵌入水印的图像文件(支持png、jpg、jpeg格式)。

3)打开选定的图像文件,并将其转换为RGB格式。

4)将用户输入的水印文本转换为二进制字符串。

5)检查水印长度是否超过图像容量,若超过则提示水印过长。

6)遍历每个像素位置,将水印信息的每一位嵌入到图像的红色通道的最低有效位。

7)保存嵌入水印后的图像文件(支持用户选择保存路径)。

python 复制代码
# 嵌入水印回调函数
def embed_watermark(entry_text, label):
    # 选择要嵌入水印的图像文件
    image_path = filedialog.askopenfilename(filetypes=[("Image Files", "*.png;*.jpg;*.jpeg")])
    if not image_path:
        return

    # 打开图像文件
    image = Image.open(image_path).convert('RGB')
    width, height = image.size

    # 输入水印文本
    watermark = entry_text

    # 将水印转换为二进制字符串
    binary_watermark = ''.join(format(ord(c), '08b') for c in watermark)

    # 检查水印长度是否超过图像容量
    if len(binary_watermark) > width * height * 3:
        label["text"] = "Watermark is too long for the selected image."
        return

    # 嵌入水印
    for i in range(len(binary_watermark)):
        x = i % width
        y = i // width

        pixel = list(image.getpixel((x, y)))
        bit = int(binary_watermark[i])

        # 将最低有效位设置为水印比特
        pixel[0] = (pixel[0] & 0xFE) | bit

        image.putpixel((x, y), tuple(pixel))

    # 保存嵌入水印后的图像
    save_path = filedialog.asksaveasfilename(defaultextension=".png", filetypes=[("PNG Image", "*.png")])
    if save_path:
        image.save(save_path)
        label["text"] = "Watermark embedded and image saved successfully."

实现的效果如下所示:

提取水印函数 extract_watermark:

1)用户选择一张含有水印的图像文件(支持png、bmp、jpg、jpeg格式)。

2)打开选定的图像文件,并将其转换为RGB格式。

3)遍历每个像素位置,提取图像红色通道的最低位作为水印信息。

4)将提取的水印信息转换为二进制字符串,再将二进制字符串转换为字符形式的水印文本。

5)创建一个包含滚动条和文本框的父容器,显示提取的水印文本信息。

python 复制代码
def extract_watermark(root):
    # 选择要提取水印的图像文件
    image_path = filedialog.askopenfilename(filetypes=[("Image Files", "*.png;*.bmp;*.jpg;*.jpeg")])
    if not image_path:
        return

    # 打开图像文件
    image = Image.open(image_path).convert('RGB')
    width, height = image.size

    watermark = ""

    # 提取水印
    for y in range(height):
        for x in range(width):
            pixel = list(image.getpixel((x, y)))
            bit = pixel[0] & 0x01  # 提取红色通道的最低位

            watermark += str(bit)

    # 将二进制字符串转换为字符
    extracted_text = "".join(chr(int(watermark[i:i+8], 2)) for i in range(0, len(watermark), 8))

    # 创建滚动条和文本框的父容器
    frame = Frame(root)
    frame.pack(expand=True, fill="both")

    scrollbar = Scrollbar(frame)
    scrollbar.pack(side="right", fill="y")

    text_area = Text(frame, height=10, width=40)
    text_area.insert("1.0", "Extracted Watermark:\n" + extracted_text)
    text_area.pack(side="left", expand=True, fill="both")

    # 将滚动条与文本框绑定
    text_area.config(yscrollcommand=scrollbar.set)
    scrollbar.config(command=text_area.yview)

实现的效果如下所示:

相关推荐
waicsdn_haha6 分钟前
Java/JDK下载、安装及环境配置超详细教程【Windows10、macOS和Linux图文详解】
java·运维·服务器·开发语言·windows·后端·jdk
_WndProc8 分钟前
C++ 日志输出
开发语言·c++·算法
qq_4335545416 分钟前
C++ 面向对象编程:+号运算符重载,左移运算符重载
开发语言·c++
qq_5290252935 分钟前
Torch.gather
python·深度学习·机器学习
数据小爬虫@35 分钟前
如何高效利用Python爬虫按关键字搜索苏宁商品
开发语言·爬虫·python
ZJ_.37 分钟前
WPSJS:让 WPS 办公与 JavaScript 完美联动
开发语言·前端·javascript·vscode·ecmascript·wps
Narutolxy43 分钟前
深入探讨 Go 中的高级表单验证与翻译:Gin 与 Validator 的实践之道20241223
开发语言·golang·gin
Hello.Reader1 小时前
全面解析 Golang Gin 框架
开发语言·golang·gin
禁默1 小时前
深入浅出:AWT的基本组件及其应用
java·开发语言·界面编程
Cachel wood1 小时前
python round四舍五入和decimal库精确四舍五入
java·linux·前端·数据库·vue.js·python·前端框架