使用OpenCV做个图片校正工具

昨天有位兄台给我发了个文件,是下面这个样子的:

那一双小脚既没有裹成三寸金莲,又没有黑丝,这图片肯定不符合我的要求。我要的是这个样子的好不好:

让他拿扫描仪重新给我规规矩矩扫一个发过来?他要能用扫描仪还用手机拍个这个东西给我?好在我有巨蟒与AI,对AI说:给我生成一个Python程序,可以打开一张图片并显示在图片容器中,在图片上点击四个角点,对四个角点先按x位置升序排序,取前两个点为右侧点,后两个点为左侧点,再将左右侧两组点分别按y位置升序排序,前面的为上部点,后面的为下部点,将这四个点按【左上,右上,右下,左下】的秩序构造一个列表。求出这四个点的minX,maxX,minY,maxY,按照[[min_x,min_y],[max_x,min_y],[max_x,max_y],[min_x,max_y]]构造一个列表,将图片中【左上,右上,右下,左下】四个点构成的四边形范围内的图像进行透视变换,变换为由[[min_x,min_y],[max_x,min_y],[max_x,max_y],[min_x,max_y]]这四个点构成的矩形图象。

几乎任意一个AI都能够给出一个基本能够实现功能的代码,然后可以继续对AI增加要求,例如组装出GUI界面、要求使用自己熟悉的工具库、对图片进行缩放显示之类,试着运行一下,自己或者让AI更正一下错误,还不到一小时就可以弄出下面的程序:(0728修改:增加微调角点位置功能)

