图像增强 目标检测 仿射变换 图像处理 扭曲图像

1.背景

在目标检测中,需要进行图像增强。这里的代码模拟了旋转、扭曲图像的功能,并且在扭曲的时候,能够同时把标注的结果也进行扭曲。

这里忽略了读取xml的过程,假设图像IMG存在对应的标注框,且坐标为左上、右下两个点,这立刻哟把IMG进行扭曲,并且尽可能保证标签的坐标点也随之扭曲。

2.效果

我有一张铁塔的照片(原图略,原图是一张角度非常正确的图片),处理后效果如下图所示,可以看到图像出现了扭曲(出现了黑边),并且图像右侧中间部分的红色绝缘子旁边出现了两个小白点(为了便于观察而添加的),说明起到了扭曲的作用,并且大概率能保证坐标也被正确变化。
在这里插入图片描述

3.动机

设计这种数据增强策略,主要出于以下两种考虑:

  1. 在实际使用中,我发现即便是yolov8这样的模型,对于扭曲的图像(例如扭曲的笔记本),识别效果会有非常严重的下降,而真实情况中非常有可能出现扭曲(例如有人把笔记本半合上),模型需要识别。在电力场景中,也可能出现拍摄角度异常等情况。
  2. 在训练中,将不同角度的目标展现给模型,也可能提高模型的效果。例如上图中的绝缘子,比原图要“矮胖”一些,因此可以算作“新样本”,为模型提供更丰富的数据来源。

4.具体实现

第一步是图像扭曲。在这一步中,代码里的trapezoid_vertices表示你希望把图像的哪一部分进行扭曲,target_trapezoid_vertices表示你希望把trapezoid_vertices扭曲到什么位置。你可以把代码中相关位置的generate_random_cut和generate_random_perspective都去掉,然后就能明白了。

第二步是转化原始标注的框。在使用gpt生成代码的时候,提示词将任务划分为了5个步骤,提示词如下

假设原始图像PIC宽度为W,高度为H。在图像标注场景中,我会告诉你两个点special_points,分别代表原始的标注框的左上角和右下角。你需要执行以下步骤:
【步骤1】根据special_points,恢复原始的标注框的四个坐标。注意保留这四个点的顺序关系。
【步骤2】:利用自定义函数fun1计算出变化之后的四个点的新坐标。
【步骤3】依次判断四个点所连成的线段是否与原始图像PIC的边缘有交点,如果有,就将这些交点的坐标存到point_list中。
【步骤4】判断变换后的四个点有哪些位于原始图像PIC的范围内,如果有,则将这些点的坐标存到point_list中。
【步骤5】分析point_list中的所有点,找出一个能完整包含这些点的矩形R,返回这个矩形R的左上角和右下角。

辅助代码包含了前面提到的5个步骤,具体如下:

