2021广东工业智造创新大赛—智能算法赛总结(一)

举报
AI浩 发表于 2022/02/10 09:07:50 2022/02/10
【摘要】 ​ 目录赛题一、赛题背景二、赛题数据说明初赛数据示例初赛数据提供训练数据文件结构标注说明类别说明三、提交说明评测结果提交python示例代码四、评估指标初赛数据分析查看数据处理数据 赛题一、赛题背景佛山作为国内最大的瓷砖生产制造基地之一,拥有众多瓷砖厂家和品牌。经前期调研,瓷砖生产环节一般(不同类型砖工艺不一样,这里以抛釉砖为例)经过原材料混合研磨、脱水、压胚、喷墨印花、淋釉、烧制、抛光,最...

 目录

赛题

一、赛题背景

二、赛题数据说明

初赛数据示例

初赛数据提供


训练数据文件结构

标注说明

类别说明

三、提交说明

评测结果提交

python示例代码

四、评估指标

初赛


数据分析

查看数据

处理数据 


赛题

一、赛题背景

佛山作为国内最大的瓷砖生产制造基地之一,拥有众多瓷砖厂家和品牌。经前期调研,瓷砖生产环节一般(不同类型砖工艺不一样,这里以抛釉砖为例)经过原材料混合研磨、脱水、压胚、喷墨印花、淋釉、烧制、抛光,最后进行质量检测和包装。得益于产业自动化的发展,目前生产环节已基本实现无人化。而质量检测环节仍大量依赖人工完成。一般来说,一条产线需要配2~6名质检工,长时间在高光下观察瓷砖表面寻找瑕疵。这样导致质检效率低下、质检质量层次不齐且成本居高不下。瓷砖表检是瓷砖行业生产和质量管理的重要环节,也是困扰行业多年的技术瓶颈。

本赛场聚焦瓷砖表面瑕疵智能检测,要求选手开发出高效可靠的计算机视觉算法,提升瓷砖表面瑕疵质检的效果和效率,降低对大量人工的依赖。要求算法尽可能快与准确的给出瓷砖疵点具体的位置和类别,主要考察疵点的定位和分类能力。

二、赛题数据说明

大赛深入到佛山瓷砖知名企业,在产线上架设专业拍摄设备,实地采集生产过程真实数据,解决企业真实的痛点需求。大赛数据覆盖到了瓷砖产线所有常见瑕疵,包括粉团、角裂、滴釉、断墨、滴墨、B孔、落脏、边裂、缺角、砖渣、白边等。实拍图示例如下:


针对某些缺陷在特定视角下的才能拍摄到,每块砖拍摄了三张图,包括低角度光照黑白图、高角度光照黑白图、彩色图,示例如下:


数据主要分为两种:

  1. 白板瓷砖。花色简单,数量总共约12000张,包含训练集和测试集,用于初赛。
  2. 复杂瓷砖。花色相对复杂,并提供相应的模板图片(同花色且无瑕疵图片),数量总共约12000张,包含训练集和测试集,用于复赛。

初赛数据示例

白板数据包含有瑕疵图片、无瑕疵图片和标注数据。标注数据标注瑕疵位置和类别信息。图片示例如下:

初赛数据提供

  1. 初赛训练集于1月4日提供。
  2. 初赛一阶段测试集于1月4日提供下载,初赛二阶段测试集1月28号提供下载。


训练数据文件结构

└── dataset
    ├── Readme.md
    ├── train_annos.json
    ├── train_imgs

train_imgs:训练图片数据,jpg格式
train_annos.json:训练标注数据,json格式
Readme.md:说明文件

标注说明

训练标注是train_annos.json,内容如下:

[
    {
        "name": "226_46_t20201125133518273_CAM1.jpg",
        "image_height": 6000,
        "image_width": 8192,
        "category": 4,
        "bbox": [
            1587,
            4900,
            1594,
            4909
        ]
    },
    '''
    '''
]

具体说明如下:
name是图片名,"226_46"代表砖的唯一id,"CAM1"代表相机1拍照所得,一般来说每块砖会有三张样本,分别是CAM1,CAM2,CAM3。image_heightimage_width是图片高宽,category"是类别id,bbox是目标框信息xyrb格式,分别指[左上角x坐标,左上角y坐标,右下角x坐标,右下角y坐标]

类别说明

id和瑕疵名的对应关系如下:

{ "0": "背景", "1": "边异常", "2": "角异常", "3": "白色点瑕疵", "4": "浅色块瑕疵", "5": "深色点块瑕疵", "6": "光圈瑕疵", "7": "记号笔", "8": "划伤" }

其中,记号笔划伤是复赛新增。

三、提交说明

评测结果提交

参赛者需要提供一份json文件包含所有预测结果,文件内容如下:

[
    {
        "name": "226_46_t20201125133518273_CAM1.jpg",
        "category": 4,
        "bbox": [
            5662,
            2489,
            5671,
            2497
        ],
        "score": 0.130577
    },
    {
        "name": "226_46_t20201125133518273_CAM1.jpg",
        "category": 2,
        "bbox": [
            6643,
            5416,
            6713,
            5444
        ],
        "score": 0.120612
    },
    ...
    ...
    {
        "name": "230_118_t20201126144204721_CAM2.jpg",
        "category": 5,
        "bbox": [
            3543,
            3875,
            3554,
            3889
        ],
        "score": 0.160216
    }
]

具体说明如下:
1.提交的
json文件中包含多个疵点样本,每个疵点样本都包含namecategorybboxscore四个字段。
2.
name字段为图片名称;category字段为类别标签;bboxxyrb格式坐标框,分别指[左上角x坐标,左上角y坐标,右下角x坐标,右下角y坐标],小数点后保留2位;score为置信度概率,范围0-1;
3.对于单张图片存在多个疵点样本时,依次列出即可;对于不存在疵点的无疵点图片,不能出现在
json列表中。
4.
name字段不允许出现非测试集中的图片名。

python示例代码

result=[]
result.append({'name': image_name(str),'category': defect_label(int),'bbox':bbox(xyxy,float),'score': score(float)})
import json
with open('result.json', 'w') as fp:
     json.dump(result, fp, indent=4, ensure_ascii=False)

四、评估指标

初赛

赛题分数计算方式: 0.2ACC+0.8mAP

ACC:是有瑕疵或无瑕疵的分类指标,考察瑕疵检出能力。
其中提交结果
name字段中出现过的测试图片均认为有瑕疵,未出现的测试图片认为是无瑕疵。

mAP:参照PASCALVOC的评估标准计算瑕疵的mAP值。
参考链接:GitHub - rafaelpadilla/Object-Detection-Metrics: Most popular metrics used to evaluate object detection algorithms. 具体逻辑见evaluator文件。


需要指出,本次大赛评分计算过程中,分别在检测框和真实框的交并比(IoU)在阈值0.10.30.5下计算mAP,最终mAP取三个值的平均值。


数据分析

查看数据

首先查看下载来的数据,随意打开一张图片,查看图片的属性。


从上图可以看出图像是8192×6000大小的彩色图片,图片尺寸和COCO数据的图片相比大了很多。然后在数据集转为Labelme的标注数据。转化代码如下:

import sys
import os.path as osp
import io
from labelme.logger import logger
from labelme import PY2
from labelme import QT4
import PIL.Image
import base64
from labelme import utils
import os
import cv2
import xml.etree.ElementTree as ET
from PIL import ImageFile
ImageFile.LOAD_TRUNCATED_IMAGES = True
module_path = os.path.abspath(os.path.join('..'))
if module_path not in sys.path:
    sys.path.append(module_path)
import json
from PIL import Image

Image.MAX_IMAGE_PIXELS = None

class_name_dic = {
    "0": "背景",
    "1": "边异常",
    "2": "角异常",
    "3": "白色点瑕疵",
    "4": "浅色块瑕疵",
    "5": "深色点块瑕疵",
    "6": "光圈瑕疵"
}
rawImgDir = '../tile_round1_train_20201231/train_imgs/'
rawLabelDir = '../tile_round1_train_20201231/train_annos.json'
with open(rawLabelDir) as f:
    annos = json.load(f)

#
image_ann = {}
for i in range(len(annos)):
    anno = annos[i]
    name = anno['name']
    if name not in image_ann:
        image_ann[name] = []
    image_ann[name].append(i)


def load_image_file(filename):
    try:
        image_pil = PIL.Image.open(filename)
    except IOError:
        logger.error('Failed opening image file: {}'.format(filename))
        return

    # apply orientation to image according to exif
    image_pil = utils.apply_exif_orientation(image_pil)

    with io.BytesIO() as f:
        ext = osp.splitext(filename)[1].lower()
        if PY2 and QT4:
            format = 'PNG'
        elif ext in ['.jpg', '.jpeg']:
            format = 'JPEG'
        else:
            format = 'PNG'
        image_pil.save(f, format=format)
        f.seek(0)
        return f.read()


def dict_json(flags, imageData, shapes, imagePath, fillColor=None, lineColor=None, imageHeight=100, imageWidth=100):
    '''
    :param imageData: str
    :param shapes: list
    :param imagePath: str
    :param fillColor: list
    :param lineColor: list
    :return: dict
    '''
    return {"version": "3.16.4", "flags": flags, "shapes": shapes, 'lineColor': lineColor, "fillColor": fillColor,
            'imagePath': imagePath.split('/')[-1], "imageData": imageData, 'imageHeight': imageHeight,
            'imageWidth': imageWidth}


