7.2 简单的图片标注为xml代码和图片增强代码(python)

发布于:2024-09-18 ⋅ 阅读:(129) ⋅ 点赞:(0)

       yolov5已经普及常用,最常规的步骤就是获取数据(图片)、标注图片(标注xml文件)、对标注的数据增强(椒盐模糊、高斯模糊、翻转等) 、训练数据。

        在这里,假如客户新增一个目标,需要客户自己标注数据、增强数据和训练数据不合理,我们可以希望实现这样一个场景:建立一个界面,打开界面后:1.客户对新的产品从不同的角度拍七八张照片;2.客户在界面上对七八张图片的目标用长方形画框;3.界面系统自动对这几张标注好的图片进行数据扩增;4.将扩增后的数据自动移入训练的文件夹;5.训练模型。

        其实,希望客户做以上的1、2、5三步就行,3和4由界面系统自动完成,这样就能很简单的完成新模型的标注和训练。下面分别是标注和数据扩增的代码。

1.图片标注

代码如下:

import cv2
import numpy as np

#1. 在图片中标注矩形框
def draw_circle(event, x, y, flags, param):
    global ix, iy, drawing, px, py

    if event == cv2.EVENT_LBUTTONDOWN:
        drawing = True
        ix, iy = x, y
    elif event == cv2.EVENT_MOUSEMOVE:
        if drawing == True:
            # cv2.rectangle(img, (ix, iy), (px, py), (0, 0, 0), thickness=2)  # 将刚刚拖拽的矩形涂黑
            # cv2.rectangle(img, (ix, iy), (x, y), (0, 255, 0), 0)
            px, py = x, y
    elif event == cv2.EVENT_LBUTTONUP:
        drawing = False
        cv2.rectangle(img, (ix, iy), (x, y), (0, 255, 0), 0)
        preds.append([classes, [iy, ix, y, x]])
        # px, py = -1, -1

# 2. 将矩形框的坐标和名称写入xml文件
# 2.写入xml文件
def img_xml(img_path, xml_path, img_name, pred):
    if len(pred) != 0:

        # 1.读取图片(xml需要写入图片的长宽高)
        img = cv2.imread(img_path)

        # 2.写入xml文件
        # (1)写入文件头部
        files_path = img_path.split("\\")[-2]
        print("..:", files_path)

        xml_file = open((xml_path + img_name + '.xml'), 'w')
        xml_file.write('<annotation>\n')
        xml_file.write('	<folder>' + files_path + '</folder>\n')
        xml_file.write('	<filename>' + img_name + '.jpg' + '</filename>\n')
        xml_file.write('	<path>' + img_path + '</path>\n')

        xml_file.write('	<source>\n')
        xml_file.write('		<database>Unknown</database>\n')
        xml_file.write('	</source>\n')

        # (2)写入图片的长宽高信息
        xml_file.write('	<size>\n')
        xml_file.write('		<width>' + str(img.shape[1]) + '</width>\n')
        xml_file.write('		<height>' + str(img.shape[0]) + '</height>\n')
        xml_file.write('		<depth>' + str(img.shape[2]) + '</depth>\n')
        xml_file.write('	</size>\n')

        xml_file.write('	<segmented>0</segmented>\n')

        # 3.写入字符串信息:[["water",[15,20,30,40]],["red",[12,13,14,15]]]
        for item in pred:
            xml_file.write('	<object>\n')
            xml_file.write('		<name>' + str(item[0]) + '</name>\n')
            xml_file.write('		<pose>Unspecified</pose>\n')
            xml_file.write('		<truncated>0</truncated>\n')
            xml_file.write('		<difficult>0</difficult>\n')
            xml_file.write('		<bndbox>\n')

            # 写入字符串信息
            # [top, left, bottom, right]
            xml_file.write('			<xmin>' + str(item[1][1]) + '</xmin>\n')
            xml_file.write('			<ymin>' + str(item[1][0]) + '</ymin>\n')
            xml_file.write('			<xmax>' + str(item[1][3]) + '</xmax>\n')
            xml_file.write('			<ymax>' + str(item[1][2]) + '</ymax>\n')

            xml_file.write('		</bndbox>\n')
            xml_file.write('	</object>\n')
        xml_file.write('</annotation>\n')



if __name__ == "__main__":

    # 1.读取图片
    img_path = "D:\AI\yq\datas\imgs\\4.jpg"
    img = cv2.imread(img_path)

    xml_path = "D:\AI\yq\datas\labels\\"
    img_name = "4"

    # pred = [["water", [15, 20, 30, 40]], ["red", [12, 13, 14, 15]]]  #[top,left,bottom,right]
    classes = "luoding"
    preds = []

    #2. 鼠标给图片画矩形,并将矩形的左上角和右下角的坐标记录下来
    drawing = False  # 鼠标按下为真
    mode = True  # 如果为真,画矩形,按m切换为曲线
    ix, iy = -1, -1
    px, py = -1, -1

    cv2.namedWindow('image')
    cv2.setMouseCallback('image', draw_circle)

    while (1):
        cv2.imshow('image', img)
        k = cv2.waitKey(1) & 0xFF
        if k == ord('q'):
            print(preds)
            img_xml(img_path, xml_path, img_name, preds)
            break
        elif k == 27:
            break
    cv2.destroyAllWindows()

      以上是标注图片,并生成xml文件的代码,结果如下:

    

        在以上代码中,有2个函数,函数draw_circle(event, x, y, flags, param)是在图片中用鼠标画矩形框,并将矩形框的左上角和右下角的坐标记录下来(preds用来记录每个目标的坐标和目标名称),preds的打印结果如下:

        这个数据加上图片的路径和名称就可以写出图片目标的xml文件了。

        函数img_xml(img_path, xml_path, img_name, pred)是将用鼠标标注的图片信息写入xml文件,函数中的参数为