import random
import cv2
import numpy as np# 原始图像剪裁的范围
max_cut = 0.1
min_cut = 0.05# 扭曲的最大和最小程度
max_perspective = 0.1
min_perspective = 0.05# 制定初始剪裁范围,你可以选择从某些地方开始剪裁你的图像
def generate_random_cut(x):# 计算 x 的 10% 和 20%min_value = min_perspective * xmax_value = max_perspective * x# 生成一个在 10% 到 20% 范围内的随机数random_value = random.uniform(min_value, max_value)# 随机决定这个数是正数还是负数sign = random.choice([-1, 1])return sign * random_value# 制定目标梯形的范围
def generate_random_perspective(x):# 计算 x 的 10% 和 20%min_value = min_perspective * xmax_value = max_perspective * xrandom_value = random.uniform(min_value, max_value)# 随机决定这个数是正数还是负数sign = random.choice([-1, 1])return sign * random_value# 根据输入的两个点的坐标,复原出连续的矩形框的四个点坐标
def get_bbox_corners(special_points):top_left = special_points[0][0]bottom_right = special_points[0][1]top_right = [bottom_right[0], top_left[1]]bottom_left = [top_left[0], bottom_right[1]]return np.array([top_left, top_right, bottom_right, bottom_left])# line_intersection所需要的辅助函数
def on_segment(p, q, r):if (q[0] <= max(p[0], r[0]) and q[0] >= min(p[0], r[0]) andq[1] <= max(p[1], r[1]) and q[1] >= min(p[1], r[1])):return Truereturn False# line_intersection所需要的辅助函数
def orientation(p, q, r):val = (q[1] - p[1]) * (r[0] - q[0]) - (q[0] - p[0]) * (r[1] - q[1])if val == 0:return 0  # collinearelif val > 0:return 1  # clockwiseelse:return 2  # counterclockwise# line_intersection所需要的辅助函数
def segments_intersect(p1, q1, p2, q2):o1 = orientation(p1, q1, p2)o2 = orientation(p1, q1, q2)o3 = orientation(p2, q2, p1)o4 = orientation(p2, q2, q1)if o1 != o2 and o3 != o4:return Trueif o1 == 0 and on_segment(p1, p2, q1):return Trueif o2 == 0 and on_segment(p1, q2, q1):return Trueif o3 == 0 and on_segment(p2, p1, q2):return Trueif o4 == 0 and on_segment(p2, q1, q2):return Truereturn False# 判断四个点所连成的线段是否与原始图像的边缘有交点
def line_intersection(p1, p2, edge_start, edge_end):if not segments_intersect(p1, p2, edge_start, edge_end):return Nonexdiff = (p1[0] - p2[0] + 0.01, edge_start[0] - edge_end[0] + 0.01)ydiff = (p1[1] - p2[1] + 0.01, edge_start[1] - edge_end[1] + 0.01)def det(a, b):return a[0] * b[1] - a[1] * b[0]div = det(xdiff, ydiff)if div == 0:return Noned = (det(p1, p2), det(edge_start, edge_end))x = det(d, xdiff) / divy = det(d, ydiff) / divreturn x, y# 判断四个点所连成的线段是否与原始图像的边缘有交点
def check_intersections(bbox_corners, w, h):edges = [((0.01, 0), (w, 0.01)),((w, 0.01), (w, h)),((w, h), (0.01, h)),((0.01, h), (0.01, 0.01))]point_list = []for i in range(len(bbox_corners)):p1 = bbox_corners[i]p2 = bbox_corners[(i + 1) % len(bbox_corners)]for edge in edges:intersection = line_intersection(p1, p2, edge[0], edge[1])if intersection:point_list.append(intersection)return point_list# 判断变换后的四个点是否位于原始图像范围内
def check_points_within_image(bbox_corners, w, h):point_list = []for point in bbox_corners:if 0 <= point[0] <= w and 0 <= point[1] <= h:point_list.append(point)return point_list# 分析所有点,找出一个能完整包含这些点的矩形
def get_bounding_box(point_list):min_x = min(point[0] for point in point_list)max_x = max(point[0] for point in point_list)min_y = min(point[1] for point in point_list)max_y = max(point[1] for point in point_list)return (min_x, min_y), (max_x, max_y)

使用方法如下:

# 数据增强 - 拉伸
if __name__ == "__main__":# 定义两个特殊点,即原始标注框的左上和右下special_points = [[3400, 1655],  # Example points, replace with your own[4550, 2350]]# 读取图片image_path = r'D:\data\拉伸原始图像.jpg'image = cv2.imread(image_path)# 获取图像宽度和高度height, width = image.shape[:2]# 定义顶点坐标,你会保留这四个点之内的图像,以便进行后续步骤trapezoid_vertices = np.array([[0 + generate_random_cut(width), 0 + generate_random_cut(height)],[width + generate_random_cut((width)), 0 + generate_random_cut((height))],[width + generate_random_cut(width), height + generate_random_cut(height)],[0 + generate_random_cut(width), height + generate_random_cut(height)]],dtype=np.float32)# 定义目标图像的顶点坐标,会将trapezoid_vertices所保留的图像拉伸,拉伸到target_trapezoid_vertices所对应的范围内target_trapezoid_vertices = np.array([[0 + generate_random_perspective(width), 0 + generate_random_perspective(height)],[width + generate_random_perspective(width), 0 + generate_random_perspective(height)],[width + generate_random_perspective(width),height + generate_random_perspective(height)],[0 + generate_random_perspective(width), height + generate_random_perspective(height)]],dtype=np.float32)# 计算透视变换矩阵,用于将image进行拉伸perspective_matrix = cv2.getPerspectiveTransform(trapezoid_vertices, target_trapezoid_vertices)# 进行透视变换trapezoid_image = cv2.warpPerspective(image, perspective_matrix, (width, height))# 将坐标框进行处理,利用perspective_matrix得到新的坐标框的左上角和右下角special_points = np.array(special_points, dtype='float32')special_points = np.array([special_points])transformed_special_points = cv2.perspectiveTransform(special_points, perspective_matrix)# 得到新的标注的框的四个顶点的坐标bbox_corners = get_bbox_corners(transformed_special_points)# 如果有超出图像边界的点,就计算与图像边界的交点,保存到point_list中。point_list = check_intersections(bbox_corners, width, height)# 把留在图像范围内的点也加到point_list中point_list.extend(check_points_within_image(bbox_corners, width, height))# 得出新的理想中的标注框的坐标if point_list:rect = get_bounding_box(point_list)else:rect = Noneprint(len(rect))# 就爱那个新的标注框的左上角和右下角绘制出来,以便判断是否正确for point in rect:x = tuple([int(i) for i in point])cv2.circle(trapezoid_image, x, 15, (256, 256, 256), 10)# 保存变换后的图像save_path = r'D:\data\拉伸结果.jpg'cv2.imwrite(save_path, trapezoid_image)

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

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

