tkinter+socket&MySQL+keras识别minst手写数字


网络编程课设,要求自己想方向自己做,附代码+实验报告。

环境配置

python 3.7.3
pymysql 1.0.2
tensorflow-gpu 2.0.0(cpu也可)
numpy 1.19.2
tk 8.6.10

MySQL 8.0.23
Navicat 版本
都不关键,环境设置主要跟tf有关

代码

点击可下载github代码

服务端

需要的神经网络模型cnn.h5可由其他训练得到,可以在最下面参考文献中从github下下来我改写的原型。

import socket
import pickle
import numpy
import pymysql
from tensorflow import keras

# 默认tcp方式传输
sk = socket.socket()
sk2 = socket.socket()
# 绑定IP与端口
ip_port = ('192.168.43.31', 8888)
ip_port2 = ('192.168.43.31', 8889)
# 绑定监听
sk.bind(ip_port)
sk2.bind(ip_port2)
# 最大连接数
sk.listen(5)
sk2.listen(5)
#加载训练好的cnn模型
model=keras.models.load_model("cnn.h5")
#连接至本地数据库
con = pymysql.connect(host='localhost',
                       user='root',
                       password='0208',
                       db='client_list',
                       charset='utf8')
cursor = con.cursor()
log_in = 0

def send_from(arr, dest):
  view = memoryview(arr).cast('B')
  while len(view):
    nsent = dest.send(view)
    view = view[nsent:]
 
def recv_into(arr, source):
  view = memoryview(arr).cast('B')
  while len(view):
    nrecv = source.recv_into(view)
    view = view[nrecv:]

# 不断循环接收数据
while True:
    # 提示信息
    print("正在等待接收数据。。。。。。。")
    # 接收数据  连接对象和客户地址
    con2, address = sk2.accept()
    while True:
        user = con2.recv(1024).decode()
        sql = "select password from password where username = '" + user + "'"
        cursor.execute(sql)
        #print("searching")
        password=con2.recv(1024).decode()
        for i in cursor.fetchall():#返回一个tuple
            #print(i)
            if password in i:
                log_in = 1
        con2.send(pickle.dumps(log_in))
        if log_in == 1:
            break
    
    
    #登录过程
    conn, address = sk.accept()
    # 不断接收客户发来的消息
    while True:
        # 接收客户端消息
        if log_in == 1:
            print('receiving data')
            data=numpy.zeros(784).reshape(1,28,28,1).astype(float)
            recv_into(data, conn)
            #print(data)
        # 接收到退出指令
            if data.sum() == 0:
                print('EXIT')
                log_in = 0
                break

            rec = model.predict_classes(data)[0]
            conn.send(pickle.dumps(rec))
        
    # 主动关闭连接
    conn.close()

客户端

主函数main.py

# -*- coding:utf-8 -*-
from tkinter import Tk
from Window import Window
import socket

if __name__ == '__main__':
    win=Tk()
    ww = 530#窗口宽设定530
    wh = 430#窗口高设定430
    # 服务端为TCP方式,客户端也采用TCP方式,默认参数即为TCP
    client = socket.socket()
        # 访问服务器的IP和端口
    ip_port= ('192.168.43.31', 8888)
        # 连接主机
    client.connect(ip_port)
    Window(win,ww,wh,client)
    win.protocol("WM_DELETE_WINDOW",Window.closeEvent)
    win.mainloop()

类Window.py

# -*- coding:utf-8 -*-
from tkinter import *
from PIL import Image,ImageDraw
import numpy as np
import pickle
import socket
import sys

def send_from(arr, dest):
  view = memoryview(arr).cast('B')
  while len(view):
    nsent = dest.send(view)
    view = view[nsent:]
 
def recv_into(arr, source):
  view = memoryview(arr).cast('B')
  while len(view):
    nrecv = source.recv_into(view)
    view = view[nrecv:]