data = json.load(open('1.json'))
for name in image_ann.keys():
    indexs = image_ann[name]
    height, width = annos[indexs[0]]["image_height"], annos[indexs[0]]["image_width"]
    imagePath=rawImgDir+name
    print(imagePath)
    if not os.path.exists(imagePath): # True/False
        continue
    image = cv2.imread(imagePath)
    shapes = data["shapes"]
    version = data["version"]
    flags = data["flags"]
    lineColor = data["lineColor"]
    fillColor = data['fillColor']
    newshapes = []
    for inx in indexs:
        obj = annos[inx]
        assert name == obj['name']
        bbox = obj['bbox']
        category = obj['category']
        xmin, ymin, xmax, ymax = bbox
        class_name = class_name_dic[str(category)]
        newPoints = [[float(xmin), float(ymin)], [float(xmax), float(ymax)]]
        line_color = None
        fill_color = None
        shape_type = 'rectangle'
        flags = flags
        newshapes.append(
            {"label": class_name, "line_color": line_color, "fill_color": fill_color, "points": newPoints,
             "shape_type": shape_type, "flags": flags})
    imageData_90 = load_image_file(imagePath)
    imageData_90 = base64.b64encode(imageData_90).decode('utf-8')
    imageHeight = image.shape[0]
    imageWidth = image.shape[1]
    data_90 = dict_json(flags, imageData_90, newshapes, imagePath, fillColor, lineColor, imageHeight, imageWidth)
    json_file = imagePath[:-4] + '.json'
    print(json_file)
    json.dump(data_90, open(json_file, 'w'))

运行结果: 

运行完成后,用Labelme查看检测的目标。

处理数据 

待检测目标和图片大小相比小了很多,所以不能采用直接将图片Resize大小放到模型。我在本次比赛采用的策略是:小图训练,大图测试。

1、把训练集的大图切分成固定大小的图片,然后放入模型训练。

2、对测试集采用大图测试。

制作训练集

自作训练集,我在这里分了两个步骤,首先将Labelme数据转为txt数据集。再将图片和txt数据集按照滑窗的方式切成800×800的图片。然后再转为Labelme数据(其实不用这么麻烦,主要是不想再写代码了,直接复用以前写的)

将Labelme数据转为txt数据集代码:


import json
import os
from glob import glob
import shutil

# convert labelme json to DOTA txt format

def custombasename(fullname):
    return os.path.basename(os.path.splitext(fullname)[0])
IN_PATH = '../tile_round1_train_20201231/train_imgs/'
OUT_PATH = '../labeltxt'
if not os.path.exists(OUT_PATH):
    os.makedirs(OUT_PATH)
file_list = glob(IN_PATH + '/*.json')
for i in range(len(file_list)):
    with open(file_list[i]) as f:
        label_str = f.read()
        label_dict = json.loads(label_str)  # json文件读入dict
        imgepath=file_list[i][:-5]+'.jpg'
        print(imgepath)
        # 输出 txt 文件的路径
        out_file = OUT_PATH + '/' + custombasename(file_list[i]) + '.txt'
        print(out_file)
        shutil.copy(imgepath, OUT_PATH)
        # 写入 poly 四点坐标 和 label
        fout = open(out_file, 'w')
        out_str = ''
        for shape_dict in label_dict['shapes']:
            out_str += shape_dict['label'] + ' '
            points = shape_dict['points']
            for p in points:
                out_str += (str(p[0]) + ' ' + str(p[1]) + ' ')
            out_str +='\n'
        fout.write(out_str)
        fout.close()
    print('%d/%d' % (i + 1, len(file_list)))

 将大图切成小图

import os
import math
from glob import glob
from PIL import ImageFile
ImageFile.LOAD_TRUNCATED_IMAGES = True
size = 800
step = 700
labelme_path = '../labeltxt/'
if not os.path.exists('../CutResult'):
    os.makedirs('../CutResult')
from PIL import Image

Image.MAX_IMAGE_PIXELS = None

def custombasename(fullname):
    return os.path.basename(os.path.splitext(fullname)[0])


