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

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 * x
    max_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 * x
    max_value = max_perspective * x

    random_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]) and
            q[1] <= max(p[1], r[1]) and q[1] >= min(p[1], r[1])):
        return True
    return 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  # collinear
    elif val > 0:
        return 1  # clockwise
    else:
        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 True

    if o1 == 0 and on_segment(p1, p2, q1):
        return True

    if o2 == 0 and on_segment(p1, q2, q1):
        return True

    if o3 == 0 and on_segment(p2, p1, q2):
        return True

    if o4 == 0 and on_segment(p2, q1, q2):
        return True

    return False


# 判断四个点所连成的线段是否与原始图像的边缘有交点
def line_intersection(p1, p2, edge_start, edge_end):
    if not segments_intersect(p1, p2, edge_start, edge_end):
        return None

    xdiff = (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 None

    d = (det(p1, p2), det(edge_start, edge_end))
    x = det(d, xdiff) / div
    y = det(d, ydiff) / div
    return 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 = None
    print(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.mfbz.cn/a/769718.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

黑龙江等保测评知多少

黑龙江的等保测评&#xff0c;即网络安全等级保护测评&#xff0c;是指在中国黑龙江地区针对信息系统的安全性进行的一系列评估活动&#xff0c;确保这些系统符合国家规定的安全等级标准。这个过程涉及到多个阶段&#xff0c;旨在提升信息系统整体的安全水平。 以下是黑龙江等…

程序包lombok不存在,解决办法。

当前方法是针对于代码没有报错&#xff0c;本身有lombok jar包的情况 1.找到本地maven仓库中的已经下载好的lombok包&#xff0c;删掉。 2. 直接刷新maven&#xff0c;会重新下载lombok jar包&#xff0c;此时再启动项目时就可以正常运行了。

第四届数字安全大会:AI时代数据安全策略与天空卫士创新实践

2024年6月22日&#xff0c;以 “新质•真能力”为主题的第四届数字安全大会在北京隆重召开。这场由数世咨询和CIO时代联合主办的行业盛会&#xff0c;集中探讨了大模型、数据治理与流通、以及安全运营等当前最前沿的议题。大会吸引了来自不同行业的首席信息官&#xff08;CIO&a…

和闺蜜的泰国之旅

每当我回想起那次和闺蜜丽丽&#xff08;全名罗莉&#xff09;的泰国之旅&#xff0c;心中总是涌起复杂的情绪。那段经历仿佛一场噩梦&#xff0c;至今仍无法从脑海中挥去。 我们满怀期待地抵达曼谷&#xff0c;热带的阳光、繁忙的街道、美味的街头小吃&#xff0c;都让我们兴…

用例子和代码了解词嵌入和位置编码

1.嵌入&#xff08;Input Embedding&#xff09; 让我用一个更具体的例子来解释输入嵌入&#xff08;Input Embedding&#xff09;。 背景 假设我们有一个非常小的词汇表&#xff0c;其中包含以下 5 个词&#xff1a; "I""love""machine"&qu…

【后端面试题】【中间件】【NoSQL】MongoDB提高可用性的方案(主从结构、仲裁节点、分片、写入语义)

主从结构 MongoDB的高可用和别的中间件的高可用方案基本类似。比如在MySQL里&#xff0c;接触了分库分表和主从同步&#xff1b;在Redis里&#xff0c;Redis也有主从结构&#xff1b;在Kafka里&#xff0c;分区也是有主从结构的。 所以先介绍启用了主从同步 我们的系统有一个关…

使用ChatGPT自动生成测试用例思维导图

使用ChatGPT自动生成测试用例思维导图 引言ChatGPT在测试用例编写中的应用全面覆盖测试场景边界测试避免测试用例重复 借助ChatGPT生成测试用例思维导图准备工作步骤一&#xff1a;与ChatGPT对话步骤二&#xff1a;生成思维导图代码 结语 引言 在编写测试用例时&#xff0c;测…

物联网应用Fast ingest

一、原文路径 Tuning the System Global Area 二、翻译 1、原理 Fast ingest 优化是针对高并发&#xff0c;单行数据的插入这种场景的。比如IOT应用采集&#xff08;很符合国网的用采数据场景&#xff09;。 Fast ingest 使用MEMOPTIMIZE_WRITE 提示来插入数据到 MEMOPTIM…

WordPress付费进群V2主题,多种引流方法,引私域二次变现

全新前端UI界面&#xff0c;多种前端交互特效让页面不再单调&#xff0c;进群页面群成员数&#xff0c;群成员头像名称&#xff0c;每次刷新页面随机更新不重复&#xff0c;最下面评论和点赞也是如此随机刷新不重复 进群页面简介&#xff0c;群聊名称&#xff0c;群内展示&…

thinkphp6/8 验证码

html和后台验证代码按官方来操作 ThinkPHP官方手册 注意&#xff1a; 如果验证一直失败&#xff0c;看看Session是否开启&#xff0c; 打印dump(session_status());结果2为正确的&#xff0c; PHP_SESSION_DISABLED: Session功能被禁用&#xff08;返回值为0&#xff09;。…

WAF的新选择,雷池 SafeLine-安装动态防护使用指南

什么是 WAF WAF 是 Web Application Firewall 的缩写&#xff0c;也被称为 Web 应用防火墙。 区别于传统防火墙&#xff0c;WAF 工作在应用层&#xff0c;对基于 HTTP/HTTPS 协议的 Web 系统有着更好的防护效果&#xff0c;使其免于受到黑客的攻击&#xff1b; 通俗来讲&#…

vue3+ts项目中.env配置环境变量与情景配置

一、环境变量配置 官网https://cn.vitejs.dev/guide/env-and-mode.html#intellisense 1. 新建.env开头的文件在根目录 为了防止意外地将一些环境变量泄漏到客户端&#xff0c;只有以 VITE_ 为前缀的变量才会暴露给经过 vite 处理的代码 .env 所有环境默认加载 .env.developm…

C++ 语法

一、头文件与源文件 头文件用于声明函数,类似于java中service层的接口; 源文件用于实现头文件函数,相当于java中serviceImpl层的实现类; 定义接口 实现接口 使用接口 二、指针概述 定义与使用 定义一个指针p用于存a变量的内存地址,即指针就是地址; 解引用可以获取或修改…

并发编程面试题1

一、原子性高频问题: 1.1 Java中如何实现线程安全? 多线程操作共享数据会出现问题。可以使用锁来解决: 悲观锁: 使用 synchronized 和 Lock乐观锁: 使用 CAS(Compare-And-Swap)可以根据业务情况选择 ThreadLocal,让每个线程处理自己的数据。 1.2 CAS底层实现 回答思…

Prometheus 监控服务器

Prometheus概述 组件化设置&#xff1a;nginx ,ceph , Prometheus 部署Prometheus服务器 配置时间 安装Prometheus服务器 访问web页面&#xff1a;http://192.168.88.5:9090/ 添加被监控端 监控方式&#xff1a; 拉取&#xff1a;pull。监控端联系被监控端&#xff0c;采集数…

116-基于5VLX110T FPGA FMC接口功能验证6U CPCI平台

一、板卡概述 本板卡是Xilinx公司芯片V5系列芯片设计信号处理板卡。由一片Xilinx公司的XC5VLX110T-1FF1136 / XC5VSX95T-1FF1136 / XC5VFX70T-1FF1136芯片组成。FPGA接1片DDR2内存条 2GB&#xff0c;32MB Nor flash存储器&#xff0c;用于存储程序。外扩 SATA、PCI、PCI expres…

STM32远程烧录程序

目录 简介 不同的程序下载方式 ICP&#xff1a;In-Circuit Programming ISP&#xff1a;In-System Programing IAP&#xff1a;In-Application Programming BootLoader Bootloader 是什么&#xff1f; STM32的启动方式 存储器组织 存储器映像 嵌入式SRAM 嵌入式FL…

【JVM系列】内存泄漏

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

Python数据分析-股票数据分析(GARCH模型)

一、研究背景 随着金融市场的不断发展和全球经济的日益复杂&#xff0c;市场波动性和风险管理成为投资者和金融机构关注的焦点。波动率是衡量市场风险的重要指标&#xff0c;准确预测和评估波动率对于资产定价、风险控制和投资决策具有重要意义。在金融时间序列分析中&#xf…

唐山养老院哪家好---老了怎么过?到这里,享受生活的每一刻!

随着时间的流逝&#xff0c;我们每个人都将迎来老年时光&#xff0c;而"老了&#xff0c;怎么过&#xff1f;"这个问题&#xff0c;虽然简单&#xff0c;却深深触动了无数人的心。 面对老年生活&#xff0c;每个人都有不同的选择和追求。有的人选择顺其自然&#xf…