# pip install pillow opencv-python numpy
import tkinter as tk
from tkinter import filedialog, messagebox
from tkinter import ttkfrom PIL import Image, ImageTk
import cv2
import numpy as npclass ImageCorrectorApp:def __init__(self, root):self.root = rootself.root.title("图片校正工具")self.img_canvas_size = 800  # 图片显示容器尺寸self.max_show_size = 800    # 校正后图片最大显示尺寸# 设置ttk主题和样式style = ttk.Style()# 若系统支持clam/alt/vista/xpnative等主题可选themes = style.theme_names()if 'clam' in themes:style.theme_use('clam')style.configure('TFrame', background='#f8f9fa')style.configure('TLabel', background='#f8f9fa', font=('微软雅黑', 11))style.configure('TButton', font=('微软雅黑', 11, 'bold'), padding=6)style.configure('Warn.TLabel', foreground='#d9534f', font=('微软雅黑', 10, 'bold'), background='#f8f9fa')# 图片相关变量self.original_img = None      # 原始PIL图片self.display_img = None       # 当前显示的PIL图片self.tk_img = None            # Tkinter显示用图片self.scale = 1.0              # 当前缩放比例(放大/缩小按钮用)self.display_scale = 1.0      # 打开图片时的等比例缩放因子self.points = []              # 用户点击的点(用于校正)self.active_idx = None       # 当前激活点下标self.corrected_img = None     # 校正后的PIL图片# 主framemain_frame = ttk.Frame(root, padding=10)main_frame.pack(padx=10, pady=10)# 图片显示区及滚动条img_frame = ttk.Frame(main_frame)img_frame.grid(row=0, column=0)self.canvas = tk.Canvas(img_frame, width=self.img_canvas_size, height=self.img_canvas_size, bg='#e9ecef', highlightthickness=2, highlightbackground='#adb5bd')self.canvas.grid(row=0, column=0, sticky='nsew')# 水平和垂直滚动条self.hbar = ttk.Scrollbar(img_frame, orient=tk.HORIZONTAL, command=self.canvas.xview)self.hbar.grid(row=1, column=0, sticky='ew')self.vbar = ttk.Scrollbar(img_frame, orient=tk.VERTICAL, command=self.canvas.yview)self.vbar.grid(row=0, column=1, sticky='ns')self.canvas.config(xscrollcommand=self.hbar.set, yscrollcommand=self.vbar.set)self.canvas.bind("<Button-1>", self.on_canvas_click)  # 绑定鼠标点击事件self.root.bind("<Up>", self.on_key_press)self.root.bind("<Down>", self.on_key_press)self.root.bind("<Left>", self.on_key_press)self.root.bind("<Right>", self.on_key_press)# 右侧按钮区right_frame = ttk.Frame(main_frame)right_frame.grid(row=0, column=1, sticky='nw', padx=10, rowspan=2)# 用Canvas模拟绿色圆角矩形边框的Labellabel_canvas = tk.Canvas(right_frame, width=110, height=190, bg='#f8f9fa', highlightthickness=0)label_canvas.pack(pady=8)        # 画绿色圆角矩形def draw_round_rect(canvas, x1, y1, x2, y2, radius=12, **kwargs):points = [x1+radius, y1,x2-radius, y1,x2, y1,x2, y1+radius,x2, y2-radius,x2, y2,x2-radius, y2,x1+radius, y2,x1, y2,x1, y2-radius,x1, y1+radius,x1, y1]return canvas.create_polygon(points, smooth=True, **kwargs)draw_round_rect(label_canvas, 2, 2, 108, 188, radius=14, outline='#28a745', width=2, fill='#f8f9fa')# 绘制文字label_canvas.create_text(10, 10, anchor='nw', text="打开图片后点击图片选择四个角点进行校正。 \可以使用上下左右箭头对角点位置进行微调。", fill='#f00', font=('微软雅黑', 12), width=90)ttk.Button(right_frame, text="撤销最后选点", width=10, command=self.reset_point, style='TButton').pack(pady=8)ttk.Button(right_frame, text="放大图片", width=10, command=self.zoom_in, style='TButton').pack(pady=8)ttk.Button(right_frame, text="缩小图片", width=10, command=self.zoom_out, style='TButton').pack(pady=8)# 下方按钮区bottom_frame = ttk.Frame(main_frame)bottom_frame.grid(row=1, column=0, sticky='sew', padx=10, pady=10)ttk.Button(bottom_frame, text="打开图片", width=12, command=self.open_image, style='TButton').pack(side='left', expand=True, padx=8)ttk.Button(bottom_frame, text="校正图片", width=12, command=self.correct_image, style='TButton').pack(side='left', expand=True, padx=8)ttk.Button(bottom_frame, text="保存图片", width=12, command=self.save_image, style='TButton').pack(side='left', expand=True, padx=8)def open_image(self):# 打开图片文件,按容器等比例缩放显示file_path = filedialog.askopenfilename(filetypes=[("Image files", "*.jpg *.png *.jpeg")])if not file_path:returnself.original_img = Image.open(file_path).convert("RGB")w, h = self.original_img.sizescale = min(self.img_canvas_size / w, self.img_canvas_size / h, 1.0)self.display_scale = scaleif scale < 1.0:self.display_img = self.original_img.resize((int(w * scale), int(h * scale)), Image.LANCZOS)else:self.display_img = self.original_img.copy()self.scale = 1.0self.points.clear()self.corrected_img = Noneself.show_image()def reset_point(self):if self.original_img is None:returnif len(self.points) > 0:# 清除保存的最后一个角点self.points.pop()self.show_image()def show_image(self):# 在canvas上显示图片,并绘制用户点击的点和线if self.display_img is None:returnw, h = self.display_img.sizeself.tk_img = ImageTk.PhotoImage(self.display_img)self.canvas.config(scrollregion=(0, 0, w, h))self.canvas.delete("all")self.canvas.create_image(0, 0, anchor='nw', image=self.tk_img)# 自动检测角点并用黄色圆圈标记。经试验,这个功能作用不大,取消# img_np = np.array(self.display_img)# if img_np.ndim == 3 and img_np.shape[2] == 3:#     gray = cv2.cvtColor(img_np, cv2.COLOR_RGB2GRAY)# else:#     gray = img_np# corners = cv2.goodFeaturesToTrack(gray, maxCorners=100, qualityLevel=0.01, minDistance=10)# if corners is not None:#     for pt in corners:#         x, y = pt.ravel()#         self.canvas.create_oval(x-4, y-4, x+4, y+4, outline='yellow', width=2)# 绘制点和线for i, (x, y) in enumerate(self.points):if self.active_idx is not None and i == self.active_idx:# 激活点用蓝色边框高亮self.canvas.create_oval(x-7, y-7, x+7, y+7, outline='blue', width=3)# 绘制角点self.canvas.create_oval(x-5, y-5, x+5, y+5, fill='red', outline='red')if i > 0:x0, y0 = self.points[i-1]# 绘制新选择的角点与前一个角点之间的连线self.canvas.create_line(x0, y0, x, y, fill='red', width=2)if len(self.points) == 4:# 绘制最后一个角点与第一个角点之间的连线x0, y0 = self.points[0]x3, y3 = self.points[3]self.canvas.create_line(x3, y3, x0, y0, fill='red', width=2)def zoom_in(self):# 放大图片,每次放大10%if self.display_img is None:returnself.scale *= 1.1w, h = self.original_img.sizescale = self.display_scale * self.scaleself.display_img = self.original_img.resize((int(w * scale), int(h * scale)), Image.LANCZOS)# 点坐标同步放大self.points = [(int(x*1.1), int(y*1.1)) for (x, y) in self.points]self.show_image()def zoom_out(self):# 缩小图片,每次缩小10%if self.display_img is None:returnself.scale /= 1.1w, h = self.original_img.sizescale = self.display_scale * self.scaleself.display_img = self.original_img.resize((int(w * scale), int(h * scale)), Image.LANCZOS)# 点坐标同步缩小self.points = [(int(x/1.1), int(y/1.1)) for (x, y) in self.points]self.show_image()def on_key_press(self, event):# 方向键移动激活点if self.active_idx is None or not self.points:returnx, y = self.points[self.active_idx]if event.keysym == 'Up':y = max(0, y - 1)elif event.keysym == 'Down':y = y + 1elif event.keysym == 'Left':x = max(0, x - 1)elif event.keysym == 'Right':x = x + 1self.points[self.active_idx] = (x, y)self.show_image()def on_canvas_click(self, event):# 支持激活点和点选逻辑if self.display_img is None:returnx = self.canvas.canvasx(event.x)y = self.canvas.canvasy(event.y)click = (int(x), int(y))# 判断是否点击到已有点idx_near = Nonefor idx, (px, py) in enumerate(self.points):            # 计算点击位置与已有角点之间的距离dist = ((px - click[0]) ** 2 + (py - click[1]) ** 2) ** 0.5if dist < 8:  # 距离在8像素内算点击到角点idx_near = idx# 点中了已有角点if idx_near is not None:self.active_idx = idx_nearself.show_image()return# 未点中已有角点,若点数<4,新增点并激活if len(self.points) < 4:self.points.append(click)self.active_idx = len(self.points) - 1self.show_image()return# 点数已满4,且未点中已有点,不做操作def correct_image(self):# 校正图片,将四边形区域映射为矩形if self.original_img is None or len(self.points) != 4:messagebox.showwarning("提示", "请先选择图片并点击4个点")return# 将显示坐标还原为原图坐标pts = np.array(self.points, dtype=np.float32) / (self.display_scale * self.scale)# 1. 按x排序,分左右两组idx = np.argsort(pts[:, 0])left_pts = pts[idx[:2]]right_pts = pts[idx[2:]]# 2. 左右组分别按y排序left_pts = left_pts[np.argsort(left_pts[:, 1])]right_pts = right_pts[np.argsort(right_pts[:, 1])]# 3. 按左上、右上、右下、左下排列原始点位ordered_src = np.array([left_pts[0],   # 左上right_pts[0],  # 右上right_pts[1],  # 右下left_pts[1],   # 左下], dtype=np.float32)# 4. 目标点直接用x/y的min/max,按左上、右上、右下、左下排列min_x = np.min(pts[:, 0])max_x = np.max(pts[:, 0])min_y = np.min(pts[:, 1])max_y = np.max(pts[:, 1])ordered_dst = np.array([[min_x, min_y],  # 左上[max_x, min_y],  # 右上[max_x, max_y],  # 右下[min_x, max_y],  # 左下], dtype=np.float32)# 透视变换img_cv = cv2.cvtColor(np.array(self.original_img), cv2.COLOR_RGB2BGR)# 计算透视变换矩阵M = cv2.getPerspectiveTransform(ordered_src, ordered_dst)# 应用透视变换warped = cv2.warpPerspective(img_cv, M, (self.original_img.width, self.original_img.height))# 裁剪到目标矩形区域cropped = warped[int(min_y):int(max_y), int(min_x):int(max_x)]warped_pil = Image.fromarray(cv2.cvtColor(cropped, cv2.COLOR_BGR2RGB))self.corrected_img = warped_pil# 校正结果缩放显示show_w, show_h = warped_pil.sizescale = min(self.max_show_size / show_w, self.max_show_size / show_h, 1.0)if scale < 1.0:warped_pil = warped_pil.resize((int(show_w * scale), int(show_h * scale)), Image.LANCZOS)# 弹窗显示校正后图片win = tk.Toplevel(self.root)win.title("校正后图片")tk_img = ImageTk.PhotoImage(warped_pil)label = tk.Label(win, image=tk_img)label.image = tk_imglabel.pack()def save_image(self):# 保存校正后的图片if self.corrected_img is None:messagebox.showwarning("提示", "没有可保存的校正图片")returnfile_path = filedialog.asksaveasfilename(defaultextension=".png", filetypes=[("PNG文件", "*.png")])if file_path:self.corrected_img.save(file_path, "PNG")messagebox.showinfo("保存成功", f"图片已保存到\n{file_path}")if __name__ == "__main__":root = tk.Tk()# 设置窗体初始位置居中,距上100pxscreen_w = root.winfo_screenwidth()screen_h = root.winfo_screenheight()win_w = 980win_h = 900x = (screen_w - win_w) // 2y = 50root.geometry(f"{win_w}x{win_h}+{x}+{y}")app = ImageCorrectorApp(root)root.mainloop()

