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

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.mzph.cn/diannao/40215.shtml

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

相关文章

[C++初阶]vector的初步理解

一、标准库中的vector类 1.vector的介绍 1. vector是表示可变大小数组的序列容器 &#xff0c; 和数组一样&#xff0c;vector可采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问&#xff0c;和数组一样高效。但是又不像数组&#xff0c;它的大…

Java学习高级一

修饰符 static 类变量的应用场景 成员方法的分类 成员变量的执行原理 成员方法的执行原理 Java之 main 方法 类方法的常见应用场景 代码块 设计模式 单例设计模式 饿汉式单例设计模式 懒汉式单例设计模式 继承 权限修饰符

小红书 达芬奇:生活问答 AI 机器人

小红书去年 9 月开始内测的生活问答 AI 机器人&#xff1a;达芬奇&#xff0c;现在可以在小红书 APP 上用了 得益于小红书平台的特性&#xff0c;该助手擅长吃、住、宠、喝、学等等各类生活知识&#xff0c;目前还在搞活动&#xff0c;写评测笔记最高得 666 元

为什么不能在foreach中删除元素

文章目录 快速失败机制&#xff08;fail-fast&#xff09;for-each删除元素为什么报错原因分析逻辑分析 如何正确的删除元素remove 后 breakfor 循环使用 Iterator 总结 快速失败机制&#xff08;fail-fast&#xff09; In systems design, a fail-fast system is one which i…

网络基础:EIGRP

EIGRP&#xff08;Enhanced Interior Gateway Routing Protocol&#xff09;是由思科开发的一种高级距离矢量路由协议&#xff0c;结合了距离矢量和链路状态路由协议的优点&#xff1b;EIGRP具有快速收敛、高效带宽利用、负载均衡等特点&#xff0c;适用于各种规模的网络。EIGR…

python sklearn机械学习-数据预处理

&#x1f308;所属专栏&#xff1a;【机械学习】✨作者主页&#xff1a; Mr.Zwq✔️个人简介&#xff1a;一个正在努力学技术的Python领域创作者&#xff0c;擅长爬虫&#xff0c;逆向&#xff0c;全栈方向&#xff0c;专注基础和实战分享&#xff0c;欢迎咨询&#xff01; 您…

【设计模式】策略模式(定义 | 特点 | Demo入门讲解)

文章目录 定义策略模式的结构 QuickStart | DemoStep1 | 策略接口Step2 | 策略实现Step3 | 上下文服务类Step4 | 客户端 策略模式的特点优点缺点 定义 策略模式Strategy是一种行为模式&#xff0c;它能定义一系列算法&#xff0c;并将每种算法分别放入到独立的类中&#xff0c…

负载均衡类型和算法解析

假如你正在设计和开发一个分布式服务系统&#xff0c;系统中存在一批能够独立运行的服务&#xff0c;而在部署上也采用了集群模式以防止出现单点故障。所谓集群&#xff0c;就是指将多个服务实例集中在一起&#xff0c;对外提供同一业务功能&#xff0c;也就是任意请求都可以由…

吉利银河L6 AQS空气质量监控系统

结论 顶配才有AQS 开启空调且auto模式 则默认开启AQS 无法关闭AQS AQS的作用 银河L6 AQS触发 和 图标 AQS官方配置参数 官方文档 吉利用户手册

开源即正义,3D软件Blender设计指南

在当今数字化时代&#xff0c;开源软件的崛起不仅代表着技术的发展&#xff0c;更象征着一种信息自由和技术民主的理念。其本质是集众人之智&#xff0c;共同去完善一个软件&#xff0c;最终使双方互惠共赢。具体来说&#xff0c;开源的价值&#xff0c;在于打破资源垄断&#…

苹果AI的国产大模型之争,没有悬念