相关文章

vue学习笔记(购物车小案例)

用一个简单的购物车demo来回顾一下其中需要注意的细节。 先看一下最终效果 功能&#xff1a; &#xff08;1&#xff09;全选按钮和下面的商品项的选中状态同步&#xff0c;当下面的商品全部选中时&#xff0c;全选勾选&#xff0c;反之&#xff0c;则不勾选。 &#xff08…

SpringBoot新手快速入门系列教程四:创建第一个SringBoot的API

首先我们用IDEA新建一个项目&#xff0c;请将这些关键位置按照我的设置设置一下 接下来我将要带着你一步一步创建一个Get请求和Post请求&#xff0c;通过客户端请求的参数&#xff0c;以json格式返回该参数{“message”:"Hello"} 1,先在IDE左上角把这里改为文件模式…

华为云OBS 通过S3客户端访问

华为云好像没有对S3协议的支持说明其实底层是支持S3协议的。 使用S3的时候我们会需要endpoint&#xff0c;桶名字&#xff0c;region&#xff0c;AWS_ACCESS_KEY,AWS_SECRET_KEY 其中endpoint 就是图片中的&#xff0c;桶名字也很容易找到&#xff0c;region 就是你的endpoint…

【开源项目】LocalSend 局域网文件传输工具

【开源项目】LocalSend 局域网文件传输工具 一个免费、开源、跨平台的局域网传输工具 LocalSend 简介 LocalSend 是一个免费的开源跨平台的应用程序&#xff0c;允许用户在不需要互联网连接的情况下&#xff0c;通过本地网络安全地与附近设备共享文件和消息。 项目地址&…

liunx文件系统,日志分析

文章目录 1.inode与block1.1 inode与block概述1.2 inode的内容1.3 文件存储1.4 inode的大小1.5 inode的特殊作用 2.硬链接与软链接2.1链接文件分类 3.恢复误删除的文件3.1 案例:恢复EXT类型的文件3.2 案例:恢复XFS类型的文件3.2.1 xfsdump使用限制 4.分析日志文件4.1日志文件4.…

docker部署redis/mongodb/

一、redis 创建/root/redis/conf/redis.conf 全部执行命令如下 docker run -it -d --name redis -p 6379:6379 --net mynet --ip 172.18.0.9 -m 400m -v /root/redis/conf:/usr/local/etc/redis -e TXAsia/Shangehai redis redis-server /usr/local/etc/redis/redis.conf 部署…

Java 基础--File - IO流(2)

I/O流 定义 数据从硬盘流向内存为输入流&#xff0c;数据从内存流向硬盘为输出流。输入也叫读取数据&#xff0c;输出也叫写出数据。 IO分类 1.按照数据的流向分为&#xff1a;输入流和输出流 ①输入流&#xff1a;把数据从其他设备上读取到内存中的流 ②输出流&#xff1…

python小练习04

三国演义词频统计与词云图绘制 import jieba import wordcloud def analysis():txt open("三国演义.txt",r,encodingutf-8).read()words jieba.lcut(txt)#精确模式counts {}for word in words:if len(word) 1:continueelif word "诸葛亮" or word &q…

软件系统架构的一些常见专业术语

分层架构是逻辑上的&#xff0c;在物理部署上&#xff0c;三层结构可以部署在同一个物理机器上&#xff0c;但是随着网站业务的发展&#xff0c;必然需要对已经分层的模块分离部署&#xff0c;即三层结构分别部署在不同的服务器上&#xff0c;使网站拥有更多的计算资源以应对越…