AI为代码生成了注释,其实最关键的就是提示词里提到的构造出原始四边形与目标图像矩形的角点一一映射的思路,至于透视变换的矩阵运算,可以直接调用OpenCV的API接口。程序运行界面如下:

AI时代就是好,真正做到了让人基本上只负责构思与理解AI代码的逻辑以及修改一些小错误,敲字和记忆知识都由AI负责,没有AI上面的程序恐怕要花掉我半天还不一定有这么完善。

附:使用命令行将pip安装源设置为阿里云镜像:

pip config set global.index-url https://mirrors.aliyun.com/pypi/simple 

pip config set install.trusted-host mirrors.aliyun.com

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

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

相关文章

《不只是接口:GraphQL与RESTful的本质差异》

RESTful API凭借其与HTTP协议的天然融合&#xff0c;以资源为核心的架构理念&#xff0c;在过去十余年里构建了Web数据交互的基本秩序&#xff1b;而GraphQL的出现&#xff0c;以“按需获取”为核心的查询模式&#xff0c;打破了传统的请求-响应逻辑&#xff0c;重新定义了前端…

博士招生 | 香港大学 招收人工智能和网络安全方向 博士生

学校简介香港大学创立于 1911 年&#xff0c;是香港历史最悠久的高等学府&#xff0c;QS 2025 世界排名第 17 位。计算机科学学科在 QS 2025 学科排名中位列全球第 31 位、亚洲第 5 位。计算机系&#xff08;Department of Computer Science&#xff09;下设系统、人工智能、数…