img_path:标注图片的路径

xml_path:保存xml文件的路径

img_name:保存图片的名称(不带.后缀,如1.jpg的图片名称为1)

pred:图片中目标的信息,是函数draw_circle(event, x, y, flags, param)的返回值

输出结果如下:

2.数据扩增

        常见的数据扩增的方式有:原数据成倍增加、椒盐模糊、高斯模糊、水平翻转、垂直翻转、按照某个角度翻转、平移、缩放、裁剪、亮度、直方图均衡化、调整白平衡、mixup、cutmix、cutout等。这些数据增强的方式,有的只需要处理图片,而图片中的xml文件中的坐标值不变,有的则会改变目标在图片中的具体位置,这时需要修改xml文件中的坐标值。下面分别介绍不同的扩增:

2.1图片的水平翻转

        图片水平翻转后,其目标的坐标会发生变换。其xml文件需要修改扩增后的图片路径、图片名称、图片中目标的名称、图片中目标的坐标,其他信息可以根据自己的需要对该代码稍微修改即可。代码如下:

import cv2
import os
import random
import xml.etree.ElementTree as ET
from shutil import copyfile


# 2.写入xml文件
def img_xml(img_path, xml_path, img_name, pred):
    if len(pred) != 0:

        # 1.读取图片(xml需要写入图片的长宽高)
        img = cv2.imread(img_path)

        # 2.写入xml文件
        # (1)写入文件头部
        files_path = img_path.split("\\")[-2]
        print("..:", files_path)

        xml_file = open((xml_path + img_name + '.xml'), 'w')
        xml_file.write('<annotation>\n')
        xml_file.write('	<folder>' + files_path + '</folder>\n')
        xml_file.write('	<filename>' + img_name + '.jpg' + '</filename>\n')
        xml_file.write('	<path>' + img_path + '</path>\n')

        xml_file.write('	<source>\n')
        xml_file.write('		<database>Unknown</database>\n')
        xml_file.write('	</source>\n')

        # (2)写入图片的长宽高信息
        xml_file.write('	<size>\n')
        xml_file.write('		<width>' + str(img.shape[1]) + '</width>\n')
        xml_file.write('		<height>' + str(img.shape[0]) + '</height>\n')
        xml_file.write('		<depth>' + str(img.shape[2]) + '</depth>\n')
        xml_file.write('	</size>\n')

        xml_file.write('	<segmented>0</segmented>\n')

        # 3.写入字符串信息:[["water",[15,20,30,40]],["red",[12,13,14,15]]]
        for item in pred:
            xml_file.write('	<object>\n')
            xml_file.write('		<name>' + str(item[0]) + '</name>\n')
            xml_file.write('		<pose>Unspecified</pose>\n')
            xml_file.write('		<truncated>0</truncated>\n')
            xml_file.write('		<difficult>0</difficult>\n')
            xml_file.write('		<bndbox>\n')

            # 写入字符串信息
            # [top, left, bottom, right]
            xml_file.write('			<xmin>' + str(item[1][1]) + '</xmin>\n')
            xml_file.write('			<ymin>' + str(item[1][0]) + '</ymin>\n')
            xml_file.write('			<xmax>' + str(item[1][3]) + '</xmax>\n')
            xml_file.write('			<ymax>' + str(item[1][2]) + '</ymax>\n')

            xml_file.write('		</bndbox>\n')
            xml_file.write('	</object>\n')
        xml_file.write('</annotation>\n')



