【工具】旋转图片-数据集制作工具, 开源!

news2024/11/24 4:58:13

转载请注明出处:小锋学长生活大爆炸[xfxuezhang.cn]

Github:https://github.com/1061700625/small_tools_v2

        之前做了一个下载百度的旋转图片验证码的工具(多进程下载百度旋转验证码图片-制作数据集),那么拿到了图片数据,就需要手动校正调整来制作数据集,所以出了这个工具。

        效果演示:

        源码:(双击调试信息输出栏有惊喜)

import tkinter as tk
from tkinter import filedialog
from PIL import Image, ImageTk
import os
import pyperclip
import cv2
import numpy as np


def img_crop_process(img):
    try:
        img_cv = np.array(img.convert('L'))
        img_blur = cv2.GaussianBlur(img_cv, (9, 9), 2)
        circles = cv2.HoughCircles(img_blur, cv2.HOUGH_GRADIENT, dp=1, minDist=20,
                                param1=100, param2=40, minRadius=0, maxRadius=0)
        if circles is not None:
            circles = np.uint16(np.around(circles))
            max_circle = max(circles[0, :], key=lambda x: x[2])  # Select the largest circle by radius
        x, y, r = max_circle
        left = max(x - r, 0)
        right = min(x + r, img_cv.shape[1])
        top = max(y - r, 0)
        bottom = min(y + r, img_cv.shape[0])
        # Load the original image in color
        cropped_color_img = img.crop((left, top, right, bottom))
        mask = np.zeros_like(cropped_color_img)
        cv2.circle(mask, (r, r), r, (255, 255, 255), -1)
        cropped_color_img_with_white_bg = np.where(mask == (255, 255, 255), cropped_color_img, (255, 255, 255))
        final_img = Image.fromarray(np.uint8(cropped_color_img_with_white_bg))
        return final_img
    except Exception:
        return img