Linux知识回顾总结----基础IO

目录 1. 理解“文件” 1.1 文件的定义 2. 回顾 C 语言的文件操作 2.1 文件操作 2.2 实现cat 2.3 可以实现打印的几种方式 3. 系统文件的IO 3.2 使用系统的接口 3.3 内部的实现 3.4 重定向 4. 文件系统的内核结构 5. 缓冲区 5.1 是什么 5.2 为什么 5.3 有什么 5.4 见见…

网络:基础概念

网络&#xff1a;基础概念 在计算机发展过程中&#xff0c;最开始每个计算机时相互独立的&#xff0c;后来人们需要用计算机合作处理任务&#xff0c;这就牵扯到了数据交换&#xff0c;所以最开始的网络就诞生了。一开始&#xff0c;网络都是局域网LAN&#xff0c;后来技术成熟…

图像识别边缘算法

文章目录1. 基本概念2. 边缘检测原理边缘类型&#xff1a;3. 常见边缘检测算法3.1 Sobel算子3.2 Canny边缘检测3.3 Laplacian算子4. Canny边缘检测详细流程流程图示例&#xff1a;详细步骤说明&#xff1a;5. 边缘检测算法比较6. 参数调优建议Canny边缘检测参数&#xff1a;Sob…

【Java Web实战】从零到一打造企业级网上购书网站系统 | 完整开发实录(终)