文 | 智能相对论 作者 | 陈泊丞 苹果终于公布了最新的AI进程。 一个月前&#xff0c;正如此前预期的那样&#xff0c;人工智能是今年 WWDC 发布会的焦点。全程105分钟的主题演讲&#xff0c;就有40多分钟用于介绍苹果的AI成果。 苹果似乎还有意玩了一把“谐音梗”&#xff…

用机器改变人类方向

1800 世纪初&#xff0c;美国迎来了工业革命&#xff0c;这是一个由技术进步推动的变革时代。新机器和制造技术的引入重塑了经济格局&#xff0c;提高了生产效率&#xff0c;同时减少了某些领域对手工劳动的需求。因此&#xff0c;这种转变导致了失业。 如今&#xff0c;我们看…

实现点击按钮导出页面pdf

在Vue 3 Vite项目中&#xff0c;你可以使用html2canvas和jspdf库来实现将页面某部分导出为PDF文档的功能。以下是一个简单的实现方式&#xff1a; 1.安装html2canvas和jspdf&#xff1a; pnpm install html2canvas jspdf 2.在Vue组件中使用这些库来实现导出功能&#xff1a;…

统计信号处理基础 习题解答11-11

题目 考虑矢量MAP估计量 证明这个估计量对于代价函数 使贝叶斯风险最小。其中&#xff1a;, &#xff0c;且. 解答 贝叶斯风险函数&#xff1a; 基于概率密度的非负特性&#xff0c;上述对积分要求最小&#xff0c;那就需要内层积分达到最小。令内层积分为&#xff1a; 上述积…

苹果Mac电脑能玩什么游戏 Mac怎么运行Windows游戏

相对于Windows平台来说&#xff0c;Mac电脑可玩的游戏较少。虽然苹果设备的性能足以支持各种大型游戏&#xff0c;但由于系统以及苹果配套服务的限制&#xff0c;很多游戏无法在Mac系统中运行。不过&#xff0c;借助虚拟机软件&#xff0c;Mac电脑可以突破系统限制玩更多的游戏…

光照老化试验箱在化工产品暴晒测试中的应用

概述 光照老化试验箱是一种模拟自然光照条件下材料老化情况的实验设备&#xff0c;广泛应用于化工、建材、电子、汽车等行业中对材料的耐候性、耐光性能等进行测试。通过模拟日光中的紫外线和温度等环境因素&#xff0c;加速材料老化过程&#xff0c;以此评估材料在长期使用中…

2024阿里云大模型自定义插件(如何调用自定义接口)

1&#xff0c;自定义插件入口 2&#xff0c;插件定义&#xff1a;描述插件的参数 2.1&#xff0c;注意事项&#xff1a; 2.1.1&#xff0c;只支持json格式的参数&#xff1b;只支持application/JSON&#xff1b;如下图&#xff1a; 2.1.2&#xff0c;需要把接口描述进行修改&a…

03:Spring MVC

文章目录 一&#xff1a;Spring MVC简介1&#xff1a;说说自己对于Spring MVC的了解&#xff1f;1.1&#xff1a;流程说明&#xff1a; 一&#xff1a;Spring MVC简介 Spring MVC就是一个MVC框架&#xff0c;Spring MVC annotation式的开发比Struts2方便&#xff0c;可以直接代…

LeetCode 算法:二叉搜索树中第K小的元素 c++

原题链接&#x1f517;&#xff1a;二叉搜索树中第K小的元素 难度&#xff1a;中等⭐️⭐️ 题目 给定一个二叉搜索树的根节点 root &#xff0c;和一个整数 k &#xff0c;请你设计一个算法查找其中第 k 小的元素&#xff08;从1开始计数&#xff09;。 示例 1&#xff1a;…

第一周题目总结

1.车尔尼有一个数组 nums &#xff0c;它只包含 正 整数&#xff0c;所有正整数的数位长度都 相同 。 两个整数的 数位不同 指的是两个整数 相同 位置上不同数字的数目。 请车尔尼返回 nums 中 所有 整数对里&#xff0c;数位不同之和。 示例 1&#xff1a; 输入&#xff1a…