file-type

JavaScript实现3位数分隔符自动添加

ZIP文件

下载需积分: 10 | 56KB | 更新于2024-12-20 | 124 浏览量 | 0 下载量 举报 收藏
download 立即下载
标题中的"3-digit-separator"指的是一个处理数字格式化的功能或工具,它的主要作用是在每输入3位数字时添加一个逗号作为分隔符。这样的工具在处理大量的数字数据时非常有用,尤其是在财务报告、数据录入和显示大数字时,能够提高数据的可读性。 描述中提到的作者Yugeta.Koji和日期2019.09.06,给出了该功能或代码的来源和创建时间。URL http://myntinc.com指向的是MYNT.Inc的官方网站,这可能是开发此工具的公司或组织。Organization字段进一步确认了这一点。 在规格中详细描述了该工具的功能,包括: 1. 每输入3位数字添加一个逗号,这是为了提升数字的可读性,尤其是在使用千位分隔符的国家和地区。 2. 发送时将值转换为逗号分隔的形式,这可能涉及到自动格式化输出的数字,以便于其他系统或用户能够更容易地理解和处理这些数字。 3. 对于小数点,需要减去对应的位数,这意味着工具会处理整数和小数部分,并且正确地处理它们的分隔。 4. 整数值用逗号分隔,次要值用空格分隔,这个规则可能是特定于某些地区或格式要求。 5. 自动将全角数字转换为半角数字,这是一个很重要的功能,因为全角数字在某些系统中可能不被正确识别。 6. 链接事件支持表示该工具可以与其他事件或项目集成,不需要手动输入就可以自动进行数字处理。 7. 输入非数字字符串时自动删除,这是为了保证数据的准确性,防止非数字信息的干扰。 8. 如果减号没有附加在第一个字符串上,则会被删除,这确保了数字的负值表示的一致性和正确性。 选项字段中提到了input_selector,这可能是一个用于获取目标输入表单的功能或方法,例如通过CSS选择器来指定需要应用该工具的输入字段。 在标签字段中,明确指出这个工具是使用JavaScript编写的。JavaScript是一种广泛应用于网页开发的脚本语言,它能够实现丰富的交互效果,包括用户输入的实时处理和动态修改。考虑到这些功能,"3-digit-separator"很可能是用于网页或网络应用中,帮助处理用户输入或显示格式化的数字数据。 压缩包子文件的文件名称列表中的"3-digit-separator-master"表示这个项目的代码库可能托管在版本控制系统如Git上,并且"master"表明这是主分支或主版本的代码。文件名称通常表明了项目的主体功能和版本状态,这有助于开发者和用户了解项目的当前状态和功能范围。 总结来说,"3-digit-separator"是一个旨在提高数字输入和显示可读性的JavaScript工具,它通过自动添加千位分隔符、处理全角与半角转换、集成事件处理和自动清理输入数据等功能来优化数字处理。该工具可能广泛适用于需要数字输入和显示的网页或应用程序,尤其对于那些处理大量财务数据或用户生成内容的场景来说,它能极大地提升数据处理的效率和准确性。

相关推荐

filetype

<template> <el-container style="height: 100vh; overflow: hidden;"> <el-aside width="200px" style="background-color: #304156;"> <el-menu default-active="1" class="el-menu-vertical-demo" background-color="#304156" text-color="#bfcbd9" active-text-color="#409EFF"> <el-menu-item index="1"> 员工管理 </el-menu-item> </el-menu> </el-aside> <el-container> <el-header class="app-header">

员工管理系统

