图像语义分割标注工具labelme的使用及其转化为图像

以下操作在windows下进行,暂时还未在ubuntu下验证,因为之前ubuntu下labelme总是出问题,搞得人很崩溃

labelme的安装

主要参考的链接有
win10下labelme的安装教程

自己导出一下conda环境
conda导出来的结果

# This file may be used to create an environment using:
# $ conda create --name  --file 
# platform: win-64
bzip2=1.0.6=vc14_3
certifi=2016.2.28=py36_0
colorama=0.4.5=pypi_0
cycler=0.11.0=pypi_0
decorator=4.4.2=pypi_0
distro=1.8.0=pypi_0
freetype=2.5.5=vc14_2
icu=57.1=vc14_0
imageio=2.16.0=pypi_0
imgviz=1.6.2=pypi_0
jpeg=9b=vc14_0
kiwisolver=1.3.1=pypi_0
labelme=5.1.1=pypi_0
libpng=1.6.30=vc14_1
libtiff=4.0.6=vc14_3
matplotlib=3.3.4=pypi_0
natsort=8.2.0=pypi_0
networkx=2.5.1=pypi_0
numpy=1.19.5=pypi_0
olefile=0.44=py36_0
opencv-python=3.4.2.16=pypi_0
openssl=1.0.2l=vc14_0
packaging=21.3=pypi_0
pillow=8.4.0=pypi_0
pip=9.0.1=py36_1
pyparsing=3.0.7=pypi_0
pyqt=5.6.0=py36_2
python=3.6.2=0
python-dateutil=2.8.2=pypi_0
pywavelets=1.1.1=pypi_0
pyyaml=6.0=pypi_0
qt=5.6.2=vc14_6
qtpy=2.0.1=pypi_0
scikit-build=0.16.4=pypi_0
scikit-image=0.17.2=pypi_0
scipy=1.5.4=pypi_0
setuptools=59.6.0=pypi_0
sip=4.18=py36_0
six=1.16.0=pypi_0
termcolor=1.1.0=pypi_0
tifffile=2020.9.3=pypi_0
typing-extensions=4.1.1=pypi_0
vc=14=0
vs2015_runtime=14.0.25420=0
wheel=0.37.1=pypi_0
wincertstore=0.2=py36_0
zlib=1.2.11=vc14_0

pip导出来的结果

certifi==2016.2.28
colorama==0.4.5
cycler==0.11.0
decorator==4.4.2
distro==1.8.0
imageio==2.16.0
imgviz==1.6.2
kiwisolver==1.3.1
labelme==5.1.1
matplotlib==3.3.4
natsort==8.2.0
networkx==2.5.1
numpy==1.19.5
olefile==0.44
opencv-python==3.4.2.16
packaging==21.3
Pillow==8.4.0
pyparsing==3.0.7
python-dateutil==2.8.2
PyWavelets==1.1.1
PyYAML==6.0
QtPy==2.0.1
scikit-build==0.16.4
scikit-image==0.17.2
scipy==1.5.4
six==1.16.0
termcolor==1.1.0
tifffile==2020.9.3
typing-extensions==4.1.1
wincertstore==0.2

labelme在已经创建的多边形上中间添加点

点击那根线段即可在中间添加一个点

验证一下上面那个方法在ubuntu是否仍然适用

暂时未验证

labelme标注的结果转化为图片

注意python版本用3.7,不然protobuf装不上