class Window:
    def __init__(self,win,ww,wh,client):
        self.win=win
        self.ww=ww
        self.wh=wh
        self.client=client
        self.win.geometry("%dx%d+%d+%d" %(ww,wh,500,100))
        self.win.title("手写数字识别软件---by 20180620山椒鱼")
        self.can=Canvas(self.win,width=ww-130,height=wh-32,bg='white')
        self.can.place(x=0,y=0)
        self.can.bind("<B1-Motion>",self.paint)
        
        self.entry1=Entry(self.win,width=16)
        self.entry1.place(x=405,y=200)
        self.entry2=Entry(self.win,width=16)
        self.entry2.place(x=405,y=260)
        
        self.label1=Label(self.win,text="识别结果:",font=('微软雅黑',20))
        self.label1.place(x=405,y=0)
        self.label2=Label(self.win,width=6,height=2,text='',font=('微软雅黑',20),
                          background="white",relief="ridge",borderwidth=10)
        self.label2.place(x=405,y=50)
        self.label3=Label(self.win, text="用户名:",font=('微软雅黑',10))
        self.label3.place(x=405,y=170)
        self.label4=Label(self.win, text="密码:",font=('微软雅黑',10))
        self.label4.place(x=405,y=230)

        self.button1=Button(self.win,text="Predict",width=10,height=1,bg='gray',command=self.predict)
        self.button1.place(x=10,y=wh-30)
        self.button2=Button(self.win,text="Clear",width=10,height=1,bg='white',command=self.clear)
        self.button2.place(x=100,y=wh-30)
        self.button3=Button(self.win,text="Exit", width=10,height=1,bg='white',command=self.exit_)
        self.button3.place(x=200,y=wh-30)
        self.button4=Button(self.win,text="Log in", width=10,height=1,bg='white',command=self.log_in)
        self.button4.place(x=280,y=wh-30)

        self.image=Image.new("RGB",(ww-130,wh-30),color=(0,0,0))#(0,0,0)表示黑色背景
        self.draw=ImageDraw.Draw(self.image)
        self.valid_flag = 0
        
        self.client2 = socket.socket()
        # 访问服务器的IP和端口
        self.ip_port2= ('192.168.43.31', 8889)
        # 连接主机
        self.client2.connect(self.ip_port2)
        
    def log_in(self):
        user=self.entry1.get().encode()
        password=self.entry2.get().encode()
        self.client2.send(user)
        self.client2.send(password)
        _data=self.client2.recv(1024)
        self.valid_flag=pickle.loads(_data)       
        if self.valid_flag == 1:    
            messagebox.showinfo(title='登陆成功', message='欢迎!')
        else: 
            messagebox.showwarning(title='登录失败', message='用户名或密码错误!')
            
    def paint(self,event):
        self.x1,self.y1=event.x-12,event.y-12
        self.x2,self.y2=(self.x1+24),(self.y1+24)
        self.can.create_rectangle(self.x1, self.y1, self.x2, self.y2, fill="black")
        self.draw.rectangle(((self.x1,self.y1),(self.x2,self.y2)),(255,255,255))#(255,255,255)表示白色字

    def predict(self):
        if self.valid_flag== 0 :
            messagebox.showwarning(title='警告', message='请先登录')
        else:
            if np.array(self.image).sum()==0:#检测到还没进行手写就预测,显示预测结果为空
                self.display('')
            else:
                self._image=self.image.resize((28,28),Image.ANTIALIAS).convert('L')
                self._image=np.array(self._image).reshape(1,28,28,1).astype(float)#训练时shape为(-1,28,28,1)
                #self.rec=self.model.predict_classes(self._image.astype(float))[0]
                send_from(self._image, self.client)
                _data = self.client.recv(1024)
                data = pickle.loads(_data)
                #self.client.send(pickle.dumps(self._image))
                self.display(data)#显示预测结果
                self.draw=ImageDraw.Draw(self.image)

    def clear(self):
        self.can.delete("all")
        self.image=Image.new("RGB",(self.ww-130,self.wh-30),(0,0,0))#(0,0,0)表示黑色背景
        self.draw=ImageDraw.Draw(self.image)
        self.display('')

    def display(self,string):
        self.label2=Label(self.win,width=6,height=2,text=string,font=('微软雅黑',20),
                          background="white",relief="ridge",borderwidth=10)
        self.label2.place(x=405,y=50)

    def exit_(self):
        if self.valid_flag ==1:
            _image=np.zeros(784).reshape(1,28,28,1).astype(float)
            send_from(_image, self.client)
        self.client2.close()
        self.win.destroy()

    def closeEvent():
        Window.win.destroy()
        sys.exit()

实验报告部分

一、总体功能说明

1.功能概述

人工智能中的神经网络正在社会各个领域正在得到越来越广泛的应用。单机模式下采用人工神经网络处理数据的方法,不仅需要将庞大的训练完成的神经网络模型嵌入到程序中,更需在每次启动程序时加载神经网络至内存中,耗费时间。因此,我们以识别mnist数据集为例,原创编写了一套基于局域网的客户-服务端模式的手写数字神经网络识别程序。

2.功能总图

客户端黄色&服务端绿色
在这里插入图片描述

3.各子功能流程图

3.1服务端初始化

在这里插入图片描述

Include必须包: socket, numpy, pickle, pymysql, tensorflow中keras。
Tcp协议绑定监听两端口: tcp方式,绑定服务器端ip地址与两个端口(一个用于用户登录查询数据库,一个用于发送识别的手写数字),绑定监听。
加载神经网络训练模型: 通过keras包加载训练好的.h5神经网络模型。
链接至本地数据库:通过pymysql连接至本地数据库,并获取数据库游标。
登录状态置否:为否时客户机无法通过另一端口发送检测信息。同时弹窗。