</el-header> <el-main class="app-content">
<el-row :gutter="20"> <el-col :span="4"> <el-input v-model="queryParams.name" placeholder="请输入姓名" clearable /> </el-col> <el-col :span="4"> <el-select v-model="queryParams.gender" placeholder="请选择性别" clearable> <el-option label="男" :value="0" /> <el-option label="女" :value="1" /> </el-select> </el-col> <el-col :span="4"> <el-select v-model="queryParams.job" placeholder="请选择职位" clearable> <el-option v-for="item in jobOptions" :key="item.value" :label="item.label" :value="item.value" /> </el-select> </el-col> <el-col :span="6"> <el-date-picker v-model="queryParams.dateRange" type="daterange" start-placeholder="开始日期" end-placeholder="结束日期" format="YYYY-MM-DD" value-format="YYYY-MM-DD" /> </el-col> <el-col :span="6" class="action-buttons"> <el-button type="primary" @click="fetchData" icon="el-icon-search">搜索</el-button> <el-button type="success" @click="showAddDialog" icon="el-icon-plus">新增</el-button> <el-button type="danger" @click="batchDelete" icon="el-icon-delete">批量删除</el-button> </el-col> </el-row>
<el-table :data="tableData" border stripe style="width: 100%" @selection-change="handleSelectionChange"> <el-table-column type="selection" width="55" align="center" /> <el-table-column prop="id" label="ID" width="80" align="center" /> <el-table-column prop="name" label="姓名" width="120" /> <el-table-column label="头像" width="100" align="center"> <template #default="scope"> <el-avatar v-if="scope.row.image" :src="scope.row.image" :size="50" shape="square" :preview-src-list="[scope.row.image]" /> <el-avatar v-else :size="50" shape="square"> </el-avatar> </template> </el-table-column> <el-table-column prop="gender" label="性别" width="80" align="center"> <template #default="scope"> <el-tag :type="scope.row.gender === 0 ? 'primary' : 'danger'"> {{ scope.row.gender === 0 ? '男' : '女' }} </el-tag> </template> </el-table-column> <el-table-column prop="job" label="职位" width="150"> <template #default="scope"> <el-tag :type="getJobTagType(scope.row.job)"> {{ jobMap[scope.row.job] || '未知' }} </el-tag> </template> </el-table-column> <el-table-column prop="entrydate" label="入职时间" width="120" align="center" /> <el-table-column prop="createTime" label="创建时间" width="180" align="center" /> <el-table-column prop="updateTime" label="最后修改时间" width="180" align="center" /> <el-table-column label="操作" width="200" align="center" fixed="right"> <template #default="scope"> <el-button size="small" type="primary" icon="el-icon-edit" @click="handleEdit(scope.row)">编辑</el-button> <el-button size="small" type="danger" icon="el-icon-delete" @click="handleDelete(scope.row.id)">删除</el-button> </template> </el-table-column> </el-table>
<el-pagination background layout="total, prev, pager, next, sizes" :total="total" :page-sizes="[5, 10, 20, 50]" :page-size="queryParams.pageSize" v-model:current-page="queryParams.pageNum" @current-change="fetchData" @size-change="handleSizeChange" />
</el-main> </el-container> </el-container> <el-dialog v-model="dialogVisible" :title="dialogTitle" width="700px" :close-on-click-modal="false"> <el-form :model="form" ref="formRef" :rules="formRules" label-width="100px" label-position="left"> <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="姓名" prop="name"> <el-input v-model="form.name" /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="用户名" prop="username"> <el-input v-model="form.username" /> </el-form-item> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="性别" prop="gender"> <el-select v-model="form.gender" placeholder="请选择性别" style="width: 100%"> <el-option label="男" :value="0" /> <el-option label="女" :value="1" /> </el-select> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="职位" prop="job"> <el-select v-model="form.job" placeholder="请选择职位" style="width: 100%"> <el-option v-for="item in jobOptions" :key="item.value" :label="item.label" :value="item.value" /> </el-select> </el-form-item> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="密码" prop="password" v-if="!isEdit"> <el-input v-model="form.password" show-password /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="入职时间" prop="entrydate"> <el-date-picker v-model="form.entrydate" type="date" placeholder="选择日期" format="YYYY-MM-DD" value-format="YYYY-MM-DD" style="width: 100%" /> </el-form-item> </el-col> </el-row> <el-form-item label="头像链接" prop="image"> <el-input v-model="form.image" placeholder="请输入图片URL" />
<el-image :src="form.image" fit="cover" style="width: 100px; height: 100px; margin-top: 10px;" :preview-src-list="[form.image]" />
</el-form-item> <el-form-item class="form-actions"> <el-button type="primary" @click="submitForm">提交</el-button> <el-button @click="dialogVisible = false">取消</el-button> </el-form-item> </el-form> </el-dialog> </template> <script setup> import { ref, reactive, onMounted } from 'vue'; import axios from 'axios'; import { ElMessage, ElMessageBox } from 'element-plus'; // 设置 axios 实例 const apiClient = axios.create({ baseURL: 'http://localhost:8080/emps', timeout: 5000, }); // 表格数据 const tableData = ref([]); const total = ref(0); const selectedRows = ref([]); const formRef = ref(null); // 查询参数 const queryParams = reactive({ pageNum: 1, pageSize: 5, name: '', gender: null, job: null, dateRange: [] }); // 表单数据 const form = reactive({ id: null, username: '', password: '', name: '', gender: null, job: null, image: '', entrydate: '' }); // 表单验证规则 const formRules = { name: [{ required: true, message: '请输入姓名', trigger: 'blur' }], username: [{ required: true, message: '请输入用户名', trigger: 'blur' }], gender: [{ required: true, message: '请选择性别', trigger: 'change' }], job: [{ required: true, message: '请选择职位', trigger: 'change' }], password: [{ required: true, message: '请输入密码', trigger: 'blur' }], entrydate: [{ required: true, message: '请选择入职日期', trigger: 'change' }] }; // 对话框控制 const dialogVisible = ref(false); const dialogTitle = ref('新增员工'); const isEdit = ref(false); // 职位映射表 const jobMap = { 1: '班主任', 2: '讲师', 3: '学工主管', 4: '教研主管', 5: '咨询师' }; // 职位选项 const jobOptions = [ { label: '班主任', value: 1 }, { label: '讲师', value: 2 }, { label: '学工主管', value: 3 }, { label: '教研主管', value: 4 }, { label: '咨询师', value: 5 } ]; // 获取职位标签类型 const getJobTagType = (job) => { const types = ['', 'success', 'warning', 'danger', 'info', 'primary']; return types[job] || 'info'; }; // 获取数据 const fetchData = async () => { const params = { page: queryParams.pageNum, pageSize: queryParams.pageSize, name: queryParams.name, gender: queryParams.gender, job: queryParams.job, begin: queryParams.dateRange[0] || '', end: queryParams.dateRange[1] || '' }; try { const res = await apiClient.get('', { params }); if (res.data.code === 1) { tableData.value = res.data.data.rows.map(item => ({ ...item, // 格式化最后修改时间 updateTime: formatDateTime(item.updateTime) })); total.value = res.data.data.total; } else { ElMessage.error('获取数据失败:' + res.data.msg); } } catch (error) { console.error('请求出错:', error); ElMessage.error('网络请求失败,请检查后端是否运行正常'); } }; // 时间格式化函数 const formatDateTime = (dateTime) => { if (!dateTime) return ''; // 如果是字符串直接返回 if (typeof dateTime === 'string') { // 尝试解析ISO格式时间 try { const date = new Date(dateTime); return date.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' }).replace(/\//g, '-'); } catch (e) { return dateTime; } } // 如果是Date对象或时间戳 const date = new Date(dateTime); return date.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' }).replace(/\//g, '-'); }; // 显示新增对话框 const showAddDialog = () => { dialogTitle.value = '新增员工'; isEdit.value = false; Object.assign(form, { id: null, username: '', password: '', name: '', gender: null, job: null, image: '', entrydate: '' }); dialogVisible.value = true; }; // 显示编辑对话框 const handleEdit = (row) => { dialogTitle.value = '编辑员工'; isEdit.value = true; Object.assign(form, { ...row }); dialogVisible.value = true; }; // 提交表单 const submitForm = async () => { try { await formRef.value.validate(); if (isEdit.value) { await apiClient.put('', form); ElMessage.success('员工信息更新成功'); } else { await apiClient.post('', form); ElMessage.success('员工添加成功'); } dialogVisible.value = false; fetchData(); } catch (error) { if (error.name !== 'Error') { console.error('保存失败:', error); ElMessage.error('操作失败:' + (error.response?.data?.message || error.message)); } } }; // 删除员工 const handleDelete = async (id) => { try { await ElMessageBox.confirm(`确认删除ID为 ${id} 的员工吗?`, '删除确认', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }); await apiClient.delete(`/${[id]}`); ElMessage.success('删除成功'); fetchData(); } catch (error) { if (error !== 'cancel') { console.error('删除失败:', error); ElMessage.error('删除失败:' + (error.response?.data?.message || error.message)); } } }; // 批量删除 const batchDelete = async () => { if (selectedRows.value.length === 0) { ElMessage.warning('请至少选择一条记录'); return; } try { const ids = selectedRows.value.map(item => item.id); await ElMessageBox.confirm(`确认删除选中的 ${ids.length} 条员工吗?`, '批量删除确认', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }); await apiClient.delete(`/${ids}`); ElMessage.success(`成功删除 ${ids.length} 条记录`); fetchData(); } catch (error) { if (error !== 'cancel') { console.error('批量删除失败:', error); ElMessage.error('删除失败:' + (error.response?.data?.message || error.message)); } } }; // 表格选择监听 const handleSelectionChange = (rows) => { selectedRows.value = rows; }; // 处理分页大小变化 const handleSizeChange = (size) => { queryParams.pageSize = size; fetchData(); }; // 初始化加载数据 onMounted(() => { fetchData(); }); </script> <style scoped> /* 全局样式 */ .app-header { background-color: #fff; box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08); display: flex; align-items: center; padding: 0 20px; z-index: 1; } .app-title { margin: 0; font-size: 18px; font-weight: 600; color: #333; } .app-content { padding: 20px; background-color: #f0f2f5; height: calc(100vh - 60px); overflow-y: auto; } .query-section { background: #fff; padding: 20px; border-radius: 4px; margin-bottom: 20px; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); } .table-section { background: #fff; padding: 20px; border-radius: 4px; margin-bottom: 20px; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); } .pagination-section { display: flex; justify-content: center; background: #fff; padding: 15px 0; border-radius: 4px; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); } .action-buttons { display: flex; justify-content: flex-end; } .form-actions { display: flex; justify-content: center; margin-top: 20px; } .avatar-preview { display: flex; justify-content: center; margin-top: 10px; } .el-avatar { background-color: #f5f7fa; display: flex; align-items: center; justify-content: center; } .el-avatar i { color: #909399; } /* 优化对话框样式 */ .el-dialog__body { padding: 20px 25px; } /* 调整操作列按钮间距 */ .el-table .el-button { margin: 0 5px; } /* 确保选择器宽度100% */ .el-select { width: 100%; } </style> 优化一下代码使代码量减少,更简洁,解决表格右侧空白的问题
filetype

