关于Yolo标注归一化的详解(1)
关于Yolo标注归一化的详解(1)
上一期我们讲了如何从一个被打上标注点位的Json文件中提取出原始图片和对应点位,并且对点位进行了归一化,按照Yolo的归一化基本格式和yolo-pose姿态格式进行了不同的参考
但是还有部分问题是我们实际应用是需要去考虑的,在这里提出几点根据实际处理时发生的问题
图片原始像素小于640x640
图片原始像素大于640x640
图片改动后,点位要如何变动
将小于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,点位根据图像一起等比例放大
根据我们所得到的图片,我们分成两种可能性
图片的宽>高
图片的高>宽
# 如果宽大于高
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像素的图片,以及考虑点位的相对位置