absl-py==1.3.0
astunparse==1.6.3
cachetools==5.2.0
certifi @ file:///C:/b/abs_ac29jvt43w/croot/certifi_1665076682579/work/certifi
charset-normalizer==2.1.1
colorama==0.4.6
cycler==0.11.0
flatbuffers==22.12.6
fonttools==4.38.0
gast==0.4.0
google-auth==2.15.0
google-auth-oauthlib==0.4.6
google-pasta==0.2.0
grpcio==1.51.1
h5py==3.7.0
idna==3.4
imageio==2.23.0
importlib-metadata==5.2.0
keras==2.11.0
kiwisolver==1.4.4
libclang==14.0.6
Markdown==3.4.1
MarkupSafe==2.1.1
matplotlib==3.5.3
networkx==2.6.3
numpy==1.21.6
oauthlib==3.2.2
opencv-python==3.4.2.16
opt-einsum==3.3.0
packaging==22.0
Pillow==9.3.0
protobuf==3.19.6
pyasn1==0.4.8
pyasn1-modules==0.2.8
pyparsing==3.0.9
python-dateutil==2.8.2
PyWavelets==1.3.0
requests==2.28.1
requests-oauthlib==1.3.1
rsa==4.9
scikit-image==0.19.3
scipy==1.7.3
six==1.16.0
tensorboard==2.11.0
tensorboard-data-server==0.6.1
tensorboard-plugin-wit==1.8.1
tensorflow==2.11.0
tensorflow-estimator==2.11.0
tensorflow-intel==2.11.0
tensorflow-io-gcs-filesystem==0.29.0
termcolor==2.1.1
tifffile==2021.11.2
tqdm==4.64.1
typing_extensions==4.4.0
urllib3==1.26.13
Werkzeug==2.2.2
wincertstore==0.2
wrapt==1.14.1
zipp==3.11.0

相关代码

import shutil, base64, io, os, json, glob, math, warnings
import numpy as np
import PIL
import cv2
from PIL import (ExifTags, Image, ImageOps, ImageDraw, ImageDraw, ImageFont)
from skimage import img_as_ubyte
import tensorflow as tf
import os.path as osp
from tqdm import trange
import matplotlib.pyplot as plt

''' @定制化标签 '''


def shape_to_mask(img_shape, points, shape_type=None,
                  line_width=10, point_size=5):
    mask = np.zeros(img_shape[:2], dtype=np.uint8)
    mask = PIL.Image.fromarray(mask)
    draw = PIL.ImageDraw.Draw(mask)
    xy = [tuple(point) for point in points]
    if shape_type == 'circle':
        assert len(xy) == 2, 'Shape of shape_type=circle must have 2 points'
        (cx, cy), (px, py) = xy
        d = math.sqrt((cx - px) ** 2 + (cy - py) ** 2)
        draw.ellipse([cx - d, cy - d, cx + d, cy + d], outline=1, fill=1)
    elif shape_type == 'rectangle':
        assert len(xy) == 2, 'Shape of shape_type=rectangle must have 2 points'
        draw.rectangle(xy, outline=1, fill=1)
    elif shape_type == 'line':
        assert len(xy) == 2, 'Shape of shape_type=line must have 2 points'
        draw.line(xy=xy, fill=1, width=line_width)
    elif shape_type == 'linestrip':
        draw.line(xy=xy, fill=1, width=line_width)
    elif shape_type == 'point':
        assert len(xy) == 1, 'Shape of shape_type=point must have 1 points'
        cx, cy = xy[0]
        r = point_size
        draw.ellipse([cx - r, cy - r, cx + r, cy + r], outline=1, fill=1)
    else:
        assert len(xy) > 2, 'Polygon must have points more than 2'
        draw.polygon(xy=xy, outline=1, fill=1)  # xy 为[(x,y),(x.y),(...,...),...]
        pass
    '''
    ################### 定制化标签 ###################
    此处可根据需要打标签图draw进行自定义业务标签
    '''
    mask = np.array(mask, dtype=bool)
    return mask


def img_b64_to_arr(img_b64):
    f = io.BytesIO()
    f.write(base64.b64decode(img_b64))
    img_arr = np.array(PIL.Image.open(f))
    return img_arr


def img_arr_to_b64(img_arr):  # 没调用
    img_pil = PIL.Image.fromarray(img_arr)
    f = io.BytesIO()
    img_pil.save(f, format='PNG')
    img_bin = f.getvalue()
    if hasattr(base64, 'encodebytes'):
        img_b64 = base64.encodebytes(img_bin)
    else:
        img_b64 = base64.encodestring(img_bin)
    return img_b64


def img_data_to_png_data(img_data):  # 没调用
    with io.BytesIO() as f:
        f.write(img_data)
        img = PIL.Image.open(f)

        with io.BytesIO() as f:
            img.save(f, 'PNG')
            f.seek(0)
            return f.read()