3.2客户端初始化

在这里插入图片描述
Include必须包: socket, numpy, pickle, tkinter, PIL。
Tcp协议绑定监听两端口: tcp方式,连接服务器端ip地址的两个端口(一个用于用户登录查询数据库,一个用于发送识别的手写数字)。成功则不报错。
创建可视化界面: 通过tkinter创建可视化界面,包括功能按钮、提示信息、文本框等。设置可视化程序界面的宽高,绑定各个函数。
登录状态置否:为否时客户机无法通过另一端口发送检测信息。同时弹窗。

3.3用户登录+数据库查询

在这里插入图片描述
详细说明见 二、数据库部分说明 三、通信部分说明

3.4交互式画图(客户端paint函数、clear函数)、

在这里插入图片描述

鼠标落下、移动刷新时:初始化时将canvas画布绑定为鼠标左击有效,左击时调用paint函数,跟据捕获到的鼠标事件(event)的落点,不断叠加地在canvas画布上画黑框。
Clear按钮按下时:清空canvas画布,并将label2预测结果也清空。

3.5通过神经网络识别与检测手写数字(客户端predict函数)

在这里插入图片描述

详细数据流图等见 三、通信部分说明

3.6退出与切断链接(客户端exit函数)

在这里插入图片描述
详细数据流图等见 三、通信部分说明

二、数据库功能说明

1.数据库概述

数据库用于模拟存储用户名与密码信息。数据库选用MySQL数据库,在服务端使用,客户端不调取数据库。数据库创建(用户信息录入)与可视化使用Navicat软件,在python中调用数据库使用pymysql拓展包中函数。

2.数据库E-R图

在这里插入图片描述

3.数据字典

字段名数据类型默认值允许非空自动递增备注
usernamestringNO0用户名
passwordstringNO0密码

4.数据流图

在这里插入图片描述

三、通信部分说明

1.通信安全

为了保证通信安全,我们在程序开发时做出了如下考虑安排:

将数据库布置为服务端本地数据库,而非客户端通过3306端口访问服务端数据库。服务端本地访问数据库,组织数据库网络连接,可以确保服务器用户名、密码不外泄。
在客户端与服务端均设置有登录成功标志flag。服务端通过端口1(程序中为8888)收到用户端发来信息后,在数据库中查询到密码正确后,返回正确标志,并将服务端该客户的登录标志置为正确;如查询到密码错误则返回错误标志。客户端收到标志后跟据标志置设置登录成功标志。当且仅当服务端标志为正确时,才开始从端口2(程序中为8889)接受数据。当客户端登录标志不为正确时,预测则不会向服务端通过端口2发送numpy数组,此时点击exit按键函数也不会向服务端发送空数组要求断开链接,而是直接断开端口1的链接(因为此时端口2还未建立链接)。同时设置登陆成功标志意在防止客户端在未登录的情况下便向服务端发送待识别数据。

2.通信方式

联网:客户端与服务端共连手机热点局域网,实验中服务端IP地址为192.168.43.31。使用两个端口,其中登录消息确认占用一个端口(8888),待识别numpy数组发送与识别信息返回采用另一个端口(8889)。数据库采用本地数据库,不连接网络。(但是我们已经实现过局域网内连接数据库,不采用网络数据库仅为确保通信安全。
通信协议:采用python中socket包封装的通信协议。Socket提供TCP与UDP两种协议发送方式,本程序采用TCP协议发送数据。发送数据需要以字节流(byte类)方式发送,本程序采用了pickle包将int类打包发送短消息;对于超出发送TCP报文长度限制的28*28 float类型的numpy数组,采用逐内存读取发送的方式。对于字符串(用户名/密码),用python字符串类自带的.encode()与.decode()方法转化为字节流。

四、测试

1.功能测试

程序总体完整,缺陷有如下两点:
(1)必须按程序内exit键退出,无法按右上角X退出。因为链接建立封装在类内部,无法在按X销毁窗体时调用链接,因此按x会造成链接的意外中断。

(2)在未登录时,按exit退出会造成用于登录的链接意外断开,无法下次登录再建立链接。
可能改进方法:将链接的建立步骤移至类外,或通过继承的方式将链接断开放置子类中,窗体结构作为父类。

2.效率测试

由于笔记本数量限制,程序只测试过一台笔记本电脑连接,预计多客户端连接时仍然会有bug。
我们将待识别的手写数字在客户端经过预处理,从160X160的数组压缩成需要投入到神经网络的28*28的数组后再发送,大大提升数据传输的效率。

其他想说

学校的网络编程课设,要求小组想一个题目自己做,赶在最后一天ddl写的。验收除了UI只要能通过网络(socket)发送数据就行。记得要关防火墙。mysql数据库要自己建一下,改下服务端的语句就可以。bug还有很多,懒得修辣!~

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值