关于Yolo标注归一化的详解(1)

Author Avatar
根骨何尽
发表:2024-03-28 14:49:16
修改:2024-03-28 14:56:09

关于Yolo标注归一化的详解(1)

上一期我们讲了如何从一个被打上标注点位的Json文件中提取出原始图片和对应点位,并且对点位进行了归一化,按照Yolo的归一化基本格式和yolo-pose姿态格式进行了不同的参考

但是还有部分问题是我们实际应用是需要去考虑的,在这里提出几点根据实际处理时发生的问题
  1. 图片原始像素小于640x640

  2. 图片原始像素大于640x640

  3. 图片改动后,点位要如何变动

    • 将小于640x640的图片放大后的点位变化

    • 将大于640x640的图片缩小后的点位变化

    • 保持图片原始比例的情况下将图片放大(等比)

    • 破坏图片原始比例的情况下将图片放大(损失比例)

将以上4点进行思考论证后,我们依照上期的归一化公式,可以正常的对点位进行归一化

公式

对于高和宽和点位的归一化,正常除以图像宽高即可

对于裁剪过后的图像,计算方式为:

宽:(中点坐标减去x最小坐标,即为准确的中点坐标) / (x最大坐标-x最小坐标,即图像的宽)

高:(中点坐标减去y最小坐标,即为准确的中点坐标) / (y最大坐标-y最小坐标,即图像的宽) 此两句对裁剪后的图像恒成立

点位: 点位乘以图像改动的比例 / 图像的分辨率

准备阶段

所需的JS文件:

链接:https://pan.baidu.com/s/1kOzhO4Qthc5sDeE3Wv3p_A?pwd=zzzz

提取码:zzzz

开始编写代码

依照上期的步骤,做到将图片提取后,在更改图片的比例值的步骤截止,以下我们使用新的思考方式

根据问题3中的(将小于640x640的图片放大后的点位变化),我们不使用暴力修改比例值,我们首先以原图叠加的方式

import os
import json
import cv2
import numpy as np
import base64

# img文件目录,json点位目录,txt标注目录,new_img_path为新的图片保存路径
img_path = r'd:\project\imgs'
json_path = r'd:\project\json'
txt_path = r'd:\project\txts'
new_img_path = r'd:\project\imgs_640
首先读取imgs目录下的所有图片列表,同时要根据图片的名字在txts目录下找到相应的标注
path_lib = os.listdir(img_path)
for paths in path_lib:
	# 首先读取原始图片
	original_image =cv2.imread(os.path.join(img_path,paths))
	# 获取原始图片的高和宽
    original_height, original_width = original_image.shape[:2]
	# 要修改成640x640分辨率的图片	
    new_width ,new_height = 640,640
	# 要使用叠加的方式创建图片,我们要先创建一张普通的背景
	# 创建一个640x640的白色背景图片
	white_image = np.full((new_width, new_height, 3), 255, dtype=np.uint8)
创建完一张原始像素就为640x640的图片后,我们要对我们的图像进行思考,如何保持等比例的情况将图片放大到最贴近640x640,点位根据图像一起等比例放大

根据我们所得到的图片,我们分成两种可能性

  1. 图片的宽>高

  2. 图片的高>宽

	# 如果宽大于高
    if original_width > original_height:
		# 修改比例 = 640 / 原始图像宽
        resize_ratio = new_width / original_width
		# 新的高度 = int(修改比例 * 原始图像高)
        new_height = int(resize_ratio * original_height)
		# 将原始图片进行等比例的最大修改(不会超过640)
        original_image2 = cv2.resize(original_image(new_width,new_height))
		# 我们要设定一个值,如果宽>高,则为1,否则为0
        original = 1
    else:
	# 如果高大于宽
        resize_ratio = new_width / original_height
        new_width = int(resize_ratio * original_width)
        original_image2 = cv2.resize(original_image, (new_width, new_height))
		# 返回高>宽的情况
        original = 0

以上我们将原始图片以宽最大比例放大到640,或以高为最大比例放大到640,并且保证了宽/高跟着原本图片的比例一起放大

接着我们试图将修改后的图片叠加到640x640的白色图片上

在该程序中,我们会将放大后的图片放置在新图的右上角

# 无论是哪种情况,我们都要计算x的偏移值
x = 640 - original_image2.shape[1]
# 高度默认从0开始
y = 0
# 记录放大后图片的高和宽
shape2 = original_image2.shape[:2]
# 使用max来保证值不会变为负数
x_code = max(0,x)
y_code = max(0,y)
# 叠加图片
white_image[y_code:y_code + original_image2.shape[0],x_code:x_code + original_image2.shape[1] ] = original_image2
# 写下新图片
cv2.imwrite(os.path.join(new_img_path,paths),white_image)
当我们有了新的图片后,我们要思考点位的偏移值跟随x的数值
我们先读取txts的标注点位
# 根据图片的名字,修改后缀为txt,去txt_path路径下找找到标注文件读取
	# 保证图片的后缀是jpg
	if os.path.splitext(paths)[1] == '.jpg':
		# 打开图片对应的标注文件
		with open(os.path.join(json_path, paths.replace('.jpg', '.json')), 'r') as Js:
			js = json.load(Js)
			label_points = [shape['points'][0] for shape in js['shapes'] ]
			strs = []
			for x,y in label_points:
				strs.append((x,y))
我们从json文件中重新获取了3对点位

开始对点位进行处理

# 返回读取的原始图片的大小
height = shape[0]
width = shape[1]
# 点位要放大的比例
scale_x = 640 / width
scale_y = 640 / height
# 根据original得知这个图片是宽>高还是高>宽
# 然后遍历点位乘以修改比例
if original == 1 :
	x_y_points = [(x*scale_x,y*scale_x) for x,y in strs]
else :
	# 如果高>宽,那么x要向右侧进行偏移,保证点位在右侧
	cheap_x = 640 - shape2[1]
	x_y_points = [(x * scale_y+cheap_x, y * scale_y) for x, y in strs]

写入点位

Strs = ''
for x,y in x_y_points:
	Strs = Strs + f'{x} {y} '
Strs = '0 ' + Strs
with open(os.path.join(txt_path, paths.replace('.jpg', '.txt')), 'w') as ftxt:
	ftxt.writelines(Strs)
至此我们将第一种可能性,图片小于640x640的情况进行了分析,在保证图片不被暴力修改的情况下获取了最大限度放大的原始图片

如果代码报错,说明没有此文件夹,我们只需要只用os库的创建文件夹就可以

if not os.path.exists(new_img_path): os.makedirs(new_img_path)

if not.os.path.exists(txt_path): os.makedirs(txt_path)

下一篇文章中,我们将分析图片大于640x640的情况,分析如何裁剪出640x640像素的图片,以及考虑点位的相对位置

评论