def apply_exif_orientation(image):  # 没调用
    try:
        exif = image._getexif()
    except AttributeError:
        exif = None

    if exif is None:
        return image

    exif = {
        PIL.ExifTags.TAGS[k]: v
        for k, v in exif.items()
        if k in PIL.ExifTags.TAGS
    }

    orientation = exif.get('Orientation', None)

    if orientation == 1:
        # do nothing
        return image
    elif orientation == 2:
        # left-to-right mirror
        return PIL.ImageOps.mirror(image)
    elif orientation == 3:
        # rotate 180
        return image.transpose(PIL.Image.ROTATE_180)
    elif orientation == 4:
        # top-to-bottom mirror
        return PIL.ImageOps.flip(image)
    elif orientation == 5:
        # top-to-left mirror
        return PIL.ImageOps.mirror(image.transpose(PIL.Image.ROTATE_270))
    elif orientation == 6:
        # rotate 270
        return image.transpose(PIL.Image.ROTATE_270)
    elif orientation == 7:
        # top-to-right mirror
        return PIL.ImageOps.mirror(image.transpose(PIL.Image.ROTATE_90))
    elif orientation == 8:
        # rotate 90
        return image.transpose(PIL.Image.ROTATE_90)
    else:
        return image


def polygons_to_mask(img_shape, polygons, shape_type=None):
    warnings.warn(
        "The 'polygons_to_mask' function is deprecated, "
        "use 'shape_to_mask' instead."
    )
    return shape_to_mask(img_shape, points=polygons, shape_type=shape_type)


def shapes_to_label(img_shape, shapes, label_name_to_value, type='class'):
    assert type in ['class', 'instance']

    cls = np.zeros(img_shape[:2], dtype=np.int32)
    if type == 'instance':
        ins = np.zeros(img_shape[:2], dtype=np.int32)
        instance_names = ['_background_']
    for shape in shapes:
        points = shape['points']
        label = shape['label']
        shape_type = shape.get('shape_type', None)
        if type == 'class':
            cls_name = label
        elif type == 'instance':
            cls_name = label.split('-')[0]
            if label not in instance_names:
                instance_names.append(label)
            ins_id = instance_names.index(label)
        cls_id = label_name_to_value[cls_name]
        # mask = shape_to_mask(img_shape[:2], points, shape_type) # detail
        mask = shape_to_mask(img_shape[:2], points, shape_type, line_width=10, point_size=5)  # detail
        cls[mask] = cls_id  # 对每个label进行赋值类别
        if type == 'instance':
            ins[mask] = ins_id
            pass
        pass
    if type == 'instance':
        return cls, ins
    return cls


def labelme_shapes_to_label(img_shape, shapes):
    warnings.warn('labelme_shapes_to_label is deprecated, so please use '
                  'shapes_to_label.')

    label_name_to_value = {'_background_': 0}
    for shape in shapes:
        label_name = shape['label']
        if label_name in label_name_to_value:
            label_value = label_name_to_value[label_name]
        else:
            label_value = len(label_name_to_value)
            label_name_to_value[label_name] = label_value

    lbl = shapes_to_label(img_shape, shapes, label_name_to_value)
    return lbl, label_name_to_value


def masks_to_bboxes(masks):
    if masks.ndim != 3:
        raise ValueError(
            'masks.ndim must be 3, but it is {}'
                .format(masks.ndim)
        )
    if masks.dtype != bool:
        raise ValueError(
            'masks.dtype must be bool type, but it is {}'
                .format(masks.dtype)
        )
    bboxes = []
    for mask in masks:
        where = np.argwhere(mask)
        (y1, x1), (y2, x2) = where.min(0), where.max(0) + 1
        bboxes.append((y1, x1, y2, x2))
    bboxes = np.asarray(bboxes, dtype=np.float32)
    return bboxes


def label_colormap(N=256):
    def bitget(byteval, idx):
        return ((byteval & (1 << idx)) != 0)

    cmap = np.zeros((N, 3))
    for i in range(0, N):
        id = i
        r, g, b = 0, 0, 0
        for j in range(0, 8):
            r = np.bitwise_or(r, (bitget(id, 0) << 7 - j))
            g = np.bitwise_or(g, (bitget(id, 1) << 7 - j))
            b = np.bitwise_or(b, (bitget(id, 2) << 7 - j))
            id = (id >> 3)
        cmap[i, 0] = r
        cmap[i, 1] = g
        cmap[i, 2] = b
    cmap = cmap.astype(np.float32) / 255
    return cmap