image_files = glob(labelme_path + "*.jpg")
for fileImag in image_files:
    img = Image.open(fileImag)
    objectList = []
    with open(fileImag[:-4] + ".txt") as f:
        for line in f.readlines():
            for aa in line.split(' '):
                if aa != '\n':
                    objectList.append(aa)
    name = custombasename(fileImag)
    print(name)
    print(objectList)
    width, height = img.size
    for i in range(math.ceil(width / step)):
        for j in range(math.ceil(height / step)):
            x1 = i * step
            y1 = j * step
            x2 = size + i * step
            y2 = size + j * step
            if x2 > width:
                x2 = width
                x1 = width - size
            if y2 > height:
                y2 = height
                y1 = height - size
            listLable = []
            for k in range(int(len(objectList) / 5)):
                object_label = objectList[k * 5]
                object_x1 = objectList[k * 5 + 1]
                object_y1 = objectList[k * 5 + 2]
                object_x2 = objectList[k * 5 + 3]
                object_y2 = objectList[k * 5 + 4]
                if float(object_x1) >= float(x1) and float(object_y1) >= float(y1) and float(object_x2) <= float(
                        x2) and float(
                        object_y2) <= float(y2):
                    listLable.append(object_label)
                    listLable.append(str(float(object_x1) - float(x1)))
                    listLable.append(str(float(object_y1) - float(y1)))
                    listLable.append(str(float(object_x2) - float(x1)))
                    listLable.append(str(float(object_y2) - float(y1)))
            if len(listLable) > 0:
                cropped = img.crop((x1, y1, x2, y2))  # (left, upper, right, lower)
                cropped.save("../CutResult/%s-%s_%s.jpg" % (name, x1, y1))
                test_str = " ".join(listLable)
                file = open('../CutResult/%s-%s_%s.txt' % (name, x1, y1), 'w')
                file.write(test_str);
                file.close()

 将txt数据集转为json

import os
import sys
import os.path as osp
import io
from labelme.logger import logger
from labelme import PY2
from labelme import QT4
import PIL.Image
from scipy import ndimage
import base64
from labelme import utils

module_path = os.path.abspath(os.path.join('..'))
if module_path not in sys.path:
    sys.path.append(module_path)
import json
from PIL import Image

Image.MAX_IMAGE_PIXELS = None
imagepath = 'CutResult//'


def load_image_file(filename):
    try:
        image_pil = PIL.Image.open(filename)
    except IOError:
        logger.error('Failed opening image file: {}'.format(filename))
        return

    # apply orientation to image according to exif
    image_pil = utils.apply_exif_orientation(image_pil)

    with io.BytesIO() as f:
        ext = osp.splitext(filename)[1].lower()
        if PY2 and QT4:
            format = 'PNG'
        elif ext in ['.jpg', '.jpeg']:
            format = 'JPEG'
        else:
            format = 'PNG'
        image_pil.save(f, format=format)
        f.seek(0)
        return f.read()


def dict_json(flags, imageData, shapes, imagePath, fillColor=None, lineColor=None, imageHeight=100, imageWidth=100):
    '''
    :param imageData: str
    :param shapes: list
    :param imagePath: str
    :param fillColor: list
    :param lineColor: list
    :return: dict
    '''
    return {"version": "3.16.4", "flags": flags, "shapes": shapes, 'lineColor': lineColor, "fillColor": fillColor,
            'imagePath': imagePath.split('/')[-1], "imageData": imageData, 'imageHeight': imageHeight,
            'imageWidth': imageWidth}


import cv2
from glob import glob

txtFiles_Path = "CutResult"
txtFiles = glob(txtFiles_Path + "/*.txt")
print(txtFiles)
for file in txtFiles:
    print(file)
    txtFile = file
    imagePH = file.replace(".txt", ".jpg")
    image = cv2.imread(imagePH)
    listResult = []
    with open(txtFile) as f:
        for line in f.readlines():
            for aa in line.split(' '):
                if aa != '\n':
                    listResult.append(aa)
    data = json.load(open('1.json'))
    shapes = data["shapes"]
    version = data["version"]
    flags = data["flags"]
    lineColor = data["lineColor"]
    fillColor = data['fillColor']
    newshapes = []
    for elem in range(int(len(listResult) / 5)):
        labelName = listResult[elem * 5]
        xmin = listResult[elem * 5 + 1]
        ymin = listResult[elem * 5 + 2]
        xmax = listResult[elem * 5 + 3]
        ymax = listResult[elem * 5 + 4]
        line_color = None
        fill_color = None
        newPoints = [[float(xmin), float(ymin)],
                     [float(xmax), float(ymax)]]
        shape_type = 'rectangle'
        flags = flags
        newshapes.append({"label": labelName, "line_color": line_color, "fill_color": fill_color, "points": newPoints,
                          "shape_type": shape_type, "flags": flags})
    imageData_90 = load_image_file(imagePH)
    imageData_90 = base64.b64encode(imageData_90).decode('utf-8')
    print(image.shape)
    imageHeight = image.shape[0]
    imageWidth = image.shape[1]
    data_90 = dict_json(flags, imageData_90, newshapes, imagePH, fillColor, lineColor, imageHeight, imageWidth)
    json_file = imagePH[:-4] + '.json'
    json.dump(data_90, open(json_file, 'w'))

查看转化后的数据集 

经过处理后,终于看着舒服点了。





【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。