import turtle import time # 初始化屏幕设置 def init_screen(): screen = turtle.Screen() screen.setup(800, 600) screen.title("数字时钟") return screen # 绘制大号数字 def draw_digit(digit, x, y, color): t = turtle.Turtle() t.hideturtle() t.penup() t.goto(x, y) t.color(color) style = ('Arial', 50, 'bold') t.write(str(digit), font=style) # 获取当前时间并格式化 def get_time_info(): current_time = time.strftime('%H:%M:%S', time.localtime()) hour, minute, second = map(int, current_time.split(':')) angle_hour = (hour % 12 + minute / 60) * 30 # 每小时30度 angle_minute = minute * 6 # 每分钟6度 angle_second = second * 6 # 每秒钟6度 date_str = time.strftime('%Y-%m-%d 星期%w', time.localtime()).replace('星期0', '星期日') program_runtime = int(time.time() - start_time) midnight_seconds = (24*60*60 - ((hour * 3600) + (minute * 60) + second)) hours_to_midnight = divmod(midnight_seconds, 3600)[0] minutes_to_midnight = divmod(divmod(midnight_seconds, 3600)[1], 60)[0] seconds_to_midnight = divmod(divmod(midnight_seconds, 3600)[1], 60)[1] return { "current_time": current_time, "date_str": date_str, "angle_hour": angle_hour, "angle_minute": angle_minute, "angle_second": angle_second, "program_runtime": program_runtime, "midnight_remaining": f"{hours_to_midnight}:{minutes_to_midnight}:{seconds_to_midnight}" } # 主循环更新时钟 def update_clock(screen, colors): while True: info = get_time_info() # 清屏 screen.clear() # 设置背景和颜色选项 bg_color, digit_color = colors[user_choice - 1] screen.bgcolor(bg_color) # 显示时、分、秒 h, m, s = info["current_time"].split(":") draw_digit(h, -150, 0, digit_color) draw_digit(m, 0, 0, digit_color) draw_digit(s, 150, 0, digit_color) # 分隔符 ":" separator_t = turtle.Turtle() separator_t.hideturtle() separator_t.penup() separator_t.goto(-75, -50) separator_t.color(digit_color) separator_t.write(":", align="center", font=("Arial", 50, "normal")) separator_t.goto(75, -50) separator_t.write(":", align="center", font=("Arial", 50, "normal")) # 艺术化的日期显示 art_date = "" for char in info['date_str']: if char.isdigit(): art_date += chr(ord(char)+int((info["angle_hour"]+info["angle_minute"])//10)) # 字符偏移的艺术效果 else: art_date += char art_t = turtle.Turtle() art_t.hideturtle() art_t.penup() art_t.goto(0, -200) art_t.color(digit_color) art_t.write(f"今日日期: {art_date}", align="center", font=("Courier New", 20, "italic")) # 其他信息 runtime_t = turtle.Turtle() runtime_t.hideturtle() runtime_t.penup() runtime_t.goto(-350, -250) runtime_t.color(digit_color) runtime_t.write(f"程序已运行: {info['program_runtime']} 秒", font=('Arial', 16)) remaining_t = turtle.Turtle() remaining_t.hideturtle() remaining_t.penup() remaining_t.goto(300, -250) remaining_t.color(digit_color) remaining_t.write(f"距午夜还剩: {info['midnight_remaining']}", font=('Arial', 16)) screen.update() time.sleep(1) if __name__ == "__main__": print("请选择时钟的颜色主题:") print("1. 黑底绿字\n2. 白底蓝字\n3. 灰底红字") user_choice = int(input("请输入编号: ")) if user_choice not in [1, 2, 3]: raise ValueError("无效的选择,请重新启动程序") colors_list = [("black", "green"), ("white", "blue"), ("grey", "red")] start_time = time.time() screen = init_screen() update_clock(screen, colors_list)寻找错误