def _validate_colormap(colormap, n_labels):
    if colormap is None:
        colormap = label_colormap(n_labels)
    else:
        assert colormap.shape == (colormap.shape[0], 3), \
            'colormap must be sequence of RGB values'
        assert 0 <= colormap.min() and colormap.max() <= 1, \
            'colormap must ranges 0 to 1'
    return colormap


# similar function as skimage.color.label2rgb
def label2rgb(
        lbl, img=None, n_labels=None, alpha=0.5, thresh_suppress=0, colormap=None,
):
    if n_labels is None:
        n_labels = len(np.unique(lbl))

    colormap = _validate_colormap(colormap, n_labels)
    colormap = (colormap * 255).astype(np.uint8)

    lbl_viz = colormap[lbl]
    lbl_viz[lbl == -1] = (0, 0, 0)  # unlabeled

    if img is not None:
        img_gray = PIL.Image.fromarray(img).convert('LA')
        img_gray = np.asarray(img_gray.convert('RGB'))
        # img_gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
        # img_gray = cv2.cvtColor(img_gray, cv2.COLOR_GRAY2RGB)
        lbl_viz = alpha * lbl_viz + (1 - alpha) * img_gray
        lbl_viz = lbl_viz.astype(np.uint8)
        pass
    return lbl_viz


def draw_label(label, img=None, label_names=None, colormap=None, **kwargs):
    """Draw pixel-wise label with colorization and label names.

    label: ndarray, (H, W)
        Pixel-wise labels to colorize.
    img: ndarray, (H, W, 3), optional
        Image on which the colorized label will be drawn.
    label_names: iterable
        List of label names.
    """
    backend_org = plt.rcParams['backend']
    plt.switch_backend('agg')
    plt.subplots_adjust(left=0, right=1, top=1, bottom=0,
                        wspace=0, hspace=0)
    plt.margins(0, 0)
    plt.gca().xaxis.set_major_locator(plt.NullLocator())
    plt.gca().yaxis.set_major_locator(plt.NullLocator())
    if label_names is None:
        label_names = [str(l) for l in range(label.max() + 1)]
    colormap = _validate_colormap(colormap, len(label_names))
    label_viz = label2rgb(
        label, img, n_labels=len(label_names), colormap=colormap, **kwargs
    )
    plt.imshow(label_viz)
    plt.axis('off')
    plt_handlers = []
    plt_titles = []
    for label_value, label_name in enumerate(label_names):
        if label_value not in label:
            continue
        fc = colormap[label_value]
        p = plt.Rectangle((0, 0), 1, 1, fc=fc)
        plt_handlers.append(p)
        plt_titles.append('{value}: {name}'
                          .format(value=label_value, name=label_name))
    plt.legend(plt_handlers, plt_titles, loc='lower right', framealpha=.5)
    f = io.BytesIO()
    plt.savefig(f, bbox_inches='tight', pad_inches=0)
    plt.cla()
    plt.close()
    plt.switch_backend(backend_org)
    out_size = (label_viz.shape[1], label_viz.shape[0])
    out = PIL.Image.open(f).resize(out_size, PIL.Image.BILINEAR).convert('RGB')
    out = np.asarray(out)
    return out


def draw_instances(
        image=None,
        bboxes=None,
        labels=None,
        masks=None,
        captions=None,
):
    # TODO(wkentaro)
    assert image is not None
    assert bboxes is not None
    assert labels is not None
    assert masks is None
    assert captions is not None

    viz = PIL.Image.fromarray(image)
    draw = PIL.ImageDraw.ImageDraw(viz)

    font_path = osp.join(
        osp.dirname(matplotlib.__file__),
        'mpl-data/fonts/ttf/DejaVuSans.ttf'
    )
    font = PIL.ImageFont.truetype(font_path)

    colormap = label_colormap(255)
    for bbox, label, caption in zip(bboxes, labels, captions):
        color = colormap[label]
        color = tuple((color * 255).astype(np.uint8).tolist())
        xmin, ymin, xmax, ymax = bbox
        draw.rectangle((xmin, ymin, xmax, ymax), outline=color)
        draw.text((xmin, ymin), caption, font=font)

    return np.asarray(viz)