&#x1f9ea; 测试与质量保证 &#x1f50d; 全方位测试体系 我建立了企业级的全方位测试体系来确保系统质量&#xff1a; &#x1f9ea; 测试金字塔模型 #mermaid-svg-u4I8UuUAyxJVjcqs {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill…

QT开发---网络编程下

HTTP协议 HTTP&#xff08;HyperText Transfer Protocol&#xff0c;超文本传输协议&#xff09;是互联网上应用最为广泛的协议之一&#xff0c;用于客户端和服务器之间的通信。默认端口80&#xff0c;传输层使用的是TCP协议特点无连接&#xff1a;HTTP协议是无连接的&#xff…

mac 苹果电脑 Intel 芯片(Mac X86) 安卓虚拟机 Android模拟器 的救命稻草(下载安装指南)

引言&#xff1a; 还在为你的Intel芯片MacBook&#xff08;i5, i7, i9等&#xff09;找不到合适的安卓虚拟机而发愁吗&#xff1f;随着Apple Silicon (M1/M2/M3) 芯片的普及&#xff0c;大量优秀的安卓模拟器&#xff08;如Android Studio自带的模拟器、网易MuMu等&#xff09;…

C语言:顺序表(上)

C语言&#xff1a;顺序表&#xff08;上&#xff09; 1.顺序表的介绍 2.顺序表的实现 1.顺序表的介绍 线性表是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构&#xff0c;常见的线性表&#xff1a;顺序表、链表、栈、队列、字符串… 线性表在…

GPT - 5被曝将在8月初发布!并同步推出mini、nano版

据《TheVerge》最新报道&#xff0c;OpenAI 正准备在 8 月发布新版本旗舰大模型 GPT-5&#xff0c;如果顺利的话发布节点最早会在 8 月初。同时&#xff0c;下个月发布 GPT-5 时&#xff0c;还会一并推出 mini&#xff08;小型&#xff09;和 nano&#xff08;微型&#xff09;…

【Linux操作系统】简学深悟启示录:Linux环境基础开发工具使用

文章目录1.软件包管理器yum2.Linux编辑器vim2.1 三模式切换2.2 正常模式2.3 底行模式2.4 可视化模式2.5 vim 配置3.Linux编译器gcc/g3.1 预处理3.2 编译3.3 汇编3.4 连接3.5 函数库4.Linux自动化构建工具Makefile5.Linux调试器gdb希望读者们多多三连支持小编会继续更新你们的鼓…