class ImageRotatorApp(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("旋转图像器 by 小锋学长生活大爆炸")
        self.resizable(False, False)
        width = 400
        height = 600
        self.geometry(f'{width}x{height}')
        # 计算屏幕中心坐标
        screen_width = self.winfo_screenwidth()  
        screen_height = self.winfo_screenheight()
        x = (screen_width/2) - (width/2)
        y = (screen_height/2) - (height/2)
        # 设置窗口中心坐标
        self.geometry('{}x{}+{}+{}'.format(width, height, int(x), int(y)))

        # Initialize source and result directories
        self.source_directory = 'images'
        self.result_directory = 'result'
        self.original_image = None
        self.rotate_value = 0

        # Frame for source directory selection and display
        self.source_frame = tk.Frame(self)
        self.source_frame.pack(fill='x')
        self.select_source_button = tk.Button(self.source_frame, text="选择图像目录", command=self.select_source_directory)
        self.select_source_button.pack(side='left', padx=10, pady=5)
        self.source_directory_label = tk.Label(self.source_frame, text=self.source_directory)
        self.source_directory_label.pack(side='left')

        # Frame for result directory selection and display
        self.result_frame = tk.Frame(self)
        self.result_frame.pack(fill='x')
        self.select_result_button = tk.Button(self.result_frame, text="选择保存目录", command=self.select_result_directory)
        self.select_result_button.pack(side='left', padx=10, pady=5)
        self.result_directory_label = tk.Label(self.result_frame, text=self.result_directory)
        self.result_directory_label.pack(side='left')

        # Image display frame
        self.img_display = tk.Label(self, width=400, height=400)
        self.img_display.pack(expand=True, fill='both')
        
        # Slider for rotation
        self.rotation_slider = tk.Scale(self, from_=0, to=360, length=400, orient="horizontal", command=self.rotate_image)
        self.rotation_slider.pack(fill='x')

        self.button_frame = tk.Frame(self)
        self.button_frame.pack(side='bottom', fill='x')
        self.prev_button = tk.Button(self.button_frame, text="上一张", command=self.load_prev_image)
        self.prev_button.config(height=2, width=10)  
        self.prev_button.pack(expand=True, fill='x', side='left', padx=10, pady=5)
        self.crop_button = tk.Button(self.button_frame, text="自动裁剪", command=self.crop_image)
        self.crop_button.config(height=2, width=10)  # Make the button larger
        self.crop_button.pack(expand=True, fill='x', side='left', padx=10, pady=5)
        # Button to save the image
        self.save_button = tk.Button(self.button_frame, text="保存图片", command=self.save_image)
        self.save_button.config(height=2, width=10)  # Make the button larger
        self.save_button.pack(expand=True, fill='x', side='left', padx=10, pady=5)
        # Next image button 
        self.next_button = tk.Button(self.button_frame, text="下一张", command=self.load_next_image) 
        self.next_button.config(height=2, width=10)  # Make the button larger
        self.next_button.pack(expand=True, fill='x', side='left', padx=10, pady=5)

        # 信息显示标签  
        self.info_label = tk.Label(self, text='')
        self.info_label.config(height=1, width=400, anchor='w', wraplength=0)
        self.info_label.pack(side='bottom', fill='x')
        self.info_label.bind('<Double-Button-1>', self.copy_info_label)
        
        # Update the display with the first image from the source directory
        self.load_first_image_from_source()

    def show_debug_msg(self, msg):
        self.info_label.config(text=msg)
    
    def copy_info_label(self, event):
        pyperclip.copy(self.info_label['text'])
        self.show_debug_msg(">> 双击成功,该内容已复制到剪切板 <<")
    
    def select_source_directory(self):
        directory = filedialog.askdirectory()
        if directory:  # Update the directory only if a choice was made
            self.source_directory = directory
            self.source_directory_label.config(text=self.source_directory)
        self.load_first_image_from_source()

    def select_result_directory(self):
        directory = filedialog.askdirectory()
        if directory:  # Update the directory only if a choice was made
            self.result_directory = directory
            self.result_directory_label.config(text=self.result_directory)
        
    def load_next_image(self):
        if self.original_image:
            img_files = [f for f in os.listdir(self.source_directory) if f.endswith(('.png', '.jpg', '.jpeg'))]
            curr_idx = img_files.index(self.img_name)
            next_idx = curr_idx + 1 if curr_idx < len(img_files) - 1 else 0
            next_img_name = img_files[next_idx]
            self.original_image = Image.open(os.path.join(self.source_directory, next_img_name))
            self.show_debug_msg(f"当前图片[{next_idx+1}/{len(img_files)}]: {os.path.join(self.source_directory, next_img_name)}")
            self.image = self.original_image
            self.img_name = next_img_name
            self.rotation_slider.set(0)
            self.update_image_display()
    
    def load_prev_image(self):
        if self.original_image:
            img_files = [f for f in os.listdir(self.source_directory) if f.endswith(('.png', '.jpg', '.jpeg'))]
            curr_idx = img_files.index(self.img_name)
            prev_idx = curr_idx - 1 if curr_idx > 0 else len(img_files) - 1
            prev_img_name = img_files[prev_idx]
            self.original_image = Image.open(os.path.join(self.source_directory, prev_img_name))
            self.show_debug_msg(f"当前图片[{prev_idx+1}/{len(img_files)}]: {os.path.join(self.source_directory, prev_img_name)}")
            self.image = self.original_image
            self.img_name = prev_img_name
            self.rotation_slider.set(0)
            self.update_image_display()
        
    def load_first_image_from_source(self):
        try:
            img_lists = [f for f in os.listdir(self.source_directory) if f.endswith(('.png', '.jpg', '.jpeg'))]
            self.img_name = img_lists[0] if img_lists else None
            if self.img_name:
                self.original_image = Image.open(os.path.join(self.source_directory, self.img_name))
                self.image = self.original_image
                self.show_debug_msg(f"当前图片[{1}/{len(img_lists)}]: {os.path.join(self.source_directory, self.img_name)}")
            else:
                self.original_image = None
                self.image = None
            self.update_image_display()
        except FileNotFoundError:
            self.original_image = None
            self.image = None
            self.img_display.config(image='')
            self.show_debug_msg(f"'{self.source_directory}'路径下没有图片.")

    def rotate_image(self, value):
        if self.original_image:
            angle = int(value)
            self.rotate_value = angle
            self.image = self.original_image.rotate(-angle, resample=Image.Resampling.BILINEAR, fillcolor=(255, 255, 255))
            self.update_image_display()

    def crop_image(self):
        if self.original_image:
            self.original_image = img_crop_process(self.original_image)
            self.rotation_slider.set(0)
            self.update_image_display(self.original_image)

    def update_image_display(self, src_img=None):
        src_img = src_img or self.image
        if src_img:
            # 缩放图片
            image_w, image_h = src_img.size
            if image_w > 400 or image_h > 400: 
                src_img.thumbnail((400,400))
            self.tk_image = ImageTk.PhotoImage(src_img)
            self.img_display.config(image=self.tk_image)
        else:
            self.img_display.config(image='')

    def save_image(self):
        if self.image:
            # Save the rotated image to the selected result directory
            save_path = os.path.join(self.result_directory, self.img_name.split('.')[0] + f'_{self.rotate_value}.' + self.img_name.split('.')[1])
            self.image.save(save_path)
            self.show_debug_msg(f"图片保存成功: {save_path}")
        else:
            self.show_debug_msg("没有图片要保存.")

# Create the 'images' and 'result' directories if they don't exist
os.makedirs('images', exist_ok=True)
os.makedirs('result', exist_ok=True)

# Run the app
app = ImageRotatorApp()
app.mainloop()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1185807.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

还不知道IP地址不够用是怎么被大牛们解决的?(NAT/NAPT, IPv6, DHCP)

文章目录 前言1. DHCP网络管理协议什么是 DHCPDHCP 两种分配机制 2. NAT网络地址转换协议什么是 NATNAT 技术使用NAT网络设备间如何通信两个内网设备相互通信不同内网中的设备相互通信NAT IP转换过程 NAPT 技术NAT 技术的缺陷 3. IPv6 协议什么是 IPv6 总结 前言 在之前的文章…

【教3妹学编程-算法题】2915. 和为目标值的最长子序列的长度

3妹&#xff1a;2哥&#xff0c;今日都立冬了&#xff0c; 可是天气一点都不冷。 2哥 : 立冬了&#xff0c;晚上要不要一起出去吃饺子&#xff1f;&#x1f95f; 3妹&#xff1a;好呀好呀&#xff0c;2哥请吃饺子喽 2哥 : 歪歪&#xff0c;我说的是一起出去吃&#xff0c;没说我…

[Java/力扣160]相交链表

这道题的关键是&#xff0c;使两个链表上的指针同时到达相交点处 方法一&#xff1a;分别遍历两个链表&#xff0c;得到其长度。然后先让较长的链表上的指针走“两链表长度之差”。然后两指针分别一步一步走&#xff0c;就能同时到达相交点处。 方法二&#xff1a;让 p1 遍历…

SHCTF-校外赛道

SHCTF-校外赛道 [WEEK1]babyRCE 1 (1)more:一页一页的显示档案内容2 (2)less:与 more 类似&#xff0c;但是比 more 更好的是&#xff0c;他可以[pg dn][pg up]翻页3 (3)head:查看头几行4 (4)tac:从最后一行开始显示&#xff0c;可以看出 tac 是 cat 的反向显示5 (5)tail:查看…

Android内存回收机制、GC算法及内存问题分析解决

Android内存回收机制、GC算法及内存问题分析解决 在Android开发中&#xff0c;Java内存回收和垃圾收集&#xff08;GC&#xff09;机制是确保应用程序高效运行的关键部分。针对不同对象存活率&#xff0c;Android平台采用了引用计数算法和可达性分析法来判定对象的可回收性&am…

命名管道原理(和匿名管道的对比),mkfifo(命令行,函数),命名管道模拟实现代码+与多个子进程通信代码

目录 命名管道 引入 原理 和匿名管道的对比 使用 -- mkfifo 命令行指令 创建 文件类型p 使用 函数 函数原型 模拟实现 头文件 客户端代码 服务端代码 运行情况 模拟实现 -- 与多个子进程 介绍 服务端代码: 运行情况 命名管道 引入 匿名管道只能用于父子进程…

一篇文章带你搞懂DNS全流程

1.DNS与CDN DNS是域名系统的缩写&#xff0c;它是一种将域名和IP地址相互映射的分布式数据库&#xff0c;能够使人更方便地访问互联网。 DNS的主要功能是将域名解析为IP地址。当你在浏览器中输入一个网址时&#xff0c;浏览器会向DNS服务器发送一个请求&#xff0c;以获取该网…

5G-A 商用加速,赋能工业互联网

2019 年 6 月&#xff0c;中国工业和信息化部发放 5G 商用牌照。同年 10 月&#xff0c;三大运营商公布 5G 商用套餐&#xff0c;11 月 1 日正式上线 5G 商用套餐&#xff0c;标志中国正式进入 5G 商用新纪元。今年是 5G 商用的第五年&#xff0c;在当前数字经济蓬勃发展的催化…

什么是屏蔽机房?

屏蔽机房是一种用于保护数据中心设备的安全和可靠的措施。通过屏蔽机房&#xff0c;可以防止电磁干扰、防止物理入侵以及提供更好的隔离和安全性。下面是一些关于屏蔽机房的常见做法&#xff1a; 电磁屏蔽&#xff1a;为了防止电磁干扰对数据中心设备的影响&#xff0c;可以在屏…

代码随想录算法训练营第15天|102. 二叉树的层序遍历226. 翻转二叉树101. 对称二叉树

JAVA代码编写 102. 二叉树的层序遍历 给你二叉树的根节点 root &#xff0c;返回其节点值的 层序遍历 。 &#xff08;即逐层地&#xff0c;从左到右访问所有节点&#xff09;。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;[[3],[9…

11.8旧有报错与修改

我将uart_done&#xff08;出问题的信号&#xff09;的变量类型设为reg了&#xff0c;也就是我是reg uart_done这个信号的&#xff0c;这样做是错误的&#xff0c;哪怕你在接收模块确实定义的是reg类型&#xff0c;但是在顶层模块的时候&#xff0c;它可以视为是一条单纯的线而…

PPO算法是什么?

ppo称作近邻策略优化算法&#xff0c;是典型的Actor- critic算法&#xff0c;即以两个网络为输入&#xff0c;并可以同时更新两者参数&#xff1b;在RLHF中我们更关注actor网络的更新方式&#xff0c;其损失函数由三部分构成&#xff0c;分别是&#xff1a;1&#xff0c;新旧状…

二进制搭建及高可用 Kubernetes v1.20

目录 一、实验规划&#xff1a; 二、操作系统初始化配置&#xff1a; 1. 关闭防火墙 selinux&#xff1a; 2. 关闭swap分区&#xff1a; 3. 根据规划设置主机名&#xff1a; 4. 所有主机添加hosts&#xff1a; 5. 调整内核参数: 6. 时间同步: 三、部署 etcd 集群&#xff1a…

netty (二) netty原理详解

netty高性能架构设计 netty 写一个简单的demo 服务器端 package com.atguigu.netty.simple;import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import …

GIS开发入门,TopoJSON格式是什么?TopoJSON格式与GeoJSON格式有什么不同?

TopoJSON介绍 TopoJSON是一种几何拓扑结构的地理数据格式,它使用拓扑结构来表示地理对象,可以更有效地压缩和转移数据,从而加快数据加载速度。 TopoJSON格式构成 TopoJSON文件由三部分组成,transform、objects和arcs组成。transform描述了变换参数; objects描述地理实体…

mongodb分组查询

通过userId分组&#xff0c;得到结果字段为&#xff1a;_id和count db.my_solitaire.aggregate([{$group: {_id: "$userId", count: {$sum: 1}}}])通过userId分组得到分组字段和其他想要的字段&#xff0c;得到_id&#xff0c;userName&#xff0c;count userName 为…

【广州华锐互动】智能楼宇3D数字化展示,实现对建筑物的实时监控和管理

随着科技的不断发展&#xff0c;人们对于生活品质的要求也在不断提高。在这个信息爆炸的时代&#xff0c;如何将复杂的数据以直观、生动的方式呈现给用户&#xff0c;已经成为了一个重要的课题。智能楼宇3D数字化展示作为一种新型的建筑科技&#xff0c;正逐渐成为行业的新宠&a…

Spring笔记(三)(Spring整合web环境)

01、Spring整合web环境 1.1 Javaweb三大组件及环境特点 在Java语言范畴内&#xff0c;web层框架都是基于Javaweb基础组件完成的&#xff0c;所以有必要复习一下Javaweb组件的特点 组件作用特点Servlet服务端小程序&#xff0c;负责接收客户端请求并作出响应的单例对象&#…

Lec13 Sleep Wake up

进程切换的流程 一个进程出于某种原因想要进入休眠状态&#xff0c;比如说出让CPU或者等待数据&#xff0c;它会先获取自己的锁&#xff1b;之后进程将自己的状态从RUNNING设置为RUNNABLE&#xff1b;之后进程调用switch函数&#xff0c;其实是调用sched函数在sched函数中再调…

Sealos 私有云正式发布,三倍性能 1/5 成本

马斯克将推特下云后可以节省 60% 成本&#xff0c;不代表你可以。 但是有了 Sealos 之后&#xff0c;你真的可以&#xff01; Sealos 私有云正式发布&#xff0c;详情地址&#xff1a;https://sealos.run/zh-Hans/self-hosting 原文链接&#xff1a;https://forum.laf.run/d/…