filetype

import cv2 import numpy as np from sklearn.svm import SVC from sklearn.preprocessing import StandardScaler from sklearn.pipeline import make_pipeline import matplotlib.pyplot as plt import os import matplotlib.pyplot as plt import seaborn as sns from matplotlib import rcParams import joblib import tkinter as tk from tkinter import filedialog, ttk from PIL import Image, ImageTk import matplotlib matplotlib.use('TkAgg') # 设置matplotlib后端为TkAgg # 设置matplotlib中文字体 plt.rcParams.update({ 'font.family': 'Microsoft YaHei', 'font.serif': ['Times New Roman'], 'font.sans-serif': ['Microsoft YaHei'], 'mathtext.fontset': 'stix', 'axes.unicode_minus': False, 'figure.dpi': 200 }) # 支持中文路径读取 def imread_unicode(path, flags=cv2.IMREAD_GRAYSCALE): return cv2.imdecode(np.fromfile(path, dtype=np.uint8), flags) # 1. 模型训练和保存 def train_and_save_model(): tmpl_dir = r"C:\Users\Administrator\OneDrive\Desktop\Project\char_tmpl" if not os.path.exists(tmpl_dir): print(f"错误:路径不存在 - {tmpl_dir}") return # 准备数据 def load_data(): images = [] labels = [] for digit in range(10): img_path = os.path.join(tmpl_dir, f"{digit}.jpg") if not os.path.exists(img_path): print(f"错误: 文件不存在 - {img_path}") continue img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE) if img is None: print(f"使用cv2.imread加载失败,尝试替代方法...") try: img_array = np.fromfile(img_path, dtype=np.uint8) img = cv2.imdecode(img_array, cv2.IMREAD_GRAYSCALE) if img is None: print(f"严重错误: 无法加载图像 - {img_path}") continue except Exception as e: print(f"加载图像时出错: {str(e)}") continue if img is None: print(f"图像为空 - {img_path}") continue try: _, thresh = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV) resized = cv2.resize(thresh, (20, 20)) flattened = resized.flatten() images.append(flattened) labels.append(digit) print(f"成功处理: {digit}.jpg") except Exception as e: print(f"处理图像时出错: {str(e)}") return np.array(images), np.array(labels) print("开始加载图像数据...") X, y = load_data() print("数据加载完成!") print(f"加载的图像数量: {len(y)}") if len(X) == 0: print("错误: 没有加载任何图像! 请检查路径和文件格式。") return # 创建SVM模型 print("创建SVM模型...") model = make_pipeline(StandardScaler(), SVC(kernel='linear', C=1, probability=True)) print("模型创建成功!") # 训练模型 print("开始训练模型...") model.fit(X, y) print("模型训练完成!") # 保存模型 model_dir = os.path.join(os.path.dirname(tmpl_dir), "model") os.makedirs(model_dir, exist_ok=True) model_path = os.path.join(model_dir, "digit_recognition_model.joblib") joblib.dump(model, model_path) print(f"模型已保存至: {model_path}") return model_path # 2. 模型加载和预测(独立代码) def load_model(model_path): """加载训练好的模型""" if not os.path.exists(model_path): raise FileNotFoundError(f"模型文件不存在: {model_path}") model = joblib.load(model_path) print(f"已成功加载模型: {model_path}") return model def preprocess_image(image): """预处理图像:转为灰度、二值化、调整大小、展平""" try: # 如果图像已经是灰度图,则直接使用 if len(image.shape) == 3: gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) else: gray = image # 二值化处理 _, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY_INV) # 调整大小 resized = cv2.resize(thresh, (20, 20)) # 展平为特征向量 flattened = resized.flatten() return flattened except Exception as e: print(f"图像预处理失败: {str(e)}") raise def predict_digit(model, roi): """使用模型预测数字""" try: # 预处理图像 processed = preprocess_image(roi) # 预测 prediction = model.predict([processed])[0] probabilities = model.predict_proba([processed])[0] confidence = probabilities[prediction] * 100 return prediction, confidence except Exception as e: print(f"预测失败: {str(e)}") return -1, 0 # 3. GUI验证界面 class DigitRecognitionApp: def __init__(self, root, model_path): self.root = root self.root.title("数字识别系统") self.root.geometry("1200x700") # 加载模型 try: self.model = load_model(model_path) print("模型加载成功!") except Exception as e: print(f"模型加载失败: {str(e)}") tk.messagebox.showerror("错误", f"无法加载模型: {str(e)}") self.root.destroy() return # 创建界面 self.create_widgets() # 状态变量 self.image_path = None self.image = None self.display_image = None self.rect_start = None self.current_rect = None self.roi = None def create_widgets(self): # 顶部工具栏 toolbar = ttk.Frame(self.root) toolbar.pack(side=tk.TOP, fill=tk.X, padx=5, pady=5) # 文件选择按钮 ttk.Button(toolbar, text="选择图片", command=self.open_image).pack(side=tk.LEFT, padx=5) # 识别按钮 ttk.Button(toolbar, text="识别选区", command=self.recognize_selected).pack(side=tk.LEFT, padx=5) # 保存结果按钮 ttk.Button(toolbar, text="保存结果", command=self.save_result).pack(side=tk.LEFT, padx=5) # 清除按钮 ttk.Button(toolbar, text="清除选区", command=self.clear_selection).pack(side=tk.LEFT, padx=5) # 图像显示区域 image_frame = ttk.Frame(self.root) image_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=10, pady=10) self.canvas = tk.Canvas(image_frame, bg="gray80") self.canvas.pack(fill=tk.BOTH, expand=True) self.canvas.bind("<ButtonPress-1>", self.on_button_press) self.canvas.bind("<B1-Motion>", self.on_mouse_drag) self.canvas.bind("<ButtonRelease-1>", self.on_button_release) # 信息显示区域 info_frame = ttk.LabelFrame(self.root, text="识别结果") info_frame.pack(side=tk.RIGHT, fill=tk.Y, padx=10, pady=10) # 结果显示 ttk.Label(info_frame, text="识别结果:").pack(anchor=tk.W, padx=5, pady=(10, 0)) self.result_var = tk.StringVar(value="未识别") result_label = ttk.Label(info_frame, textvariable=self.result_var, font=("Arial", 24, "bold")) result_label.pack(fill=tk.X, padx=5, pady=(0, 10)) ttk.Label(info_frame, text="置信度:").pack(anchor=tk.W, padx=5, pady=(5, 0)) self.confidence_var = tk.StringVar(value="0.0%") confidence_label = ttk.Label(info_frame, textvariable=self.confidence_var, font=("Arial", 18)) confidence_label.pack(fill=tk.X, padx=5, pady=(0, 5)) # 分割线 ttk.Separator(info_frame, orient=tk.HORIZONTAL).pack(fill=tk.X, padx=5, pady=10) # ROI显示区域 ttk.Label(info_frame, text="所选数字区域:").pack(anchor=tk.W, padx=5, pady=(5, 0)) self.roi_frame = ttk.Frame(info_frame, width=150, height=150) self.roi_frame.pack(padx=5, pady=(0, 10)) self.roi_canvas = tk.Canvas(self.roi_frame, width=150, height=150, bg="gray90") self.roi_canvas.pack() # 状态栏 status_bar = ttk.Frame(self.root) status_bar.pack(side=tk.BOTTOM, fill=tk.X, padx=5, pady=5) self.status_var = tk.StringVar(value="就绪") status_label = ttk.Label(status_bar, textvariable=self.status_var) status_label.pack(side=tk.LEFT) # 帮助文本 help_text = "使用说明:\n1. 点击'选择图片'加载图片\n2. 在图片上拖动鼠标选择数字区域\n3. 点击'识别选区'进行识别\n4. 点击'保存结果'保存识别结果" ttk.Label(info_frame, text=help_text, justify=tk.LEFT).pack(fill=tk.X, padx=5, pady=10) def open_image(self): file_path = filedialog.askopenfilename( title="选择图片", filetypes=[("图像文件", "*.jpg;*.jpeg;*.png;*.bmp;*.tif")] ) if not file_path: return try: # 使用OpenCV读取图像 self.image = imread_unicode(file_path) if self.image is None: raise ValueError("无法读取图像文件") self.image_path = file_path self.display_image = self.image.copy() # 更新状态 self.status_var.set(f"已加载: {os.path.basename(file_path)}") # 在Canvas上显示图像 self.show_image() # 清除之前的选区 self.clear_selection() except Exception as e: tk.messagebox.showerror("错误", f"无法加载图像: {str(e)}") self.status_var.set(f"错误: {str(e)}") def show_image(self): if self.display_image is None: return self.canvas.delete("all") # 调整图像大小以适应Canvas canvas_width = self.canvas.winfo_width() canvas_height = self.canvas.winfo_height() if canvas_width < 10 or canvas_height < 10: canvas_width = 800 canvas_height = 600 # 获取图像尺寸 h, w = self.display_image.shape[:2] # 计算缩放比例 scale = min(canvas_width / w, canvas_height / h) new_w = int(w * scale) new_h = int(h * scale) # 调整图像大小 resized = cv2.resize(self.display_image, (new_w, new_h)) # 转换颜色空间:BGR转RGB rgb_image = cv2.cvtColor(resized, cv2.COLOR_BGR2RGB) # 转换为ImageTk格式 pil_image = Image.fromarray(rgb_image) self.tk_image = ImageTk.PhotoImage(pil_image) # 显示图像 self.canvas.create_image(0, 0, anchor=tk.NW, image=self.tk_image) # 更新显示图像比例 self.image_scale = scale def on_button_press(self, event): # 只有加载了图像时才能开始绘制选区 if self.display_image is None: return self.rect_start = (event.x, event.y) def on_mouse_drag(self, event): # 只有开始点存在才能绘制矩形 if self.rect_start is None: return # 删除之前的矩形 if self.current_rect: self.canvas.delete(self.current_rect) # 创建新矩形 x1, y1 = self.rect_start x2, y2 = event.x, event.y self.current_rect = self.canvas.create_rectangle(x1, y1, x2, y2, outline="red", width=2) def on_button_release(self, event): if self.rect_start is None or self.current_rect is None: return # 获取选区坐标 x1, y1 = self.rect_start x2, y2 = event.x, event.y # 确保坐标有效 start_x, end_x = min(x1, x2), max(x1, x2) start_y, end_y = min(y1, y2), max(y1, y2) # 计算实际图像中的选区 scale = self.image_scale roi_x1 = int(start_x / scale) roi_y1 = int(start_y / scale) roi_x2 = int(end_x / scale) roi_y2 = int(end_y / scale) # 确保选区有效 if (roi_x2 - roi_x1) < 5 or (roi_y2 - roi_y1) < 5: self.status_var.set("选区太小,请重新选择") return # 获取原始图像中的区域 self.roi = self.image[roi_y1:roi_y2, roi_x1:roi_x2].copy() # 在右侧显示ROI self.show_roi() self.status_var.set(f"已选择区域: {roi_x2-roi_x1}x{roi_y2-roi_y1} 像素") def show_roi(self): if self.roi is None: return self.roi_canvas.delete("all") # 将ROI转为灰度显示 if len(self.roi.shape) == 3: roi_gray = cv2.cvtColor(self.roi, cv2.COLOR_BGR2GRAY) else: roi_gray = self.roi # 等比例调整大小以适应ROI画布 h, w = roi_gray.shape scale = min(150 / w, 150 / h) new_w = int(w * scale) new_h = int(h * scale) # 调整大小 resized = cv2.resize(roi_gray, (new_w, new_h)) # 转换为PIL格式 roi_pil = Image.fromarray(resized) # 转换为ImageTk格式 self.tk_roi = ImageTk.PhotoImage(roi_pil) # 计算居中位置 offset_x = (150 - new_w) // 2 offset_y = (150 - new_h) // 2 # 在画布上显示 self.roi_canvas.create_image(offset_x, offset_y, anchor=tk.NW, image=self.tk_roi) def clear_selection(self): if self.current_rect: self.canvas.delete(self.current_rect) self.current_rect = None self.rect_start = None self.roi = None # 清空ROI显示 self.roi_canvas.delete("all") # 重置结果 self.result_var.set("未识别") self.confidence_var.set("0.0%") self.status_var.set("选区已清除") def recognize_selected(self): if self.roi is None: tk.messagebox.showwarning("警告", "请先选择一个数字区域") return try: # 进行预测 prediction, confidence = predict_digit(self.model, self.roi) # 显示结果 self.result_var.set(f"{prediction}") self.confidence_var.set(f"{confidence:.1f}%") self.status_var.set(f"识别完成: 数字 {prediction}, 置信度 {confidence:.1f}%") except Exception as e: tk.messagebox.showerror("识别错误", f"识别失败: {str(e)}") self.status_var.set(f"错误: {str(e)}") def save_result(self): if self.image_path is None: tk.messagebox.showwarning("警告", "请先选择一张图片") return if self.roi is None: tk.messagebox.showwarning("警告", "请先选择一个数字区域") return # 获取预测结果 prediction = self.result_var.get() if not prediction.isdigit(): tk.messagebox.showwarning("警告", "尚未进行有效识别") return # 创建结果文件夹 result_dir = os.path.join(os.path.dirname(self.image_path), "识别结果") os.makedirs(result_dir, exist_ok=True) # 保存原始图像 original_path = os.path.join(result_dir, "原始图像.jpg") cv2.imwrite(original_path, self.image) # 保存ROI图像 roi_path = os.path.join(result_dir, f"数字区域_{prediction}.jpg") cv2.imwrite(roi_path, self.roi) # 在原始图像上绘制结果 output_image = self.image.copy() # 获取在原始图像上的选区坐标 if self.current_rect: coords = self.canvas.coords(self.current_rect) scale = self.image_scale start_x = int(coords[0] / scale) start_y = int(coords[1] / scale) end_x = int(coords[2] / scale) end_y = int(coords[3] / scale) # 绘制矩形 cv2.rectangle(output_image, (start_x, start_y), (end_x, end_y), (0, 0, 255), 2) # 绘制标签 cv2.putText(output_image, f"数字: {prediction}", (start_x, start_y - 10), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2) # 保存结果图像 result_path = os.path.join(result_dir, "识别结果.jpg") cv2.imwrite(result_path, output_image) # 状态更新 tk.messagebox.showinfo("保存成功", f"结果已保存到:\n{result_dir}") self.status_var.set(f"结果已保存: {result_dir}") # 主程序入口 if __name__ == "__main__": # 模型路径 model_path = os.path.join(os.path.dirname('.'), "Project", "model", "digit_recognition_model.joblib") # 如果模型不存在,则训练并保存新模型 if not os.path.exists(model_path): print("模型未找到,开始训练新模型...") model_path = train_and_save_model() # 启动GUI应用 root = tk.Tk() app = DigitRecognitionApp(root, model_path) root.mainloop() 优化代码,实现连续框选识别数字并自动识别,然后提高此代码的置信度

小子骚骚
  • 粉丝: 34
上传资源 快速赚钱