# 3. 图片的水平翻转
def img_horizen_rotation(img, xmls,img3_path, xmls_path):
    print("...............   图片翻转   ..................")

    # 1. 图片翻转
    imgs = cv2.flip(img, 1)

    # 2. xml 文件的修改
    tree = ET.parse(xml_path)
    root = tree.getroot()
    ss = []
    modifys = []
    width = 0
    for element in root.iter():
        #(1 按照[y_min, x_min, y_max, x_max]保存数据,用来后面处理数据
        # print(element.tag, element.text)
        if element.tag == "width":
            width = int(element.text)
            # print("width:", width)
        if element.tag == "name":
            name = element.text
            ss.append(name)
        if element.tag == "xmin":
            xmin = int(element.text)
            # print("xmin:", xmin)
            ss.append(xmin)
        if element.tag == "ymin":
            ymin = int(element.text)
            # print("ymin:", ymin)
            ss.append(ymin)
        if element.tag == "xmax":
            xmax = int(element.text)
            # print("xmax:", xmax)
            ss.append(xmax)
        if element.tag == "ymax":
            ymax = int(element.text)
            # print("ymax:", ymax)
            ss.append(ymax)
        if len(ss) == 5:
            # print(ss)
            x_min = width - ss[3]
            y_min = ss[2]
            x_max = width - ss[1]
            y_max = ss[4]

            modifys.append([ss[0], [y_min, x_min, y_max, x_max]])
            ss = []
    img_name = img3_path.split("\\")[-1].split(".")[0]

    # (2 将翻转后的图片和xml文件保存在特点路径
    img_xml(img_path, xmls_path, img_name, modifys)
    cv2.imwrite(img3_path, imgs)

    # 3. 画图
    for item in modifys:
        a = (item[1][1], item[1][0])
        b = (item[1][3], item[1][2])
        cv2.rectangle(imgs, a, b, (0, 255, 0), 2)

    cv2.imshow("s2", imgs)
    cv2.waitKey(0)

    return img, xmls




if __name__ == "__main__":

    # 要处理的图片和对应的xml文件路径
    img_path = "D:\AI\yq\datas\strange\imgs\\1.jpg"
    xml_path = "D:\AI\yq\datas\strange\labels\\1.xml"

    # 保存处理后的图片和xml文件路径
    img_saves = "D:\AI\yq\datas\strange\imgs_strange\\"
    xml_saves = "D:\AI\yq\datas\strange\labels_strange\\"

    # 水平翻转后的图片路径
    img3_path = "D:\AI\yq\datas\imgs\\5.jpg"

    # 3. 图片的水平翻转
    img3, xml3 = img_horizen_rotation(img, xml_path, img3_path, xml_saves)




        以上代码中进行了原图片中目标坐标的转换,并写入新的xml文件。结果如下:

      

        后面一张是前面一张翻转后的图片,画的框是前面图片标注的框翻转后画在后面图片的目标上,可以看到转换后的坐标框是和目标物体吻合的。

2.2  椒盐算法

代码如下:

# 1.椒盐算法增强
   #  (1.img:要处理的图片;
   #  (2.xmls:要处理的图片对应的xml文件;
   #  (3. percentage: 椒盐算法加噪点的比率
def pepper_and_salt(img, xmls, img3_path, xml_saves, percentage):

    # 1. 椒盐算法
    num=int(percentage*img.shape[0]*img.shape[1])#  椒盐噪声点数量
    random.randint(0, img.shape[0])
    img2=img.copy()
    for i in range(num):
        X=random.randint(0,img2.shape[0]-1)#从0到图像长度之间的一个随机整数,因为是闭区间所以-1
        Y=random.randint(0,img2.shape[1]-1)
        if random.randint(0,1) ==0: #黑白色概率55开
            img2[X,Y] = (255,255,255) # 白色
        else:
            img2[X,Y] = (0, 0, 0)  # 黑色
    #  保存图片
    cv2.imwrite(img3_path, img2)

    imgs = cv2.imread(img3_path)
    # 2. xml 文件的修改
    tree = ET.parse(xml_path)
    root = tree.getroot()
    ss = []
    modifys = []
    width = 0
    for element in root.iter():
        # (1 按照[y_min, x_min, y_max, x_max]保存数据,用来后面处理数据
        # print(element.tag, element.text)
        if element.tag == "width":
            width = int(element.text)
            # print("width:", width)
        if element.tag == "name":
            name = element.text
            ss.append(name)
        if element.tag == "xmin":
            xmin = int(element.text)
            # print("xmin:", xmin)
            ss.append(xmin)
        if element.tag == "ymin":
            ymin = int(element.text)
            # print("ymin:", ymin)
            ss.append(ymin)
        if element.tag == "xmax":
            xmax = int(element.text)
            # print("xmax:", xmax)
            ss.append(xmax)
        if element.tag == "ymax":
            ymax = int(element.text)
            # print("ymax:", ymax)
            ss.append(ymax)
        if len(ss) == 5:
            # print(ss)
            x_min = ss[1]
            y_min = ss[2]
            x_max = ss[3]
            y_max = ss[4]

            modifys.append([ss[0], [y_min, x_min, y_max, x_max]])
            ss = []
    img_name = img3_path.split("\\")[-1].split(".")[0]

    # (2 将翻转后的图片和xml文件保存在特点路径
    img_xml(img_path, xml_saves, img_name, modifys)

    # 3. 画图
    for item in modifys:
        a = (item[1][1], item[1][0])
        b = (item[1][3], item[1][2])
        cv2.rectangle(imgs, a, b, (0, 255, 0), 2)

    cv2.imshow("s1", img)
    cv2.imshow("s2", imgs)
    cv2.waitKey(0)

    return img2, xmls

        这里只写了算法的函数,由于椒盐算法没有改变目标物在图片中的位置,所以不需要修改xml文件或者只需要修改新的图片路径即可。输出效果如下:

   

        前面的图片是原图,后面的图片是对原图做了椒盐变换,并将标注的坐标画成了框,显然是吻合的。


网站公告

今日签到

点亮在社区的每一天
去签到