def lblsave(filename, lbl):
    if os.path.splitext(filename)[1] != '.png':
        filename += '.png'
    # Assume label ranses [-1, 254] for int32,
    # and [0, 255] for uint8 as VOC.
    if lbl.min() >= -1 and lbl.max() < 255:
        lbl_pil = PIL.Image.fromarray(lbl.astype(np.uint8), mode='P')
        colormap = label_colormap(255)
        lbl_pil.putpalette((colormap * 255).astype(np.uint8).flatten())
        lbl_pil.save(filename)
    else:
        raise ValueError(
            '[%s] Cannot save the pixel-wise class label as PNG. '
            'Please consider using the .npy format.' % filename
        )
        pass
    pass


def json2label(json_root="dataset\\2021-08-30", img_root="dataset\\2021-08-30", save_root="dataset\\2021-08-30",
               label_name_to_value={'_background_': 0, "object": 1, "flaw": 2},
               use_label=True, use_color=True, use_jpeg=True, use_visual=True,
               is_show_time=1):
    ####### 默认输出路径 ########
    save_img = os.path.join(save_root, "JPEGImages")
    save_ano = os.path.join(save_root, "Annotations")
    save_label = os.path.join(save_root, "SegmentationClassRaw")
    save_color = os.path.join(save_root, "SegmentationClassPNG")
    save_visual = os.path.join(save_root, "SegmentationClassVisualization")

    if use_jpeg and not os.path.exists(save_img):
        os.makedirs(save_img)
    if use_label and not os.path.exists(save_ano):
        os.makedirs(save_ano)
    if use_color and not os.path.exists(save_color):
        os.makedirs(save_color)
    if use_label and not os.path.exists(save_label):
        os.makedirs(save_label)
    if use_visual and not os.path.exists(save_visual):
        os.makedirs(save_visual)
        pass

    # 检索对应的json和img
    print("img_root={}".format(img_root))
    img_dirs = glob.glob(os.path.join(img_root, "*[jpg,JPG,JPEG,bmp,BMP]"))  # list 的文件
    print("当前root下有图片个数: ", len(img_dirs))
    for j in trange(len(img_dirs)):
        img_dir = img_dirs[j]
        path, name = os.path.split(img_dir)
        first, second = os.path.splitext(name)
        json_dir = os.path.join(json_root, first + ".json")

        if not os.path.exists(img_dir) or not os.path.isfile(img_dir):
            print("no_exists: ", j, img_dir)
            continue
            pass
        if not os.path.exists(json_dir) or not os.path.isfile(json_dir):
            print("no_exists: ", j, json_dir)
            continue
            pass

        # 进行json解析和标签转换
        if os.path.isfile(json_dir):
            data = json.load(open(json_dir))
            pass
        if data['imageData']:
            imageData = data['imageData']
        else:
            with open(img_dir, 'rb') as f:
                imageData = f.read()
                imageData = base64.b64encode(imageData).decode('utf-8')
                pass
            pass
        ''' 解析图片: img : (array type)  '''
        img = img_b64_to_arr(imageData)

        # mask the label_name
        for shape in data['shapes']:
            label_name = shape['label']  # 获取label名
            if label_name in label_name_to_value:
                label_value = label_name_to_value[label_name]
            else:
                label_value = len(label_name_to_value)
                label_name_to_value[label_name] = label_value

        label_values, label_names = [], []
        for ln, lv in sorted(label_name_to_value.items(), key=lambda x: x[1]):
            label_values.append(lv)
            label_names.append(ln)
            pass

        if label_values != list(range(len(label_values))):
            print("assert label_values")

        ''' 解析标签: "seg_label"  lbl已经为0,1,2,3,的标签了 '''
        lbl = shapes_to_label(img.shape, data['shapes'], label_name_to_value)

        ''' 掩码可视化:"draw_mask"  lbl_viz '''
        captions = ['{}: {}'.format(lv, ln)
                    for ln, lv in label_name_to_value.items()]
        lbl_viz = draw_label(lbl, img, captions)

        if is_show_time > 0:
            is_show_time -= 1
            plt.figure(figsize=(20, 20))
            plt.subplot(121)
            plt.imshow(lbl)
            plt.subplot(122)
            plt.imshow(lbl_viz)
            plt.show()
            pass

        # 保存为统一图片
        if use_jpeg:
            PIL.Image.fromarray(img).save(os.path.join(save_img, first + ".jpeg"))

        # 保存为数字标签图
        if use_label:
            with tf.io.gfile.GFile(os.path.join(save_label, first + ".png"), mode='w') as f:
                Image.fromarray(lbl.astype(dtype=np.uint8)).save(f, 'PNG')

        # 保存为彩色标签图
        if use_color:
            lblsave(os.path.join(save_color, first + ".png"), lbl)

            # 保存为掩码可视化
        if use_visual:
            PIL.Image.fromarray(lbl_viz).save(os.path.join(save_visual, first + ".png"))

        # 移动一份数据-到aon和jpg文件夹,统一输出格式  这里是移动,也可以复制吧
        if use_label:
            # shutil.move(img_dir, os.path.join(save_ano, name)) if os.path.exists(img_dir) else None
            # shutil.move(json_dir, os.path.join(save_ano, first + ".json")) if os.path.exists(json_dir) else None
            shutil.copy(img_dir, os.path.join(save_ano, name)) if os.path.exists(img_dir) else None
            shutil.copy(json_dir, os.path.join(save_ano, first + ".json")) if os.path.exists(json_dir) else None
            pass
        pass
    pass


