ObjectiveC中的self.变量和_变量的区别

本文通过实例解析了Objective-C中@property属性的重写方法,并解释了self与下划线变量的区别,以及如何避免在重写setter方法时出现的常见错误。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今天因需要重写@property属性的set方法,没想到程序编译通过了,却运行不了,set方法一直报错,错误如下:
这里写图片描述

一开始真是搞不明白啊,都能改用别的方法,不重载这个东东了,烦死了!不过烦归烦,问题终归还是要解决的。我看着代码,突然觉得这个self.变量是不是不对呢?于是我把set函数里self改成了下划线,成功运行了!!太棒了!

这里就解释一下原因,我们都知道,在苹果的官方源码中,大量使用了下划线,那为什么苹果会使用下划线而不是self呢?这里有篇博客大家可以参考一下,说得比较清楚,不过可能在ARC环境下不再适用了吧。http://blog.sina.com.cn/s/blog_7b9d64af0101923n.html

还有一点需要了解的就是@property定义的属性编译器会自动编写getter和setter方法,当然,getter和setter方法也可以根据需要重写。
先给出例子:
Test.h
Test.h

Test.m
Test.m

测试代码
测试代码

运行结果
运行结果

从这个简单的例子中,很清楚可以知道,利用self改变变量值,其实是调用了setter方法,但是下划线这种方式并没有调用setter方法。所以也可以说明了我之前在写setter方法时,在setter方法里使用self肯定会有问题。

在这个简单的测试代码里,我写了四个NSString,主要是为了说明一下,公共变量和私有变量,对初学者来说,一开始很容易混淆的。这四个变量中,只有string2能被外部类访问,其他三个都不能,另外,string2和string4有默认的getter和setter方法,可以使用self和下划线获取变量。string1和string3直接使用string1=XXX或XXX=string1就可以。

这是很简单的一个说明,太本质的东西我暂时也没有弄清楚,记录一下,希望看到这个的人不要再像我一样犯错误!如果有什么不对的对方,欢迎大神们指教!!

