labelme:json文件转成图片形式

该文章介绍了如何利用Python的json、numpy和opencv库将Labelme生成的json标注文件转换为图片形式。主要步骤包括读取json文件中的标注信息,使用这些信息在原始图片上创建掩模,并将掩模保存为新的图片文件。

labelme:json文件转成图片形式


Tips

1、要把json文件名命名成顺序数字的形式,例如:1.jpg、2.jpg、3.jpg .......

2、json文件要和原始图片放在同一个文件夹中

例如:


代码

import json
import numpy as np
import cv2

# range里面的数字根据自己数据量命名,例如我的图片有310张,就要range(1,311)
# 要把json文件名命名成顺序数字的形式
for i in range(1,311):

    with open('xxx/%s.json'%(i), "r") as f: # 填上你自己的json文件的地址
        data = f.read()

    data = json.loads(data)

    points = data["shapes"][0]["points"]
    points = np.array(points, dtype=np.int32)

    image = cv2.imread('xxx/%s.jpg' %(i) ) # 标记前的图片

    mask = np.zeros_like(image, dtype=np.uint8)

    cv2.fillPoly(mask, [points], (255, 255, 255))

    cv2.imwrite('xxx/%s.png' %(i), mask) # 标记后的存储地址

<think>我们有一个labelmeJSON文件,它包含图像标注信息,通常包括多个形状(多边形、矩形等)以及每个形状对应的标签。我们的目标是将这些标注转换为单通道的PNG掩码图像。 在单通道PNG掩码中,每个像素的值表示该像素所属的类别(通常用整数表示,例如0表示背景,1表示类别1,2表示类别2,等等)。 步骤: 1. 读取JSON文件,解析出图像的高度和宽度。 2. 创建一个与图像大小相同的全零数组(表示背景)。 3. 遍历JSON中的每一个形状(shape),每个形状都有一个标签(label)和一组点(points)。 4. 根据标签,我们确定该形状对应的类别索引(需要一个从标签名到类别索引的映射,例如通过一个类别列表)。 5. 使用这些点绘制多边形(或其他形状)填充,将数组中对应的位置设置为类别索引。 6. 将数组保存为单通道的PNG图像。 注意:labelmeJSON文件可能包含多个不同类别的形状,甚至可能有重叠。我们需要确保在绘制时,后绘制的形状可能会覆盖先绘制的(取决于需求,但通常掩码要求每个像素只属于一个类别,所以需要处理覆盖顺序,或者使用其他规则,如按标注顺序,后面的覆盖前面的)。 但是,在语义分割中,我们通常希望每个像素只属于一个类别。如果标注有重叠,我们可以按照标注的顺序,后面的覆盖前面的,或者我们可以使用一个规则(例如,只保留第一个出现的类别)。这里我们按照常见的做法:按顺序绘制,后面的形状会覆盖前面的。 另外,labelme支持多种形状(多边形、矩形、圆形、线段等),但通常用于语义分割的是多边形和矩形。这里我们主要处理多边形(polygon)和矩形(rectangle)。矩形可以看作是多边形的一种特殊形式(四个点)。 实现: 我们可以使用PIL(Python Imaging Library)或者OpenCV来绘制多边形并填充。这里我们使用PIL的ImageDraw。 首先,需要安装必要的库(如果未安装): pip install labelme numpy Pillow 注意:labelme库并不是必须的,因为我们可以直接解析JSON,但这里我们不需要labelme库,直接使用json库读取即可。 步骤详细说明: 1. 读取JSON文件,使用json.load。 2. 从JSON中获取图像高度和宽度(imageHeight和imageWidth)。 3. 创建一个新的PIL图像,模式为'I'(32位有符号整数)或者'L'(8位无符号整数),具体取决于类别数量。如果类别数量小于256,我们可以使用8位,否则需要更大的位数。这里假设类别数量较少,使用8位(0-255)。 4. 使用ImageDraw来绘制多边形。 但是,注意:单通道图像中,0通常表示背景,而类别索引从1开始。所以我们需要一个从标签名到索引的映射(例如,{'class1':1, 'class2':2, ...})。这个映射可以由用户提供,或者从JSON文件中提取所有标签并分配索引(注意顺序,我们可以按照标签在JSON中出现的顺序,但要注意重复标签)。 然而,为了避免混淆,最好由用户提供一个类别列表,这样索引顺序是固定的。如果没有提供,我们可以从JSON中提取所有不重复的标签(按出现顺序),然后背景为0,第一个标签为1,以此类推。 但是,在转换过程中,我们通常需要预先定义好的类别映射。因此,我们可以要求用户传入一个类别名称到索引的字典,或者我们自动生成(但自动生成可能因顺序问题导致不一致)。 这里,我们假设用户会提供一个标签名称到索引的字典(label_to_index),其中不包括背景(背景默认为0)。如果某个标签不在字典中,我们可以选择忽略或报错。 具体代码步骤: 1. 读取JSON文件。 2. 创建空白画布(全0,大小与原图相同)。 3. 遍历每个形状(shape): 获取形状的标签(label) 获取点(points)列表 将点转换为元组列表(因为PIL的多边形绘制需要) 在画布上绘制多边形,用对应的类别索引填充。 4. 将数组保存为PNG。 注意:绘制多边形时,我们使用numpy数组操作,然后转换为PIL图像,或者直接在PIL图像上绘制。这里我们使用PIL的ImageDraw。 但是,PIL的ImageDraw在绘制多边形时,要求多边形的点是一个元组序列,例如[(x0,y0), (x1,y1), ...]。 另外,labelmeJSON文件中,点的坐标是浮点数,我们需要转换为整数(四舍五入)。 处理矩形:labelme的矩形在JSON中是以两个点的形式(左上角和右下角)存储的。我们可以将矩形转换为四个点的多边形。 但是,在labelmeJSON中,形状的"shape_type"字段可以是"polygon"、"rectangle"等。对于矩形,我们可以这样转换: points = [[x1, y1], [x2, y2]] 然后转换为多边形:[[x1, y1], [x2, y1], [x2, y2], [x1, y2]] 然而,实际上labelme在导出矩形时,points就是两个点,我们需要自己扩展成四个点。 因此,我们需要根据shape_type来处理: 如果是"rectangle",则扩展成四个点; 如果是"polygon",则直接使用点列表。 另外,还有圆形(circle)?labelme支持圆形,但通常语义分割中较少使用。如果遇到,我们可以用多边形近似(例如用正多边形),但这里我们暂时不支持圆形,或者忽略。 考虑到通用性,我们只处理多边形和矩形。 代码示例: 假设我们有一个JSON文件路径和一个输出PNG路径,以及一个标签到索引的字典(label_map)。 如果没有提供label_map,我们可以自动生成(但这样可能每次顺序不同,所以不推荐)。因此,我们要求必须提供label_map。 步骤: 1. 读取JSON文件。 2. 获取图像大小(注意:有些JSON文件可能没有imageHeight和imageWidth,而是通过imagePath指向的图片获取大小。但labelmeJSON通常包含这两个字段)。 3. 创建一个全零的数组(numpy数组),大小为(height, width),数据类型为uint8。 4. 遍历每个形状: label = shape['label'] shape_type = shape.get('shape_type', 'polygon') # 默认为多边形 points = shape['points'] if label not in label_map: # 可以选择跳过或报错,这里我们跳过 continue index = label_map[label] # 处理矩形 if shape_type == 'rectangle' and len(points) == 2: # 将矩形转换为四个点 x1, y1 = points[0] x2, y2 = points[1] points = [[x1, y1], [x2, y1], [x2, y2], [x1, y2]] shape_type = 'polygon' # 现在当做多边形处理 if shape_type == 'polygon': # 将点转换为整数(四舍五入) int_points = [(int(round(x)), int(round(y))) for x, y in points] # 创建一个临时图像,用于绘制这个多边形 # 注意:我们不能直接在原数组上绘制,因为PIL的ImageDraw操作的是图像,而不是数组。 # 因此,我们可以创建一个和原图一样大的空白图像,绘制多边形,然后将绘制的结果(非零位置)赋值到原数组。 # 这样,每个形状我们单独绘制,然后合并到主掩码上。 # 但是这样效率较低,因为每个形状都要创建一个新图像。我们可以考虑使用另一个方法:在同一个ImageDraw上绘制,但是这样会覆盖之前的形状(因为我们是按顺序绘制,后面的覆盖前面,符合要求)。 # 因此,我们可以创建一个PIL图像(与掩码同样大小),然后绘制,最后将绘制的结果(像素值等于当前索引)更新到主掩码数组。 # 但是,我们也可以在主掩码图像上直接绘制。因为我们已经在使用PIL,我们可以: # 1. 将当前的掩码数组转换为PIL图像(模式为'L') # 2. 创建ImageDraw对象 # 3. 绘制多边形(用当前索引值填充) # 4. 将PIL图像转回数组 # 但是这样每次都要转换,效率不高。 # 另一种方法:一开始就创建PIL图像,然后遍历每个形状,在PIL图像上绘制。这样我们只需要绘制一次。 # 所以,我们改变策略: # 初始化一个PIL图像(全0),模式为'L' # 遍历每个形状,用ImageDraw绘制多边形(填充值为类别索引) # 最后保存这个图像。 # 但是,这样我们就需要一开始就创建PIL图像,然后绘制。这样更高效。 因此,我们修改步骤: a. 创建PIL图像:img = Image.new('L', (width, height), 0) # 0是背景 b. 创建draw对象:draw = ImageDraw.Draw(img) c. 遍历每个形状: 如果是多边形,则绘制多边形(用当前类别索引填充) 如果是矩形,则绘制矩形(但ImageDraw有绘制矩形的方法,但这里我们已经将矩形转换为多边形,所以统一用多边形绘制) d. 保存img为PNG。 但是,这样绘制的话,后面的形状会覆盖前面的形状,因为每次绘制都会覆盖之前的像素。这正是我们想要的(按标注顺序,后面的覆盖前面的)。 因此,我们不需要使用数组,直接使用PIL绘制即可。 但是,注意:绘制多边形时,如果多边形有重叠,那么重叠部分会被后面的覆盖。这符合我们的要求。 代码实现: 注意:PIL的ImageDraw.polygon方法要求点的顺序是[(x0,y0), (x1,y1), ...],并且是整数。 另外,矩形转换后也是多边形,所以统一用多边形绘制。 对于其他形状(如圆形、线段),我们暂时不支持,可以跳过并给出警告。 具体代码: 假设输入:json_file(JSON文件路径),output_png(输出PNG路径),label_map(标签到索引的字典) 步骤: 1. 读取JSON文件。 2. 获取图像宽度和高度(如果JSON中没有,则可能需要从图像文件读取,但labelmeJSON一般有,所以直接读取)。 3. 创建PIL图像(模式'L',背景0)。 4. 创建ImageDraw对象。 5. 遍历每个形状(shapes): label = shape['label'] shape_type = shape['shape_type'] points = shape['points'] if label not in label_map: print(f"Warning: label '{label}' not found in label_map, skipped.") continue idx = label_map[label] if shape_type == 'rectangle' and len(points) == 2: # 转换为四个点 x1, y1 = points[0] x2, y2 = points[1] points = [[x1, y1], [x2, y1], [x2, y2], [x1, y2]] # 然后当做多边形处理 shape_type = 'polygon' if shape_type == 'polygon': # 转换为整数点 int_points = [(int(round(x)), int(round(y))) for x, y in points] # 绘制多边形 draw.polygon(int_points, fill=idx) else: print(f"Warning: shape_type '{shape_type}' is not supported, skipped.") 6. 保存图像:img.save(output_png) 注意:PIL的坐标是(x,y),而numpy数组是(height, width),但这里我们创建图像时使用了(width, height),所以坐标点需要是(x,y)。我们使用JSON中的点,注意JSON中的点坐标是浮点数,四舍五入取整。 但是,这样绘制会覆盖重叠区域,符合我们的要求。 但是,有一个问题:如果同一个形状内部有空洞(即多个多边形组成一个对象),labelme通常用一个包含多个多边形的形状来表示(但labelme的一个形状其实是一个连续的多边形,如果有空洞,实际上是一个多边形包含内环)。但labelmeJSON格式中,每个形状只有一个多边形(即一个点列表),所以如果有空洞,需要多个形状?或者在一个形状中用多个点列表?实际上,labelmeJSON格式中,每个形状的"points"是一个点列表,表示一个多边形。如果有空洞,通常用多个形状(多个shape)来表示,每个形状都是独立的多边形,然后通过不同的标签?或者相同的标签?所以,我们按照标签来绘制,相同标签的多个多边形会分别绘制,但绘制顺序可能会影响(因为后面的会覆盖前面的,但相同标签覆盖也没关系,因为填充值相同)。但是,如果有空洞,通常需要将空洞区域标记为背景,那么我们需要在绘制时考虑。然而,在语义分割中,我们通常不区分子区域,而是将整个区域标记为同一类别。所以,如果出现空洞,那么空洞区域会被后面绘制的其他形状覆盖,或者如果空洞区域没有被其他形状覆盖,那么它仍然是背景(0),这符合要求。 因此,我们不需要特别处理空洞。 但是,实例分割和语义分割不同。我们这里做的是语义分割(单通道,每个像素一个类别),所以不考虑实例。 所以,代码可以这样写。 但是,如果用户没有提供label_map,我们可以自动生成吗?我们可以遍历所有形状,收集所有标签,然后分配索引(从1开始),背景为0。但是这样可能会因为每次运行顺序不同而导致索引变化。所以,我们最好要求用户提供。 我们可以提供一个可选参数,如果用户不提供,则自动生成,但需要按标签名字母排序,以保证顺序一致?或者按出现顺序?按出现顺序可能会不一致。所以,我们要求用户提供label_map。 下面是一个完整的函数示例: 注意:我们假设JSON文件中有imageHeight和imageWidth。如果没有,则需要从图像文件读取,但这里我们不做处理,直接报错。 代码:</think>### 将LabelMeJSON文件转换为单通道PNG掩码 以下是使用Python实现的完整代码,通过解析LabelMeJSON标注文件生成单通道PNG掩码图像。核心步骤包括解析多边形坐标、创建空白掩码画布、填充多边形区域,并处理重叠标注。 ```python import json import numpy as np from PIL import Image, ImageDraw def labelme_json_to_mask(json_path, output_path): """ 将LabelMeJSON标注文件转换为单通道PNG掩码 :param json_path: LabelMe JSON文件路径 :param output_path: 输出PNG掩码路径 """ # 加载JSON数据 with open(json_path, 'r') as f: data = json.load(f) # 获取图像尺寸 width = data['imageWidth'] height = data['imageHeight'] # 创建空白掩码(单通道,初始为0=背景) mask = np.zeros((height, width), dtype=np.uint8) pil_mask = Image.fromarray(mask) draw = ImageDraw.Draw(pil_mask) # 遍历所有标注形状 for shape in data['shapes']: label = shape['label'] points = shape['points'] # 将多边形坐标转换为整数元组 polygon = [(int(x), int(y)) for point in points for x, y in [point]] # 根据标签分配像素值(示例:'cat'->1, 'dog'->2) if label == 'cat': fill_value = 1 elif label == 'dog': fill_value = 2 else: # 其他类别 fill_value = 3 # 绘制填充多边形 draw.polygon(polygon, fill=fill_value) # 保存单通道PNG掩码 pil_mask.save(output_path) print(f"掩码已保存至: {output_path}") # 使用示例 labelme_json_to_mask('annotation.json', 'mask.png') ``` ### 关键说明: 1. **标签值映射** - 背景像素值固定为 `0` - 示例中:`'cat'→1`, `'dog'→2`,其他类别→`3` - 需根据实际类别修改映射关系(如构建字典 `label_mapping = {'cat':1, 'dog':2}`) 2. **重叠处理** - 后绘制的形状会覆盖先前区域(按JSON中顺序) - 确保关键类别在JSON中靠后声明 3. **支持类型** - 原生支持多边形(`polygon`) - 矩形自动转换为多边形(LabelMe矩形即两点定义的多边形) 4. **注意事项** - 要求JSON包含 `imageWidth`/`imageHeight` 字段 - 输出为8位单通道PNG(0-255类别值)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值