if __name__ == "__main__":
    '''
    说明:
    函数 shapes_to_label  可以修改线宽,支持 矩形,点
    函数 shape_to_mask    标签扩充放大, 使用膨胀'''
    #jsonroot和img_root可不可以是同一个文件夹,可以,他会自动判断类别
    json2label(json_root="tower",
               img_root="tower",
               save_root="output",
               label_name_to_value={'_background_': 0, "tower1": 1, "tower2": 2},
               use_label=True,  #
               use_color=True,  #
               use_jpeg=True,  #
               use_visual=True,  #
               is_show_time=1)

图像语义分割标注工具labelme的使用及其转化为图像_第1张图片
输入文件夹,其中.json为标注后生成的
输出文件夹,使用genlabel.py后生成

使用方法,修改最下面的json_root和img_root,这两个可以是一个文件夹,因为他最终会判断文件的后缀名
参考链接
labelme转图片

json数据转yolov5训练数据

使用的代码为,注意修改最上面的文件的路径

import json
import os

#输入路径  路径到包含.json文件为止,可以包含图片文件
file_path = ['towerSegoutput/Annotations']
outputRoot = 'txts'
#标签对应情况
name = ['background','tower','cate2']  # class names
a = os.listdir(file_path[0])    #.

#如果输出路径不存在,自动创建
if not os.path.exists(outputRoot):
    os.mkdir(outputRoot)

for j in range(len(file_path)):
    #对于每个.json文件
    for file_name in os.listdir(file_path[j]):
        if ".json" not in file_name:
            print("不是json文件")
            continue
        print("是json文件")
        print('\n文件名', file_name)
        path = os.path.join(file_path[j],file_name)
        print("path={}".format(path))
        with open(path,'r') as load_f:
            dict = json.load(load_f)
            # print('解析后得json文件',dict)
            #这个可能要修改,图片长度和宽度
            # h, w = dict['imgHeight'], dict['imgWidth']
            h, w = dict['imageHeight'], dict['imageWidth']

        print("具有的物体的数量={}".format(len(dict['shapes'])))
        first,last = os.path.splitext(file_name)
        txt_name = first+'.txt'
        path_txt = os.path.join(outputRoot,txt_name)
        print("path_txt={}".format(path_txt))
        f = open(path_txt, 'w')

        #一个json文件里可能有多个物体
        for i in range(len(dict['shapes'])):
            if dict['shapes'][i]['label'] in name:
                obj_id = name.index(dict['shapes'][i]['label'])
                label_pts = [obj_id]
                #这里的poings之前时polygons(类似)
                points = dict['shapes'][i]['points']
                print("多边形的点的数量为={}".format(len(points)))
                for t in points:
                    x = t[0]/w
                    x = round(x,5)
                    y = t[1]/h
                    y = round(y,5)
                    label_pts.append(x)
                    label_pts.append(y)

                for t in label_pts:
                     f.write(str(t))
                     f.write(' ')
                f.write('\n')

                print('物体的类别数量为{}'.format(name[obj_id]))

        f.close()    #把文件关闭

你可能感兴趣的:(python,图像处理)