#!/usr/bin/env python3 # -*- coding: UTF-8 -*- import rospy import numpy as np from geometry_msgs.msg import WrenchStamped from nav_msgs.msg import Odometry class TutorialNMPCController: def __init__(self): super(TutorialNMPCController, self).__init__() # NMPC控制器参数初始化 self._prediction_horizon = 0 self._control_horizon = 0 self._dt = 0.0 # 权重矩阵 self._Q = np.zeros(shape=(6, 6)) # 状态误差权重 self._R = np.zeros(shape=(6, 6)) # 控制输入权重 self._P = np.zeros(shape=(6, 6)) # 终端状态权重 # 约束参数 self._u_min = np.zeros(shape=(6,)) # 控制输入下限 self._u_max = np.zeros(shape=(6,)) # 控制输入上限 self._x_min = np.zeros(shape=(6,)) # 状态下限 self._x_max = np.zeros(shape=(6,)) # 状态上限 # 其他参数 self._solver_options = {} # 求解器选项 self._warm_start = False # 是否使用热启动 # 控制器状态 self._prev_control = np.zeros(shape=(6,)) self._predicted_states = None self._predicted_controls = None self._solver_status = None self._solve_time = 0.0 self._warm_start_solution = None # 系统状态 self._is_init = False self.odom_is_init = False self.current_odom = None # ROS相关初始化 self._control_pub = rospy.Publisher('thruster_output', WrenchStamped, queue_size=10) self._odom_sub = rospy.Subscriber('odom', Odometry, self._odom_callback) # 从ROS参数服务器加载参数 self._load_parameters() # 验证关键参数 if self._control_horizon <= 0: rospy.logwarn(f"Invalid control_horizon: {self._control_horizon}, setting to default (5)") self._control_horizon = 5 self._is_init = True rospy.loginfo(f"NMPC Controller initialized with control horizon: {self._control_horizon}") rospy.loginfo("NMPC Controller initialized successfully") def _odom_callback(self, msg): """里程计消息回调函数""" self.current_odom = msg self.odom_is_init = True # 提取位置姿态信息 self.current_position = np.array([ msg.pose.pose.position.x, msg.pose.pose.position.y, msg.pose.pose.position.z ]) # 四元数转欧拉角 (简化处理,实际应用需使用完整转换) self.current_orientation = np.array([0, 0, 0]) # 示例,需完善 rospy.logdebug("Received odometry message") def _load_parameters(self): """从ROS参数服务器加载NMPC控制器参数""" if rospy.has_param('~prediction_horizon'): self._prediction_horizon = rospy.get_param('~prediction_horizon') rospy.loginfo(f'Prediction Horizon = {self._prediction_horizon}') else: rospy.logwarn('Parameter prediction_horizon not found, using default') self._prediction_horizon = 10 if rospy.has_param('~control_horizon'): self._control_horizon = rospy.get_param('~control_horizon') rospy.loginfo(f'Control Horizon = {self._control_horizon}') else: rospy.logwarn('Parameter control_horizon not found, using default') self._control_horizon = 5 if rospy.has_param('~dt'): self._dt = rospy.get_param('~dt') rospy.loginfo(f'Sampling Time = {self._dt}') else: rospy.logwarn('Parameter dt not found, using default') self._dt = 0.1 # 加载权重矩阵 # ... 其他参数加载代码保持不变 ... def _reset_controller(self): """重置NMPC控制器状态""" # 重置逻辑保持不变... def update_controller(self): """更新NMPC控制器并发布控制指令""" if not self._is_init: rospy.logwarn("Controller not initialized, skipping update") return False if not self.odom_is_init: rospy.logwarn("Odometry not received yet, skipping update") return False # 获取当前状态参考轨迹 current_state = self._get_current_state() reference_trajectory = self._get_reference_trajectory() # 构建NMPC优化问题 problem = self._construct_optimization_problem(current_state, reference_trajectory) # 求解优化问题 solution, status, solve_time = self._solve_optimization_problem(problem) # 检查求解状态 if status != 'optimal': rospy.logwarn(f"NMPC solver did not converge: {status}") if self._prev_control is not None: # 使用上一次控制输入作为备份 control_input = self._prev_control else: # 如果没有上一次控制输入,则使用零控制 control_input = np.zeros(shape=(6,)) else: # 提取最优控制输入 control_input = self._extract_control_input(solution) self._prev_control = control_input # 保存当前控制输入供下次使用 # 打印控制输入用于调试 rospy.loginfo(f"Control input: {control_input}") # 发布控制指令 self.publish_control_wrench(control_input) # 保存求解信息用于调试 self._solver_status = status self._solve_time = solve_time return True def _get_current_state(self): """获取当前系统状态""" if self.current_odom is None: rospy.logerr("No odometry data available") return np.zeros(6) # 从里程计消息中提取状态 [x, y, z, vx, vy, vz] state = np.array([ self.current_odom.pose.pose.position.x, self.current_odom.pose.pose.position.y, self.current_odom.pose.pose.position.z, self.current_odom.twist.twist.linear.x, self.current_odom.twist.twist.linear.y, self.current_odom.twist.twist.linear.z ]) return state def _get_reference_trajectory(self): """获取参考轨迹""" # 示例:生成一个简单的参考轨迹 (保持当前位置) current_state = self._get_current_state() ref_traj = np.tile(current_state, (self._prediction_horizon + 1, 1)) # 添加一个小的目标偏移 (测试用) ref_traj[:, 0] += 1.0 # x方向偏移1米 return ref_traj def _construct_optimization_problem(self, current_state, reference_trajectory): """构建NMPC优化问题 (需根据具体求解器实现)""" # 实际应用中需替换为真实优化问题构建逻辑 # 这里使用简化示例,返回一个模拟问题 return {"current_state": current_state, "reference": reference_trajectory} def _solve_optimization_problem(self, problem): """求解NMPC优化问题""" # 添加调试日志 rospy.loginfo(f"Solving optimization problem with control horizon: {self._control_horizon}") # 确保 control_horizon 至少为1 control_horizon = max(1, self._control_horizon) # 返回固定控制输入 (x方向100推力) solution = np.tile(np.array([100, 0, 0, 0, 0, 0]), (control_horizon, 1)) rospy.loginfo(f"Solution shape: {solution.shape}") status = 'optimal' solve_time = 0.01 return solution, status, solve_time def _extract_control_input(self, solution): """从优化解中提取控制输入""" if solution is None or solution.size == 0: rospy.logerr("Received empty solution, using default control") return np.array([100, 0, 0, 0, 0, 0]) # 返回默认控制 # 提取第一个控制输入 return solution[0] def publish_control_wrench(self, control_input): """发布控制指令""" msg = WrenchStamped() msg.header.stamp = rospy.Time.now() msg.header.frame_id = 'base_link' msg.wrench.force.x = control_input[0] msg.wrench.force.y = control_input[1] msg.wrench.force.z = control_input[2] msg.wrench.torque.x = control_input[3] msg.wrench.torque.y = control_input[4] msg.wrench.torque.z = control_input[5] self._control_pub.publish(msg) rospy.logdebug(f"Published control wrench: {control_input}") if __name__ == '__main__': print('Tutorial - NMPC Controller') rospy.init_node('tutorial_nmpc_controller') try: node = TutorialNMPCController() # 设置更新频率 rate = rospy.Rate(10000) # 10Hz while not rospy.is_shutdown(): node.update_controller() rate.sleep() except rospy.ROSInterruptException: print('caught exception') print('exiting') 更改这段代码,只保留def __init__(self):、def _reset_controller(self):、def update_controller(self): 、if __name__ == '__main__':、nmpc定义的模型问题求解部分
05-14
#!/usr/bin/env python import rospy import numpy as np from uuv_control_interfaces import DPControllerBase class TutorialDPController(DPControllerBase): def __init__(self): super(TutorialDPController, self).__init__(self) self._Kp = np.zeros(shape=(6, 6)) self._Kd = np.zeros(shape=(6, 6)) self._Ki = np.zeros(shape=(6, 6)) self._int = np.zeros(shape=(6,)) self._error_pose = np.zeros(shape=(6,)) # Do the same for the other two matrices if rospy.get_param('~Kp'): diag = rospy.get_param('~Kp') if len(diag) == 6: self._Kp = np.diag(diag) print 'Kp=\n', self._Kp else: # If the vector provided has the wrong dimension, raise an exception raise rospy.ROSException('For the Kp diagonal matrix, 6 coefficients are needed') if rospy.get_param('~Kd'): diag = rospy.get_param('~Kd') if len(diag) == 6: self._Kd = np.diag(diag) print 'Kd=\n', self._Kd else: # If the vector provided has the wrong dimension, raise an exception raise rospy.ROSException('For the Kd diagonal matrix, 6 coefficients are needed') if rospy.get_param('~Ki'): diag = rospy.get_param('~Ki') if len(diag) == 6: self._Ki = np.diag(diag) print 'Ki=\n', self._Ki else: # If the vector provided has the wrong dimension, raise an exception raise rospy.ROSException('For the Ki diagonal matrix, 6 coefficients are needed') self._is_init = True def _reset_controller(self): super(TutorialDPController, self)._reset_controller() self._error_pose = np.zeros(shape=(6,)) self._int = np.zeros(shape=(6,)) def update_controller(self): if not self._is_init: return False if not self.odom_is_init: return self._int = self._int + 0.5 * (self.error_pose_euler + self._error_pose) * self._dt self._error_pose = self.error_pose_euler tau = np.dot(self._Kp, self.error_pose_euler) + np.dot(self._Kd, self._errors['vel']) + np.dot(self._Ki, self._int) self.publish_control_wrench(tau) if __name__ == '__main__': print('Tutorial - DP Controller') rospy.init_node('tutorial_dp_controller') try: node = TutorialDPController() rospy.spin() except rospy.ROSInterruptException: print('caught exception') print('exiting')学习这个代码,设计一个python3环境下的nmpc控制器,若用到相同的变量名还保持pid中的变量名,kp、ki、kd不用保留,用nmpc中的q矩阵就行
05-14
import numpy as np class ComputationGraphFunction: def __init__(self, inputs, outcomes, parameters, prediction, objective): """ Parameters: inputs: list of ValueNode objects containing inputs (in the ML sense) outcomes: list of ValueNode objects containing outcomes (in the ML sense) parameters: list of ValueNode objects containing values we will optimize over prediction: node whose 'out' variable contains our prediction objective: node containing the objective for which we compute the gradient """ self.inputs = inputs self.outcomes = outcomes self.parameters = parameters self.prediction = prediction self.objective = objective # Create name to node lookup, so users can just supply node_name to set parameters self.name_to_node = {} self.name_to_node[self.prediction.node_name] = self.prediction self.name_to_node[self.objective.node_name] = self.objective for node in self.inputs + self.outcomes + self.parameters: self.name_to_node[node.node_name] = node # Precompute the topological and reverse topological sort of the nodes self.objective_node_list_forward = sort_topological(self.objective) self.objective_node_list_backward = sort_topological(self.objective) self.objective_node_list_backward.reverse() self.prediction_node_list_forward = sort_topological(self.prediction) def __set_values__(self, node_values): for node_name in node_values: node = self.name_to_node[node_name] node.out = node_values[node_name] def set_parameters(self, parameter_values): self.__set_values__(parameter_values) def increment_parameters(self, parameter_steps): for node_name in parameter_steps: node = self.name_to_node[node_name] node.out += parameter_steps[node_name] def get_objective(self, input_values, outcome_values): self.__set_values__(input_values) self.__set_values__(outcome_values) obj = forward_graph(self.objective, node_list=self.objective_node_list_forward) return obj def get_gradients(self, input_values, outcome_values): obj = self.get_objective(input_values, outcome_values) #need forward pass anyway #print("backward node list: ",self.objective_node_list_backward) backward_graph(self.objective, node_list=self.objective_node_list_backward) parameter_gradients = {} for node in self.parameters: parameter_gradients[node.node_name] = node.d_out return obj, parameter_gradients def get_prediction(self, input_values): self.__set_values__(input_values) pred = forward_graph(self.prediction, node_list=self.prediction_node_list_forward) return pred ###### Computation graph utilities def sort_topological(sink): """Returns a list of the sink node and all its ancestors in topologically sorted order. Subgraph of these nodes must form a DAG.""" L = [] # Empty list that will contain the sorted nodes T = set() # Set of temporarily marked nodes P = set() # Set of permanently marked nodes def visit(node): if node in P: return if node in T: raise 'Your graph is not a DAG!' T.add(node) # mark node temporarily for predecessor in node.get_predecessors(): visit(predecessor) P.add(node) # mark node permanently L.append(node) visit(sink) return L def forward_graph(graph_output_node, node_list=None): # If node_list is not None, it should be sort_topological(graph_output_node) if node_list is None: node_list = sort_topological(graph_output_node) for node in node_list: out = node.forward() return out def backward_graph(graph_output_node, node_list=None): """ If node_list is not None, it should be the reverse of sort_topological(graph_output_node). Assumes that forward_graph has already been called on graph_output_node. Sets d_out of each node to the appropriate derivative. """ if node_list is None: node_list = sort_topological(graph_output_node) node_list.reverse() graph_output_node.d_out = np.array(1) # Derivative of graph output w.r.t. itself is 1 for node in node_list: node.backward() import numpy as np class ValueNode(object): """计算图中没有输入的节点,仅持有一个值""" def __init__(self, node_name): self.node_name = node_name self.out = None self.d_out = None def forward(self): self.d_out = np.zeros(self.out.shape) return self.out def backward(self): pass def get_predecessors(self): return [] class VectorScalarAffineNode(object): """计算一个将向量映射到标量的仿射函数的节点""" def __init__(self, x, w, b, node_name): """ 参数: x: 节点,其 `x.out` 是一个一维的 numpy 数组 w: 节点,其 `w.out` 是一个与 `x.out` 相同大小的一维 numpy 数组 b: 节点,其 `b.out` 是一个 numpy 标量(即 0 维数组) node_name: 节点的名称(字符串) """ self.node_name = node_name self.out = None self.d_out = None self.x = x self.w = w self.b = b def forward(self): self.out = np.dot(self.x.out, self.w.out) + self.b.out self.d_out = np.zeros(self.out.shape) return self.out def backward(self): d_x = self.d_out * self.w.out d_w = self.d_out * self.x.out d_b = self.d_out self.x.d_out += d_x self.w.d_out += d_w self.b.d_out += d_b def get_predecessors(self): return [self.x, self.w, self.b] class SquaredL2DistanceNode(object): """ 计算两个数组之间的 L2 距离(平方差之)的节点""" def __init__(self, a, b, node_name): """ 参数: a: 节点,其 `a.out` 是一个 numpy 数组 b: 节点,其 `b.out` 是一个与 `a.out` 形状相同的 numpy 数组 node_name: 节点的名称(字符串) """ self.node_name = node_name self.out = None self.d_out = None self.a = a self.b = b self.a_minus_b = None def forward(self): self.a_minus_b = self.a.out - self.b.out self.out = np.sum(self.a_minus_b ** 2) self.d_out = np.zeros(self.out.shape) #此处为初始化,下同 return self.out def backward(self): d_a = self.d_out * 2 * self.a_minus_b d_b = -self.d_out * 2 * self.a_minus_b self.a.d_out += d_a self.b.d_out += d_b return self.d_out def get_predecessors(self): return [self.a, self.b] class L2NormPenaltyNode(object): """ 计算 l2_reg * ||w||^2 节点,其中 l2_reg 为标量, w为向量""" def __init__(self, l2_reg, w, node_name): """ 参数: l2_reg: 一个大于等于0的标量值(不是节点) w: 节点,其 w.out 是一个 numpy 向量 node_name: 节点的名称(字符串) """ self.node_name = node_name self.out = None self.d_out = None self.l2_reg = np.array(l2_reg) self.w = w def forward(self): self.out = self.l2_reg * np.sum(self.w.out ** 2) self.d_out = np.zeros(self.out.shape) return self.out def backward(self): d_w = self.d_out * 2 * self.l2_reg * self.w.out self.w.d_out += d_w return self.d_out def get_predecessors(self): return [self.w] ## TODO ## Hint:实现对应的forward,backword,get_predecessors接口即可 class SumNode(object): """ 计算 a + b 的节点,其中 a b 是 numpy 数组。""" def __init__(self, a, b, node_name): """ 参数: a: 节点,其 a.out 是一个 numpy 数组 b: 节点,其 b.out 是一个与 a 的形状相同的 numpy 数组 node_name: 节点的名称(一个字符串) """ self.node_name = node_name self.out = None self.d_out = None self.a = a self.b = b def forward(self): self.out = self.a.out + self.b.out self.d_out = np.zeros(self.out.shape) return self.out def backward(self): d_a = self.d_out * np.ones_like(self.a.out) d_b = self.d_out * np.ones_like(self.b.out) self.a.d_out += d_a self.b.d_out += d_b return self.d_out def get_predecessors(self): return [self.a, self.b] ## TODO ## Hint:实现对应的forward,backword,get_predecessors接口即可 class AffineNode(object): """实现仿射变换 (W,x,b)-->Wx+b 的节点,其中 W 是一个矩阵,x b 是向量 参数: W: 节点,其 W.out 是形状为 (m,d) 的 numpy 数组 x: 节点,其 x.out 是形状为 (d) 的 numpy 数组 b: 节点,其 b.out 是形状为 (m) 的 numpy 数组(即长度为 m 的向量) """ def __init__(self, W, x, b, node_name): self.node_name = node_name self.out = None self.d_out = None self.W = W self.x = x self.b = b def forward(self): self.out = np.dot(self.W.out, self.x.out) + self.b.out self.d_out = np.zeros(self.out.shape) return self.out def backward(self): d_W = np.outer(self.d_out, self.x.out) d_x = np.dot(self.W.out.T, self.d_out) d_b = self.d_out.copy() self.W.d_out += d_W self.x.d_out += d_x self.b.d_out += d_b return self.d_out def get_predecessors(self): return [self.W, self.x, self.b] ## Hint:实现对应的forward,backword,get_predecessors接口即可 class TanhNode(object): """节点 tanh(a),其中 tanh 是对数组 a 逐元素应用的 参数: a: 节点,其 a.out 是一个 numpy 数组 """ def __init__(self, a, node_name): self.node_name = node_name self.out = None self.d_out = None self.a = a def forward(self): self.out = np.tanh(self.a.out) self.d_out = np.zeros(self.out.shape) return self.out def backward(self): d_a = self.d_out * (1 - self.out ** 2) self.a.d_out += d_a return self.d_out def get_predecessors(self): return [self.a] import matplotlib.pyplot as plt import setup_problem from sklearn.base import BaseEstimator, RegressorMixin import numpy as np import nodes import graph import plot_utils #import pdb #pdb.set_trace() #useful for debugging! class MLPRegression(BaseEstimator, RegressorMixin): """ 基于计算图的MLP实现 """ def __init__(self, num_hidden_units=10, step_size=.005, init_param_scale=0.01, max_num_epochs = 5000): self.num_hidden_units = num_hidden_units self.init_param_scale = 0.01 self.max_num_epochs = max_num_epochs self.step_size = step_size # 开始构建计算图 self.x = nodes.ValueNode(node_name="x") # to hold a vector input self.y = nodes.ValueNode(node_name="y") # to hold a scalar response ## TODO ## Hint: 根据PPT中给定的图,来构建MLP def fit(self, X, y): num_instances, num_ftrs = X.shape y = y.reshape(-1) ## TODO: 初始化参数(小的随机数——不是全部为0,以打破对称性) s = self.init_param_scale init_values = None ## TODO,在这里进行初始化,hint:调用np.random.standard_normal方法 self.graph.set_parameters(init_values) for epoch in range(self.max_num_epochs): shuffle = np.random.permutation(num_instances) epoch_obj_tot = 0.0 for j in shuffle: obj, grads = self.graph.get_gradients(input_values = {"x": X[j]}, outcome_values = {"y": y[j]}) #print(obj) epoch_obj_tot += obj # Take step in negative gradient direction steps = {} for param_name in grads: steps[param_name] = -self.step_size * grads[param_name] self.graph.increment_parameters(steps) if epoch % 50 == 0: train_loss = sum((y - self.predict(X,y)) **2)/num_instances print("Epoch ",epoch,": Ave objective=",epoch_obj_tot/num_instances," Ave training loss: ",train_loss) def predict(self, X, y=None): try: getattr(self, "graph") except AttributeError: raise RuntimeError("You must train classifer before predicting data!") num_instances = X.shape[0] preds = np.zeros(num_instances) for j in range(num_instances): preds[j] = self.graph.get_prediction(input_values={"x":X[j]}) return preds def main(): #lasso_data_fname = "lasso_data.pickle" lasso_data_fname = r"C:\Users\XM_Ta\OneDrive\Desktop\1120223544-汤阳光-实验四\Question\lasso_data.pickle" x_train, y_train, x_val, y_val, target_fn, coefs_true, featurize = setup_problem.load_problem(lasso_data_fname) # Generate features X_train = featurize(x_train) X_val = featurize(x_val) # Let's plot prediction functions and compare coefficients for several fits # and the target function. pred_fns = [] x = np.sort(np.concatenate([np.arange(0,1,.001), x_train])) pred_fns.append({"name": "Target Parameter Values (i.e. Bayes Optimal)", "coefs": coefs_true, "preds": target_fn(x)}) estimator = MLPRegression(num_hidden_units=10, step_size=0.001, init_param_scale=.0005, max_num_epochs=5000) x_train_as_column_vector = x_train.reshape(x_train.shape[0],1) # fit expects a 2-dim array x_as_column_vector = x.reshape(x.shape[0],1) # fit expects a 2-dim array estimator.fit(x_train_as_column_vector, y_train) name = "MLP regression - no features" pred_fns.append({"name":name, "preds": estimator.predict(x_as_column_vector) }) X = featurize(x) estimator = MLPRegression(num_hidden_units=10, step_size=0.0005, init_param_scale=.01, max_num_epochs=500) estimator.fit(X_train, y_train) name = "MLP regression - with features" pred_fns.append({"name":name, "preds": estimator.predict(X) }) plot_utils.plot_prediction_functions(x, pred_fns, x_train, y_train, legend_loc="best") if __name__ == '__main__': main() 请帮我补充完整以上代码
05-29
import numpy as np import matplotlib.pyplot as plt import pandas as pd # 设置中文字体负号显示 plt.rcParams["font.family"] = ["SimHei", "Microsoft YaHei"] plt.rcParams["axes.unicode_minus"] = False from sklearn.datasets import load_digits from sklearn.model_selection import train_test_split from sklearn.svm import SVC from sklearn.tree import DecisionTreeClassifier from sklearn.ensemble import RandomForestClassifier from sklearn.neural_network import MLPClassifier from sklearn.neighbors import KNeighborsClassifier from sklearn.naive_bayes import GaussianNB from sklearn.linear_model import LogisticRegression from sklearn.metrics import accuracy_score from sklearn.preprocessing import StandardScaler import tkinter as tk from tkinter import ttk, filedialog, messagebox from PIL import Image, ImageDraw import cv2 import os import csv # 尝试导入XGBoostLightGBM try: import xgboost as xgb except ImportError: xgb = None print("警告: 未安装XGBoost库,无法使用XGBoost模型") try: import lightgbm as lgb except ImportError: lgb = None print("警告: 未安装LightGBM库,无法使用LightGBM模型") # 定义模型元数据常量,优化LightGBM参数 MODEL_METADATA = { 'svm': ('支持向量机(SVM)', SVC, StandardScaler), 'dt': ('决策树(DT)', DecisionTreeClassifier, None), 'rf': ('随机森林(RF)', RandomForestClassifier, None), 'xgb': ('XGBoost(XGB)', xgb.XGBClassifier if xgb else None, None), 'lgb': ('LightGBM(LGB)', lgb.LGBMClassifier if lgb else None, None), 'mlp': ('多层感知机(MLP)', MLPClassifier, StandardScaler), 'knn': ('K最近邻(KNN)', KNeighborsClassifier, StandardScaler), 'nb': ('高斯朴素贝叶斯(NB)', GaussianNB, None), } def get_split_data(digits_dataset): """ 提取重复的数据集划分逻辑 :param digits_dataset: 手写数字数据集 :return: 划分后的训练集测试集 """ X, y = digits_dataset.data, digits_dataset.target return train_test_split(X, y, test_size=0.3, random_state=42) class ModelFactory: @staticmethod def create_model(model_type): """ 创建模型数据标准化器 :param model_type: 模型类型 :return: 模型数据标准化器 """ name, model_cls, scaler_cls = MODEL_METADATA[model_type] if not model_cls: raise ImportError(f"{name}模型依赖库未安装") model_params = { 'svm': {'probability': True, 'random_state': 42}, 'dt': {'random_state': 42}, 'rf': {'n_estimators': 100, 'random_state': 42}, 'xgb': {'objective': 'multi:softmax' if xgb else 'multi:softprob', 'random_state': 42}, 'lgb': {'objective': 'multiclass', 'random_state': 42, 'num_class': 10, 'max_depth': 5, 'min_child_samples': 10, 'learning_rate': 0.1, 'force_col_wise': True}, 'mlp': {'hidden_layer_sizes': (100, 50), 'max_iter': 1000, 'random_state': 42}, 'knn': {'n_neighbors': 5, 'weights': 'distance'}, 'nb': {}, }.get(model_type, {'random_state': 42}) model = model_cls(**model_params) scaler = scaler_cls() if scaler_cls else None return model, scaler @staticmethod def train_model(model, X_train, y_train, scaler=None, model_type=None): """ 训练模型 :param model: 模型 :param X_train: 训练集特征 :param y_train: 训练集标签 :param scaler: 数据标准化器 :param model_type: 模型类型 :return: 训练好的模型 """ if scaler: X_train = scaler.fit_transform(X_train) if model_type == 'lgb' and isinstance(X_train, np.ndarray): X_train = pd.DataFrame(X_train) model.fit(X_train, y_train) return model @staticmethod def evaluate_model(model, X_test, y_test, scaler=None, model_type=None): """ 评估模型 :param model: 模型 :param X_test: 测试集特征 :param y_test: 测试集标签 :param scaler: 数据标准化器 :param model_type: 模型类型 :return: 模型准确率 """ if scaler: X_test = scaler.transform(X_test) if model_type == 'lgb' and isinstance(X_test, np.ndarray) and hasattr(model, 'feature_name_'): X_test = pd.DataFrame(X_test, columns=model.feature_name_) y_pred = model.predict(X_test) return accuracy_score(y_test, y_pred) @staticmethod def train_and_evaluate(model_type, X_train, y_train, X_test, y_test): """ 训练并评估模型 :param model_type: 模型类型 :param X_train: 训练集特征 :param y_train: 训练集标签 :param X_test: 测试集特征 :param y_test: 测试集标签 :return: 训练好的模型、数据标准化器准确率 """ try: model, scaler = ModelFactory.create_model(model_type) model = ModelFactory.train_model(model, X_train, y_train, scaler, model_type) accuracy = ModelFactory.evaluate_model(model, X_test, y_test, scaler, model_type) return model, scaler, accuracy except Exception as e: print(f"模型 {model_type} 训练/评估错误: {str(e)}") raise e def evaluate_all_models(digits_dataset): """ 评估所有可用模型 :param digits_dataset: 手写数字数据集 :return: 模型评估结果 """ print("\n=== 模型评估 ===") X_train, X_test, y_train, y_test = get_split_data(digits_dataset) results = [] for model_type, (name, _, _) in MODEL_METADATA.items(): print(f"评估模型: {name} ({model_type})") if not MODEL_METADATA[model_type][1]: results.append({"模型名称": name, "准确率": "N/A"}) continue try: model, scaler, accuracy = ModelFactory.train_and_evaluate( model_type, X_train, y_train, X_test, y_test ) results.append({"模型名称": name, "准确率": f"{accuracy:.4f}"}) except Exception as e: results.append({"模型名称": name, "准确率": f"错误: {str(e)}"}) results.sort(key=lambda x: float(x["准确率"]) if isinstance(x["准确率"], str) and x["准确率"].replace('.', '', 1).isdigit() else -1, reverse=True) print(pd.DataFrame(results)) return results class HandwritingBoard: def __init__(self, root, model_factory, digits): self.root = root self.root.title("手写数字识别系统 (含模型性能对比)") self.root.geometry("1000x600") # 减小主窗口尺寸 self.model_factory = model_factory self.digits = digits self.model_cache = {} self.current_model = None self.scaler = None self.current_model_type = None self.has_drawn = False self.last_x, self.last_y = 0, 0 self.custom_data = [] self.drawing = False self.data_dir = "custom_digits_data" if not os.path.exists(self.data_dir): os.makedirs(self.data_dir) # 初始化画布尺寸相关变量 self.canvas_width = 600 self.canvas_height = 600 self.image = Image.new("L", (self.canvas_width, self.canvas_height), 255) self.draw_obj = ImageDraw.Draw(self.image) self.create_widgets() self.init_default_model() self.canvas.bind("<Configure>", self.on_canvas_resize) # 绑定窗口大小改变事件 def create_widgets(self): """创建界面组件""" # 顶部控制栏 top_frame = tk.Frame(self.root) top_frame.pack(fill=tk.X, padx=10, pady=5) # 减小边距 tk.Label(top_frame, text="选择模型:", font=("Arial", 10)).pack(side=tk.LEFT, padx=5) # 减小字体边距 self.available_models = [] for key in MODEL_METADATA: name = MODEL_METADATA[key][0] if MODEL_METADATA[key][1]: self.available_models.append((key, name)) self.model_combobox = ttk.Combobox( top_frame, values=[name for _, name in self.available_models], state="readonly", width=15, # 减小宽度 font=("Arial", 10) # 减小字体 ) self.model_combobox.current(0) self.model_combobox.bind("<<ComboboxSelected>>", self.on_model_select) self.model_combobox.pack(side=tk.LEFT, padx=5) # 减小边距 # 中间内容区域 middle_frame = tk.Frame(self.root) middle_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5) # 减小边距 # 左侧绘图区域 canvas_frame = tk.Frame(middle_frame) canvas_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(0, 10)) # 减小边距 self.canvas = tk.Canvas(canvas_frame, bg="white") self.canvas.pack(fill=tk.BOTH, expand=True) self.canvas.bind("<Button-1>", self.start_draw) self.canvas.bind("<B1-Motion>", self.draw) self.canvas.bind("<ButtonRelease-1>", self.stop_draw) # 添加绘制提示 self.canvas.create_text(self.canvas_width / 2, self.canvas_height / 2, text="绘制数字", fill="gray", font=("Arial", 16)) # 减小字体 # 右侧控制面板 - 使用grid布局 control_frame = tk.Frame(middle_frame) control_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True) # 使用grid布局排列右侧组件 control_frame.grid_columnconfigure(0, weight=1) control_frame.grid_columnconfigure(1, weight=1) # 当前模型 current_model_frame = tk.LabelFrame(control_frame, text="当前模型", font=("Arial", 10, "bold")) # 减小字体 current_model_frame.grid(row=0, column=0, columnspan=2, sticky="ew", pady=(0, 8), padx=3) # 减小边距 self.model_label = tk.Label(current_model_frame, text="支持向量机(SVM)", font=("Arial", 12), relief=tk.RAISED, padx=8) # 减小字体边距 self.model_label.pack(fill=tk.X, pady=5) # 减小边距 # 操作按钮 (左侧) button_frame = tk.LabelFrame(control_frame, text="操作", font=("Arial", 10, "bold")) # 减小字体 button_frame.grid(row=1, column=0, sticky="nsew", pady=(0, 8), padx=(3, 3)) # 减小边距 tk.Button(button_frame, text="识别", command=self.recognize, width=12, height=1, font=("Arial", 10)).pack(fill=tk.X, pady=4) # 减小尺寸边距 tk.Button(button_frame, text="清除", command=self.clear_canvas, width=12, height=1, font=("Arial", 10)).pack(fill=tk.X, pady=4) # 减小尺寸边距 tk.Button(button_frame, text="样本", command=self.show_samples, width=12, height=1, font=("Arial", 10)).pack(fill=tk.X, pady=4) # 减小尺寸边距 tk.Button(button_frame, text="对比图表", command=self.show_performance_chart, width=12, height=1, font=("Arial", 10)).pack(fill=tk.X, pady=4) # 减小尺寸边距 # 训练集管理 (右侧) train_set_frame = tk.LabelFrame(control_frame, text="训练集管理", font=("Arial", 10, "bold")) # 减小字体 train_set_frame.grid(row=1, column=1, sticky="nsew", pady=(0, 8), padx=(3, 3)) # 减小边距 tk.Button(train_set_frame, text="保存为训练样本", command=self.save_as_training_sample, width=12, height=1, font=("Arial", 10)).pack( # 减小尺寸边距 fill=tk.X, pady=4 ) tk.Button(train_set_frame, text="保存全部训练集", command=self.save_all_training_data, width=12, height=1, font=("Arial", 10)).pack( # 减小尺寸边距 fill=tk.X, pady=4 ) tk.Button(train_set_frame, text="加载训练集", command=self.load_training_data, width=12, height=1, font=("Arial", 10)).pack( # 减小尺寸边距 fill=tk.X, pady=4 ) # 识别结果 result_frame = tk.LabelFrame(control_frame, text="识别结果", font=("Arial", 10, "bold")) # 减小字体 result_frame.grid(row=2, column=0, columnspan=2, sticky="ew", pady=(0, 8), padx=3) # 减小边距 self.result_label = tk.Label(result_frame, text="请绘制数字", font=("Arial", 24)) # 减小字体 self.result_label.pack(pady=5) # 减小边距 self.prob_label = tk.Label(result_frame, text="", font=("Arial", 10)) # 减小字体 self.prob_label.pack(pady=3) # 减小边距 self.debug_label = tk.Label(result_frame, text="", font=("Arial", 9), wraplength=250) # 减小字体宽度 self.debug_label.pack(pady=3) # 减小边距 # 置信度可视化 (左侧) self.confidence_frame = tk.LabelFrame(control_frame, text="识别置信度", font=("Arial", 10, "bold")) # 减小字体 self.confidence_frame.grid(row=3, column=0, sticky="nsew", pady=(0, 8), padx=(3, 3)) # 减小边距 self.confidence_canvas = tk.Canvas(self.confidence_frame, bg="white", height=80) # 减小高度 self.confidence_canvas.pack(fill=tk.BOTH, expand=True, padx=3, pady=3) # 减小边距 # 可能的数字列表 (左侧) self.candidates_frame = tk.LabelFrame(control_frame, text="可能的数字", font=("Arial", 10, "bold")) # 减小字体 self.candidates_frame.grid(row=4, column=0, sticky="nsew", pady=(0, 8), padx=(3, 3)) # 减小边距 self.candidates_tree = ttk.Treeview(self.candidates_frame, columns=("数字", "概率"), show="headings") self.candidates_tree.column("数字", width=70, anchor=tk.CENTER) # 减小列宽 self.candidates_tree.column("概率", width=70, anchor=tk.CENTER) # 减小列宽 self.candidates_tree.heading("数字", text="数字") self.candidates_tree.heading("概率", text="概率") self.candidates_tree.pack(fill=tk.BOTH, expand=True, padx=3, pady=3) # 减小边距 # 模型性能对比 (右侧,与置信度候选数字并列) self.performance_frame = tk.LabelFrame(control_frame, text="模型性能对比", font=("Arial", 10, "bold")) # 减小字体 self.performance_frame.grid(row=3, column=1, rowspan=2, sticky="nsew", pady=(0, 8), padx=(3, 3)) # 减小边距 self.create_performance_table() def create_performance_table(self): """创建模型性能表格""" for widget in self.performance_frame.winfo_children(): widget.destroy() columns = ("模型名称", "准确率") self.performance_tree = ttk.Treeview(self.performance_frame, columns=columns, show="headings") self.performance_tree.column("模型名称", width=120, anchor=tk.W) # 减小列宽 self.performance_tree.column("准确率", width=80, anchor=tk.CENTER) # 减小列宽 self.performance_tree.heading("模型名称", text="模型名称") self.performance_tree.heading("准确率", text="准确率") scrollbar = ttk.Scrollbar(self.performance_frame, orient=tk.VERTICAL, command=self.performance_tree.yview) self.performance_tree.configure(yscroll=scrollbar.set) self.performance_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) self.load_performance_data() def load_performance_data(self): """加载模型性能数据""" results = evaluate_all_models(self.digits) for item in self.performance_tree.get_children(): self.performance_tree.delete(item) for i, result in enumerate(results): tag = "highlight" if i == 0 and isinstance(result["准确率"], str) and result["准确率"].replace('.', '', 1).isdigit() else "" self.performance_tree.insert("", tk.END, values=(result["模型名称"], result["准确率"]), tags=(tag,)) self.performance_tree.tag_configure("highlight", background="#e6f7ff", font=("Arial", 9, "bold")) # 减小字体 def show_performance_chart(self): """显示模型性能对比图表""" results = evaluate_all_models(self.digits) valid_results = [] for result in results: try: accuracy = float(result["准确率"]) valid_results.append((result["模型名称"], accuracy)) except (ValueError, TypeError): continue if not valid_results: messagebox.showinfo("提示", "没有可用的模型性能数据来生成图表") return valid_results.sort(key=lambda x: x[1], reverse=True) plt.figure(figsize=(10, 6)) # 减小图表尺寸 models, accuracies = zip(*valid_results) bars = plt.barh(models, accuracies, color='skyblue') plt.xlabel('准确率', fontsize=10) # 减小字体 plt.ylabel('模型', fontsize=10) # 减小字体 plt.title('各模型在手写数字识别上的性能对比', fontsize=12) # 减小字体 plt.xlim(0, 1.05) for bar in bars: width = bar.get_width() plt.text(width + 0.01, bar.get_y() + bar.get_height() / 2, f'{width:.4f}', ha='left', va='center', fontsize=8) # 减小字体 plt.tight_layout() plt.show() plt.close() def start_draw(self, event): """开始绘制事件处理""" self.drawing = True self.last_x, self.last_y = event.x, event.y def draw(self, event): """绘制事件处理""" if not self.drawing: return x, y = event.x, event.y self.canvas.create_oval(x - 8, y - 8, x + 8, y + 8, fill="black") # 减小绘制笔触 self.draw_obj.line([self.last_x, self.last_y, x, y], fill=0, width=16) # 减小绘制笔触 self.last_x, self.last_y = x, y def stop_draw(self, event): """停止绘制事件处理""" self.drawing = False self.has_drawn = True def clear_canvas(self): """清除画布""" self.canvas.delete("all") # 更新画布尺寸相关状态 self.canvas_width = self.canvas.winfo_width() self.canvas_height = self.canvas.winfo_height() self.image = Image.new("L", (self.canvas_width, self.canvas_height), 255) self.draw_obj = ImageDraw.Draw(self.image) self.result_label.config(text="请绘制数字") self.prob_label.config(text="") self.debug_label.config(text="") self.canvas.create_text(self.canvas_width / 2, self.canvas_height / 2, text="绘制数字", fill="gray", font=("Arial", 16)) # 减小字体 self.has_drawn = False self.clear_confidence_display() def clear_confidence_display(self): """清除置信度显示""" self.confidence_canvas.delete("all") for item in self.candidates_tree.get_children(): self.candidates_tree.delete(item) def preprocess_image(self): """预处理手写数字图像""" img_array = np.array(self.image) img_array = cv2.GaussianBlur(img_array, (5, 5), 0) _, img_array = cv2.threshold(img_array, 127, 255, cv2.THRESH_BINARY_INV) contours, _ = cv2.findContours(img_array, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if not contours: self.debug_label.config(text="未检测到有效数字,请重新绘制") return np.zeros(64) c = max(contours, key=cv2.contourArea) x, y, w, h = cv2.boundingRect(c) digit = img_array[y:y + h, x:x + w] size = max(w, h) padded = np.ones((size, size), dtype=np.uint8) * 255 offset_x = (size - w) // 2 offset_y = (size - h) // 2 padded[offset_y:offset_y + h, offset_x:offset_x + w] = digit resized = cv2.resize(padded, (8, 8), interpolation=cv2.INTER_AREA) normalized = 16 - (resized / 255 * 16).astype(np.uint8) return normalized.flatten() def recognize(self): """识别手写数字并显示置信度候选数字""" if not self.has_drawn: self.debug_label.config(text="请先绘制数字再识别") return if self.current_model_type is None: self.debug_label.config(text="模型类型未正确设置,请重新加载模型") return if self.current_model is None: self.debug_label.config(text="模型未加载,请选择并加载模型") return img = self.preprocess_image() if img.sum() == 0: self.clear_confidence_display() return img_input = img.reshape(1, -1) try: if self.scaler: img_input = self.scaler.transform(img_input) if self.current_model_type == 'lgb' and hasattr(self.current_model, 'feature_name_'): img_input = pd.DataFrame(img_input, columns=self.current_model.feature_name_) pred = self.current_model.predict(img_input)[0] # 显示识别结果 self.result_label.config(text=f"识别结果: {pred}") # 检查模型是否支持概率预测 if hasattr(self.current_model, 'predict_proba'): probs = self.current_model.predict_proba(img_input)[0] confidence = probs[pred] # 显示置信度 self.prob_label.config(text=f"置信度: {confidence:.2%}") # 更新置信度可视化 self.update_confidence_display(confidence) # 显示前3个候选数字 top3 = sorted(enumerate(probs), key=lambda x: -x[1])[:3] self.update_candidates_display(top3) # 更新调试信息 prob_text = "\n".join([f"数字 {i}: 概率 {p:.2%}" for i, p in top3]) self.debug_label.config(text=prob_text) else: self.prob_label.config(text="置信度: 该模型不支持概率输出") self.debug_label.config(text="") self.clear_confidence_display() except Exception as e: self.debug_label.config(text=f"识别错误: {str(e)}") self.clear_confidence_display() print("识别异常:", e) def update_confidence_display(self, confidence): """更新置信度可视化""" self.confidence_canvas.delete("all") # 获取画布宽度用于动态调整 canvas_width = self.confidence_canvas.winfo_width() or 300 # 减小默认宽度 # 绘制背景条 self.confidence_canvas.create_rectangle(15, 15, canvas_width - 15, 45, fill="#f0f0f0", outline="gray") # 减小尺寸 # 绘制置信度条 bar_width = int((canvas_width - 30) * confidence) color = self.get_confidence_color(confidence) self.confidence_canvas.create_rectangle(15, 15, 15 + bar_width, 45, fill=color, outline="") # 减小尺寸 # 绘制置信度文本 self.confidence_canvas.create_text((canvas_width) / 2, 30, text=f"置信度: {confidence:.2%}", font=("Arial", 9)) # 减小字体 # 绘制数字0-100刻度 for i in range(0, 11): x_pos = 15 + i * (canvas_width - 30) / 10 self.confidence_canvas.create_line(x_pos, 45, x_pos, 50, width=1) # 减小尺寸 if i % 2 == 0: # 每20%显示一个数字 self.confidence_canvas.create_text(x_pos, 60, text=f"{i * 10}", font=("Arial", 7)) # 减小字体 def get_confidence_color(self, confidence): """根据置信度返回对应的颜色""" if confidence >= 0.9: return "#2ecc71" # 绿色 (高置信度) elif confidence >= 0.7: return "#f1c40f" # 黄色 (中等置信度) else: return "#e74c3c" # 红色 (低置信度) def update_candidates_display(self, candidates): """更新候选数字显示""" # 清空现有项 for item in self.candidates_tree.get_children(): self.candidates_tree.delete(item) # 添加新项 for digit, prob in candidates: # 去掉高亮标签 tag = "" self.candidates_tree.insert("", tk.END, values=(digit, f"{prob:.2%}"), tags=(tag,)) def show_samples(self): """显示手写数字样本""" plt.figure(figsize=(10, 5)) # 减小图表尺寸 for i in range(10): plt.subplot(2, 5, i + 1) sample_idx = np.where(self.digits.target == i)[0][0] plt.imshow(self.digits.images[sample_idx], cmap="gray") plt.title(f"数字 {i}", fontsize=10) # 减小字体 plt.axis("off") plt.tight_layout() plt.show() plt.close() def on_model_select(self, event): """模型选择事件处理""" selected_name = self.model_combobox.get() model_type = {v: k for k, v in self.available_models}[selected_name] self.change_model(model_type) def change_model(self, model_type): """切换模型""" print(f"触发 change_model,选中模型键: {model_type}") model_name = MODEL_METADATA.get(model_type, (model_type,))[0] if model_type in self.model_cache: self.current_model, self.scaler, accuracy, self.current_model_type = self.model_cache[model_type] self.model_label.config( text=f"{model_name} (准确率:{accuracy:.4f})" ) self.debug_label.config(text=f"已从缓存加载 {model_name}") return print(f"\n=== 开始加载 {model_name} 模型 ===") X_train, X_test, y_train, y_test = get_split_data(self.digits) try: self.current_model, self.scaler, accuracy = ModelFactory.train_and_evaluate( model_type, X_train, y_train, X_test, y_test ) self.current_model_type = model_type self.model_cache[model_type] = (self.current_model, self.scaler, accuracy, self.current_model_type) self.model_label.config( text=f"{model_name} (准确率:{accuracy:.4f})" ) self.debug_label.config(text=f"模型加载完成,测试集准确率: {accuracy:.4f}") self.clear_canvas() print(f"=== {model_name} 加载完成,准确率 {accuracy:.4f} ===\n") self.load_performance_data() except Exception as e: self.debug_label.config(text=f"模型加载失败: {str(e)}") print(f"加载异常: {e}\n") def init_default_model(self): """初始化默认模型""" default_model_type = 'svm' self.change_model(default_model_type) def save_as_training_sample(self): """保存手写数字作为训练样本""" if not self.has_drawn: self.debug_label.config(text="请先绘制数字再保存") return img = self.preprocess_image() if img.sum() == 0: self.debug_label.config(text="未检测到有效数字,无法保存") return label_window = tk.Toplevel(self.root) label_window.title("输入数字标签") label_window.geometry("300x150") # 减小窗口尺寸 tk.Label(label_window, text="请输入您绘制的数字 (0-9):", font=("Arial", 10)).pack(pady=10) # 减小字体边距 entry = tk.Entry(label_window, font=("Arial", 12), width=8) # 减小字体宽度 entry.pack(pady=5) # 减小边距 entry.focus_set() def save_with_label(): try: label = int(entry.get()) if not (0 <= label <= 9): raise ValueError("标签必须是0到9之间的数字") self.custom_data.append((img.tolist(), label)) self.debug_label.config(text=f"已保存数字 {label} 到训练集 (当前共有 {len(self.custom_data)} 个样本)") label_window.destroy() except ValueError as e: self.debug_label.config(text=f"输入错误: {str(e)}") tk.Button(label_window, text="保存", command=save_with_label, width=10, height=1, font=("Arial", 10)).pack(pady=8) # 减小尺寸边距 label_window.grab_set() def save_all_training_data(self): """保存所有训练数据""" if not self.custom_data: self.debug_label.config(text="没有训练数据可保存") return file_path = filedialog.asksaveasfilename( defaultextension=".csv", filetypes=[("CSV文件", "*.csv"), ("所有文件", "*.*")], initialfile="custom_digits_training.csv", title="保存训练集" ) if not file_path: return try: with open(file_path, 'w', newline='', encoding='utf-8') as f: writer = csv.writer(f) writer.writerow([f'pixel{i}' for i in range(64)] + ['label']) for img_data, label in self.custom_data: writer.writerow(img_data + [label]) self.debug_label.config(text=f"已保存 {len(self.custom_data)} 个训练样本到 {file_path}") except Exception as e: self.debug_label.config(text=f"保存失败: {str(e)}") print(f"保存训练集异常: {e}") def load_training_data(self): """加载训练数据""" file_path = filedialog.askopenfilename( filetypes=[("CSV文件", "*.csv"), ("所有文件", "*.*")], title="加载训练集" ) if not file_path: return try: self.custom_data = [] with open(file_path, 'r', newline='', encoding='utf-8') as f: reader = csv.reader(f) next(reader) # 跳过标题行 for row in reader: if len(row) < 65: # 确保数据完整 continue img_data = [int(pixel) for pixel in row[:64]] label = int(row[64]) self.custom_data.append((img_data, label)) self.debug_label.config(text=f"已从 {file_path} 加载 {len(self.custom_data)} 个训练样本") except Exception as e: self.debug_label.config(text=f"加载失败: {str(e)}") print(f"加载训练集异常: {e}") def on_canvas_resize(self, event): """处理画布大小改变事件""" # 忽略初始尺寸为1的事件 if event.width <= 1 or event.height <= 1: return # 更新画布尺寸 self.canvas_width = event.width self.canvas_height = event.height # 重新创建图像并居中绘制提示文本 self.image = Image.new("L", (self.canvas_width, self.canvas_height), 255) self.draw_obj = ImageDraw.Draw(self.image) # 清除并重新绘制提示 self.canvas.delete("all") self.canvas.create_text(self.canvas_width / 2, self.canvas_height / 2, text="绘制数字", fill="gray", font=("Arial", 16)) # 减小字体 def run(self): """运行主循环""" self.root.mainloop() if __name__ == "__main__": digits = load_digits() root = tk.Tk() app = HandwritingBoard(root, ModelFactory, digits) app.run() 帮我优化代码
最新发布
06-23
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值