前端Web开发HTML5+CSS3+移动web视频教程 Day4 CSS 第2天

P44 - P 四个知识点&#xff1a; 复合选择器 CSS特性 背景属性 显示模式 复合选择器 复合选择器仍然是选择器&#xff0c;只要是选择器&#xff0c;作用就是找标签。复合选择器就是把基础选择器进行组合使用。组合了之后就可以在大量的标签里面更快更精准地找标签了。找…

[附源码]基于Flask的演唱会购票系统

摘要 随着互联网技术的普及和发展&#xff0c;传统购票方式因其效率低下、流程繁琐等问题已难以满足现代社会的需求。本文设计并实现了一个基于Flask框架的演唱会购票系统&#xff0c;该系统集成了用户管理、演唱会信息管理、票务管理以及数据统计与分析等功能模块&#xff0c…

项目实战--Spring Boot + Minio文件切片上传下载

1.搭建环境 引入项目依赖 <!-- 操作minio的java客户端--> <dependency><groupId>io.minio</groupId><artifactId>minio</artifactId><version>8.5.2</version> </dependency> <!-- jwt鉴权相应依赖--> &…

Python + OpenCV 开启图片、写入储存图片

这篇教学会介绍OpenCV 里imread()、imshow()、waitKey() 方法&#xff0c;透过这些方法&#xff0c;在电脑中使用不同的色彩模式开启图片并显示图片。 imread() 开启图片 使用imread() 方法&#xff0c;可以开启图片&#xff0c;imread() 有两个参数&#xff0c;第一个参数为档…

[c++] 可变参数模版

前言 可变参数模板是C11及之后才开始使用,学校的老古董编译器不一定能用 相信大家在刚入门c/c时都接触过printf函数 int printf ( const char * format, ... ); printf用于将数据格式化输出到屏幕上,它的参数非常有意思,可以支持任意数量,任意类型的多参数.而如果我们想实现类…

陶建辉当选 GDOS 全球数据库及开源峰会荣誉顾问

近日&#xff0c;第二十三届 GOPS 全球运维大会暨 XOps 技术创新峰会在北京正式召开。本次会议重点议题方向包括开源数据库落地思考、金融数据库自主可控、云原生时代下数据库、数据库智能运维、数据库安全与隐私、开源数据库与治理。大会深入探讨这些方向&#xff0c;促进了数…

方法引用详解

什么是方法引用&#xff1f;&#xff1a;针对于函数式接口中的抽象方法 为什么用方法引用&#xff1f;&#xff1a;避免代码的重复&#xff0c;简便书写&#xff0c;提高效率 在使用Lambda表达式的时候&#xff0c;我们实际上传递进去的代码就是一种解决方案&#xff1a;拿参数…

服务器数据恢复—raid5阵列硬盘出现大量坏道的数据恢复案例

服务器存储数据恢复环境&故障&#xff1a; 一台DELL EqualLogic PS 4000存储中有一组由12块磁盘组建的raid5阵列&#xff0c;存储空间划分3个同等大小的卷&#xff0c;采用的VMFS文件系统。 两块硬盘指示灯亮黄色&#xff0c;raid5阵列崩溃&#xff0c;存储变得不可用。 服…

代码随想录——划分字母区间(Leetcode763)

题目链接 贪心 class Solution {public List<Integer> partitionLabels(String s) {int[] count new int[27];Arrays.fill(count,0);// 统计元素最后一次出现的位置for(int i 0; i < s.length(); i){count[s.charAt(i) - a] i;}List<Integer> res new Ar…

mac怎么压缩pdf文件大小,mac压缩pdf文件大小不改变清晰度

在数字化时代&#xff0c;pdf格式因其良好的兼容性和稳定性&#xff0c;成为了文档分享和传输的首选。然而&#xff0c;随着文件内容的丰富&#xff0c;pdf文件的体积也越来越大&#xff0c;给存储和传输带来了不小的困扰。本文将揭秘几种简单有效的pdf文件压缩方法&#xff0c…

多粒度封锁-封锁粒度、多粒度封锁模式

一、引言 1、若采用封锁技术实现并发控制&#xff0c;事务在访问数据库对象前要在数据库对象上加锁&#xff0c;为提高事务的并发程度&#xff0c;商用DBMS会采用一种多粒度封锁方法 2、事务可访问的数据库对象可以是逻辑单元&#xff0c;包括关系、关系中的元组、关系的属性…