八大神经网络的区别

神经网络名称全称/修正名称主要作用核心特点典型应用场景CINICNN&#xff08;卷积神经网络&#xff09;处理图像、视频等空间数据&#xff0c;提取局部特征。使用卷积核、池化操作&#xff1b;擅长平移不变性。图像分类、目标检测、人脸识别。RINIRNN&#xff08;循环神经网络&…

从 SQL Server 到 KingbaseES V9R4C12,一次“无痛”迁移与深度兼容体验实录

#数据库平替用金仓 #金仓产品体验官 摘要&#xff1a;本文以体验项目案例为主线&#xff0c;从下载安装、数据类型、T-SQL、JDBC、性能基准、踩坑回退六大维度&#xff0c;全景验证 KingbaseES V9R4C12 对 SQL Server 的“零改造”兼容承诺&#xff1b;并给出 TPCH 100G 性能对…

EasyPlayer播放器系列开发计划2025

EasyPlayer系列产品发展至今&#xff0c;已经超过10年&#xff0c;从最早的EasyPlayer RTSP播放器&#xff0c;到如今维护的3条线&#xff1a;EasyPlayer-RTSP播放器&#xff1a;Windows、Android、iOS&#xff1b;EasyPlayerPro播放器&#xff1a;Windows、Android、iOS&#…

通信名词解释:I2C、USART、SPI、RS232、RS485、CAN、TCP/IP、SOCKET、modbus等

以下内容参考AI生成内容1. I2C&#xff08;Inter-Integrated Circuit&#xff0c;集成电路间总线&#xff09;定义&#xff1a;由飞利浦&#xff08;现恩智浦&#xff09;开发的短距离串行通信总线&#xff0c;用于芯片级设备间的低速数据传输。工作原理&#xff1a;采用两根信…

bash的特性-常见的快捷键

一、前言在 Linux Shell 编程和日常使用中&#xff0c;Bash 快捷键 是提升命令行操作效率的利器。熟练掌握这些快捷键&#xff0c;不仅可以节省大量输入时间&#xff0c;还能显著提升你在终端环境下的操作流畅度。本文将带你全面了解 Bash 中常用的快捷键&#xff0c;包括&…

【Java Web实战】从零到一打造企业级网上购书网站系统 | 完整开发实录(三)

&#x1f3a8; 核心功能设计 &#x1f464; 用户管理系统 用户管理是整个系统的基础&#xff0c;我设计了完整的用户生命周期管理&#xff1a; &#x1f510; 用户注册流程 #mermaid-svg-D0eUHWissjNhkqlB {font-family:"trebuchet ms",verdana,arial,sans-serif;fon…

uniapp input 聚焦时键盘弹起滚动到对应的部分

实现效果代码如下<template><view idapp><view class"aa"></view><iconfont name"left"></iconfont>姓氏&#xff1a;<input style"background-color: antiquewhite;" type"text" v-model&quo…

【基础篇三】WebSocket:实时通信的革命

目录 一、传统HTTP的"痛点"分析 1.1 HTTP的单向通信模式 1.2 "实时"效果的痛苦尝试 ​编辑 1.3 性能对比分析 二、WebSocket 协议详解 2.1 WebSocket是什么&#xff1f; ​编辑 2.2 WebSocket的核心特性 2.2.1 全双工通信&#xff08;Full-Duple…

设计模式(十八)行为型:中介者模式详解

设计模式&#xff08;十八&#xff09;行为型&#xff1a;中介者模式详解中介者模式&#xff08;Mediator Pattern&#xff09;是 GoF 23 种设计模式中的行为型模式之一&#xff0c;其核心价值在于通过引入一个中介者对象来封装一组对象之间的交互&#xff0c;从而降低对象间的…