ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

对于七段数码数字模型进行改进:一个关键的数字1的问题

2022-01-10 16:04:37  阅读:258  来源: 互联网

标签:gray plt num self cv2 paddle 数字模型 数码 七段


简 介: 对于训练集合进行扩增,需要根据图片本身在应用中可能遇到的变化进行。对于图片中的数码管数字识别,一个最重要的问题是字符的平移,特别是对于字符1来说,遇到的可能性最大。比如在一些三位半,四位半的数字表中,最前面的数字可能只有1,0两个数字,所以分割过程中,1的位置有可能位于图片的最左。针对这种情况,对于训练数据集合进行平移扩充,通过测试结果可以看出,模型的精度得到了提高。

关键词 数码管LENETCNN数据增强

数字1的问题 文章目录 问题来源 如何解决? 重新训练 准备数据集合 训练LeNet 检验数字问题 数字平移 数据准备 训练LCDNet 测试网络 总 结 资源下载 模型应用

 

§01 字1的问题


1.1 问题来源

  在 一个中等规模的七段数码数据库以及利用它训练的识别网络 中,利用了近200张网络数码图片训练出LeNet网络,可以达到了很好的数字(LCD,LED七段数字)识别的效果,网络的适应性比较强。但是在 测试LCDNet对于万用表读数识别效果 测试中,对于如下的图片识别的结果出现了问题:

  下面的图片被识别成“07729”
▲ 图1.1 图片内容被识别成07729

▲ 图1.1 图片内容被识别成07729

  问题出现在对于字符分割的问题上,明显,对于最左侧的“1”,实际上它的左侧部分被切割出去了。因此,将上述图片按照5等分,所获得到的图片如下。如果注视到这个分割结果,对于第一个字符应该说,还是分割的不错的。主要原因是“1”所在的位置偏向中心

▲ 图1.1.2 图片分为5等分对应的图片

▲ 图1.1.2 图片分为5等分对应的图片

  为了验证这个问题,对原来图片左侧进行填补背景颜色,对应的图片如下。

▲ 图1.1.3 对原来图片左侧进行填补背景之后的图片

▲ 图1.1.3 对原来图片左侧进行填补背景之后的图片

  然后再进行五等分,对应的图片为:

▲ 图1.1.4 补充分割之后的图片

▲ 图1.1.4 补充分割之后的图片

  在这种情况下,所获得的识别结果就正确了。

  这说明在原来训练模型中,对于“1”这个字符,更多的样本对应“1”它是在图片的左侧,而不是在中间或者右边。

1.2 如何解决?

  上面的这种情况对于数字“1”比较特殊,一种简单的解决方案,就是直接对于样本中所有的“1”的样本,都进行左右平移的扩充,使得模型对于“1”的左右位置不敏感。

▲ 图1.2.1 将1图片左右平移

▲ 图1.2.1 将1图片左右平移

    plt.figure(figsize=(10, 5))

    n = inp[0][0]
    x = list(range(0, 24, 4))
    print(type(n), shape(n), x)
    for id,xx in enumerate(x):
        mm = roll(n, xx)
        plt.subplot(1, len(x), id+1)
        plt.imshow(mm)

 

§02 新训练


2.1 准备数据集合

2.1.1 数据集合进行合并

  现在已经有了四个7段数字图片集合,将它们合并在一起。

  • 输入数字目录:7seg, testlcd, testled, testseg7
  • 输出数字目录:seg7all

  最终获得数字图片:303个

from headm import *                 # =
import shutil

inputdir = ['7Seg', 'testlcd', 'testled', 'testseg7']
outdir = '/home/aistudio/work/7seg/seg7all'

count = 0
for d in inputdir:
    dstr = '/home/aistudio/work/7seg/' + d
    fdir = os.listdir(dstr)

    for f in fdir:
        ext = f.split('.')[-1].upper()
        if ext.find('JPG') < 0 and ext.find('BMP') < 0: continue

        numstr = f.split('.')[0].split('-')[-1]

        outfn = '%03d-%s.%s'%(count, numstr, ext)
        outfn = os.path.join(outdir, outfn)
        count += 1

        shutil.copyfile(os.path.join(dstr, f), outfn)

printt(count)

2.1.2 分解图片中的数字

  对前面准备好的数字图片分割相应的数字。

(1)分割代码

from headm import *                 # =

import cv2
from tqdm import tqdm

picdir = '/home/aistudio/work/7seg/seg7all'
filedim = [s for s in os.listdir(picdir) if s.upper().find('BMP') > 0 or s.upper().find('JPG') > 0]
filedim = sorted(filedim)

outdir = '/home/aistudio/work/7seg/pic48'

totalpic = 0
OUT_SIZE            = 48

for f in tqdm(filedim):
    fn = os.path.join(picdir, f)
    img = cv2.imread(fn)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    imgwidth = gray.shape[1]
    imgheight = gray.shape[0]

    numstr = f.split('.')[0].split('-')[1]
    numnum = len(numstr)

    for i in range(numnum):
        left = imgwidth * i // numnum
        right = imgwidth * (i + 1) // numnum

        data = gray[0:imgheight, left:right]
        dataout = cv2.resize(data, (OUT_SIZE, OUT_SIZE))
        outfn = os.path.join(outdir, '%04d_%s.BMP'%(totalpic, numstr[i]))
        totalpic += 1
        cv2.imwrite(outfn, dataout)

        newheight = int(imgheight * 0.85)
        newwidth = int((right-left)*0.85)
        deltaheight = (imgheight- newheight)//2
        deltawidth = (right-left-newwidth)//2

        data = gray[deltaheight:imgheight-deltaheight, left:right]
        dataout = cv2.resize(data, (OUT_SIZE, OUT_SIZE))
        outfn = os.path.join(outdir, '%04d_%s.BMP'%(totalpic, numstr[i]))
        totalpic += 1
        cv2.imwrite(outfn, dataout)

        data = gray[0:imgheight, left+deltawidth:right-deltawidth]
        dataout = cv2.resize(data, (OUT_SIZE, OUT_SIZE))
        outfn = os.path.join(outdir, '%04d_%s.BMP'%(totalpic, numstr[i]))
        totalpic += 1
        cv2.imwrite(outfn, dataout)

        data = gray[deltaheight:imgheight-deltaheight, left+deltawidth:right-deltawidth]
        dataout = cv2.resize(data, (OUT_SIZE, OUT_SIZE))
        outfn = os.path.join(outdir, '%04d_%s.BMP'%(totalpic, numstr[i]))
        totalpic += 1
        cv2.imwrite(outfn, dataout)

printt(totalpic:)

  分割完毕之后,每个数字对应四个数字,分别是原来数字,上下左右膨胀1.17倍的图片。图片总数为: 5340。

▲ 图2.1.1 十个数字的不同频次分布

▲ 图2.1.1 十个数字的不同频次分布

2.1.3 数字清洗与1平移

  下面对于分割出的数字进行清洗,其中包含有“N”背景颜色的数字。另外,对于所有为“1”的数字往右平移倍增。

    if num == 1:
        img = cv2.imread(infn)
        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        gray1 = roll(gray, -12)
        gray2 = roll(gray, -24)
        gray3 = roll(gray, -36)

▲ 图2.1.2 往右分别平移 12,24,36

▲ 图2.1.2 往右分别平移 12,24,36

  可以看到实际上,只需要平移12,24两个即可。

  处理完之后,总共的数字个数: 6548,各个数字的分布如下,可以看到其中数字1已经倍增了3倍。
▲ 图2.1.3 处理完之后的数字分布

▲ 图2.1.3 处理完之后的数字分布

from headm import *                 # =
import shutil
import cv2

digitdir = '/home/aistudio/work/7seg/pic48'
outdir = '/home/aistudio/work/7seg/pic48_1'

filedim = sorted(os.listdir(digitdir))
printt(len(filedim))

label = []
count = 0
for f in filedim:
    infn = os.path.join(digitdir, f)

    nstr = f.split('.')[0].split('_')[-1]
    if not nstr.isdigit(): continue

    extstr = f.split('.')[-1]
    num = int(nstr)

    outfn = os.path.join(outdir, '%05d_%d.%s'%(count, num, extstr))
    count += 1

    label.append(num)
    shutil.copyfile(infn, outfn)

    if num == 1:
        img = cv2.imread(infn)
        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        gray1 = roll(gray, -12)
        gray2 = roll(gray, -24)

        outfn = os.path.join(outdir, '%05d_%d.%s'%(count, num, extstr))
        count += 1
        cv2.imwrite(outfn, gray1)
        label.append(num)

        outfn = os.path.join(outdir, '%05d_%d.%s'%(count, num, extstr))
        count += 1
        cv2.imwrite(outfn, gray2)
        label.append(num)

printt(count)

plt.figure(figsize=(10,6))
plt.hist(label, 10)
plt.xlabel("N")
plt.ylabel("Frequency")
plt.grid(True)
plt.tight_layout()
plt.show()

2.1.4 图片数据归一化

  将生成的图片目录中的数据转换成归一化的图片数据。

from headm import *                 # =

import paddle
import paddle.fluid as fluid
import paddle.nn.functional as F
from paddle import to_tensor as TT
import cv2

outdir = '/home/aistudio/work/7seg/pic48_1'

filedim = sorted([s for s in os.listdir(outdir) if s.find('BMP') > 0 or s.find('JPG') > 0])
printt(len(filedim))

picarray = []
labeldim = []

for f in filedim:
    fn = os.path.join(outdir, f)
    img = cv2.imread(fn)

    gray = img[:,:,0]
    gray = gray - mean(gray)
    stdd = std(gray)

    gray1 = gray / stdd
    gray2 = gray * (-1.0)

    ff = f.split('.')[0].split('_')[-1]

    if ff.isdigit():
        ff = int(ff)
        picarray.append(gray1)
        picarray.append(gray2)
        labeldim.append(ff)
        labeldim.append(ff)

printt(shape(picarray))
printt(labeldim)

outfile = '/home/aistudio/work/7seg/seg7_48_4_1.npz'
savez(outfile, pic=picarray, label=labeldim)

  处理完之后,数据个数:

数据参数:
个数:13096
尺寸:48×48
文件名称:seg7_48_4_1.npz

2.2 训练LeNet

  利用与 一个中等规模的七段数码数据库以及利用它训练的识别网络 相同的网络,对于刚刚生成的数据库进行训练。

训练参数:
BatchSize:1000
Lr:0.001
Epoch:200
训练时间:192s
  • 训练环境: AI Studio,智尊版本。

▲ 图2.2.1 训练曲线:训练精度和测试精度

▲ 图2.2.1 训练曲线:训练精度和测试精度

  • 训练存储模型:seg7model4_1.pdparams

2.2.1 训练代码

from headm import *                 # =

import paddle
import paddle.fluid as fluid
from paddle import to_tensor as TT
from paddle.nn.functional import square_error_cost as SQRC

datafile = '/home/aistudio/work/7seg/seg7_48_4_1.npz'

data = load(datafile)
lcd = data['pic']
llabel = data['label']
printt(lcd.shape, llabel.shape)

dl = [(d,l) for d,l in zip(lcd, llabel)]
random.shuffle(dl)
printt(shape(dl))

train_ratio = 0.8
train_num = int(len(llabel) * train_ratio)

train_lcd = [a[0] for a in dl[:train_num]]
train_label = [a[1] for a in dl[:train_num]]
test_lcd = array([a[0] for a in dl[train_num:]])
test_label = array([a[1] for a in dl[train_num:]])

class Dataset(paddle.io.Dataset):
    def __init__(self, num_samples):
        super(Dataset, self).__init__()
        self.num_samples = num_samples

    def __getitem__(self, index):
        data = train_lcd[index][newaxis,:,:]
        label = train_label[index]
        return paddle.to_tensor(data,dtype='float32'), paddle.to_tensor(label,dtype='int64')

    def __len__(self):
        return self.num_samples

_dataset = Dataset(len(train_label))
train_loader = paddle.io.DataLoader(_dataset, batch_size=1000, shuffle=True)

test_d = paddle.to_tensor([a[newaxis,:,:] for a in test_lcd], dtype='float32')
test_l = paddle.to_tensor(test_label[:, newaxis], dtype='int64')

printt(shape(test_d):, shape(test_l):)

imgwidth = 48
imgheight = 48
inputchannel = 1
kernelsize   = 5
targetsize = 10
ftwidth = ((imgwidth-kernelsize+1)//2-kernelsize+1)//2
ftheight = ((imgheight-kernelsize+1)//2-kernelsize+1)//2

class lenet(paddle.nn.Layer):
    def __init__(self, ):
        super(lenet, self).__init__()
        self.conv1 = paddle.nn.Conv2D(in_channels=inputchannel, out_channels=6, kernel_size=kernelsize, stride=1, padding=0)
        self.conv2 = paddle.nn.Conv2D(in_channels=6, out_channels=16, kernel_size=kernelsize, stride=1, padding=0)
        self.mp1    = paddle.nn.MaxPool2D(kernel_size=2, stride=2)
        self.mp2    = paddle.nn.MaxPool2D(kernel_size=2, stride=2)
        self.L1     = paddle.nn.Linear(in_features=ftwidth*ftheight*16, out_features=120)
        self.L2     = paddle.nn.Linear(in_features=120, out_features=86)
        self.L3     = paddle.nn.Linear(in_features=86, out_features=targetsize)

    def forward(self, x):
        x = self.conv1(x)
        x = paddle.nn.functional.relu(x)
        x = self.mp1(x)
        x = self.conv2(x)
        x = paddle.nn.functional.relu(x)
        x = self.mp2(x)
        x = paddle.flatten(x, start_axis=1, stop_axis=-1)
        x = self.L1(x)
        x = paddle.nn.functional.relu(x)
        x = self.L2(x)
        x = paddle.nn.functional.relu(x)
        x = self.L3(x)
        return x

model = lenet()

optimizer = paddle.optimizer.Adam(learning_rate=0.001, parameters=model.parameters())
def train(model):
    model.train()
    epochs = 200
    for epoch in range(epochs):
        for batch, data in enumerate(train_loader()):
            out = model(data[0])
            loss = paddle.nn.functional.cross_entropy(out, data[1])
            acc = paddle.metric.accuracy(out, data[1]).numpy()

            preout = model(test_d)
            test_acc = paddle.metric.accuracy(preout, test_l).numpy()

            loss.backward()
            optimizer.step()
            optimizer.clear_grad()

        printt('Epoch:{}, Accuracys:{}, Test:{}'.format(epoch, acc, test_acc))

train(model)

paddle.save(model.state_dict(), './work/seg7model4_1.pdparams')

filename = '/home/aistudio/stdout.txt'

accdim = []
testdim = []
with open(filename, 'r') as f:
    for l in f.readlines():
        ll = l.split(':[')
        if len(ll) < 3: continue

        lacc = ll[-2].split(']')
        if len(lacc) < 2: continue
        accdim.append(float(lacc[0]))
        tacc = ll[-1].split(']')
        if len(tacc) < 2: continue
        testdim.append(float(tacc[0]))

plt.figure(figsize=(12, 8))
plt.plot(accdim, label='Train ACC')
plt.plot(testdim, label='Test ACC')
plt.xlabel("Step")
plt.ylabel("Acc")
plt.grid(True)
plt.legend(loc='upper right')
plt.tight_layout()
plt.show()

2.3 检验数字问题

  利用训练后所得到的模型,重新建议前面数字1所碰到的问题。

  最终的识别结果,无论是原来的图片,还是之后左边补充背景颜色的图片,识别结构都正常了。

  这说明通过平移1图片对于模型性能的提高是起到很重要的作用的。

 

§03 字平移


  据前面的结果,下面对所有的数字都进行平移扩增,只是对“1”是往左平移12,24,对于其它的数字往左右各平移6,形成倍增后的数字。

3.1 数据准备

3.1.1 平移数字

from headm import *                 # =
import shutil
import cv2

digitdir = '/home/aistudio/work/7seg/pic48'
outdir = '/home/aistudio/work/7seg/pic48_1'

filedim = sorted(os.listdir(digitdir))
printt(len(filedim))

label = []
count = 0
for f in filedim:
    infn = os.path.join(digitdir, f)

    nstr = f.split('.')[0].split('_')[-1]
    if not nstr.isdigit(): continue

    extstr = f.split('.')[-1]
    num = int(nstr)

    outfn = os.path.join(outdir, '%05d_%d.%s'%(count, num, extstr))
    count += 1

    label.append(num)
    shutil.copyfile(infn, outfn)

    if num == 1:
        img = cv2.imread(infn)
        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        gray1 = roll(gray, -12)
        gray2 = roll(gray, -24)

        outfn = os.path.join(outdir, '%05d_%d.%s'%(count, num, extstr))
        count += 1
        cv2.imwrite(outfn, gray1)
        label.append(num)

        outfn = os.path.join(outdir, '%05d_%d.%s'%(count, num, extstr))
        count += 1
        cv2.imwrite(outfn, gray2)
        label.append(num)
    else:
        img = cv2.imread(infn)
        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        gray1 = roll(gray, 6)
        gray2 = roll(gray, -6)

        outfn = os.path.join(outdir, '%05d_%d.%s'%(count, num, extstr))
        count += 1
        cv2.imwrite(outfn, gray1)
        label.append(num)

        outfn = os.path.join(outdir, '%05d_%d.%s'%(count, num, extstr))
        count += 1
        cv2.imwrite(outfn, gray2)
        label.append(num)

printt(count)

plt.figure(figsize=(10,6))
plt.hist(label, 10)
plt.xlabel("N")
plt.ylabel("Frequency")
plt.grid(True)
plt.tight_layout()
plt.show()

  评议结果:个数:15948.

3.1.2 数据归一化

  • 数据个数:43384
  • 数据尺寸:48×48
  • 数据文件:seg7_48_4_1_all.npz

3.2 训练LCDNet

  训练参数与前面相同,训练后模型存入:

/work/seg7model4_1_all.pdparams

  • BatchSize:1000
  • Lr:0.001
  • Epoch:200
  • 训练数据量:43384
  • 训练/测试占比:0.8:0.2

  最终的训练精度:

  • Train Accuarcy: 1.0
  • Test Accuarcy: 0.991

▲ 图3.2.1 训练过程的精度变化曲线

▲ 图3.2.1 训练过程的精度变化曲线

3.3 测试网络

  利用该模型,对于303个七段数码管数字识别,进行测试。

▲ 图A3.3.1 测试训练样本

▲ 图A3.3.1 测试训练样本

  总共有两个图片识别存在错误:
▲ 图3.3.1 识别为:1824

▲ 图3.3.1 识别为:1824

▲ 图3.3.2 识别为:1466

▲ 图3.3.2 识别为:1466

 

  结 ※


  于训练集合进行扩增,需要根据图片本身在应用中可能遇到的变化进行。对于图片中的数码管数字识别,一个最重要的问题是字符的平移,特别是对于字符1来说,遇到的可能性最大。比如在一些三位半,四位半的数字表中,最前面的数字可能只有1,0两个数字,所以分割过程中,1的位置有可能位于图片的最左。

  针对这种情况,对于训练数据集合进行平移扩充,通过测试结果可以看出,模型的精度得到了提高。

4.1 资源下载

4.2 模型应用

from headm import *                 # =

import paddle
import paddle.fluid as fluid
import cv2

imgwidth = 48
imgheight = 48
inputchannel = 1
kernelsize   = 5
targetsize = 10
ftwidth = ((imgwidth-kernelsize+1)//2-kernelsize+1)//2
ftheight = ((imgheight-kernelsize+1)//2-kernelsize+1)//2

class lenet(paddle.nn.Layer):
    def __init__(self, ):
        super(lenet, self).__init__()
        self.conv1 = paddle.nn.Conv2D(in_channels=inputchannel, out_channels=6, kernel_size=kernelsize, stride=1, padding=0)
        self.conv2 = paddle.nn.Conv2D(in_channels=6, out_channels=16, kernel_size=kernelsize, stride=1, padding=0)
        self.mp1    = paddle.nn.MaxPool2D(kernel_size=2, stride=2)
        self.mp2    = paddle.nn.MaxPool2D(kernel_size=2, stride=2)
        self.L1     = paddle.nn.Linear(in_features=ftwidth*ftheight*16, out_features=120)
        self.L2     = paddle.nn.Linear(in_features=120, out_features=86)
        self.L3     = paddle.nn.Linear(in_features=86, out_features=targetsize)

    def forward(self, x):
        x = self.conv1(x)
        x = paddle.nn.functional.relu(x)
        x = self.mp1(x)
        x = self.conv2(x)
        x = paddle.nn.functional.relu(x)
        x = self.mp2(x)
        x = paddle.flatten(x, start_axis=1, stop_axis=-1)
        x = self.L1(x)
        x = paddle.nn.functional.relu(x)
        x = self.L2(x)
        x = paddle.nn.functional.relu(x)
        x = self.L3(x)
        return x

model = lenet()
model.set_state_dict(paddle.load('./work/seg7model4_1_all.pdparams'))
OUT_SIZE    = 48
def pic2netinput(imgfile):
    img = cv2.imread(imgfile)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    imgwidth = gray.shape[1]
    imgheight = gray.shape[0]

    f = os.path.basename(imgfile)
    numstr = f.split('.')[0].split('-')[1]
    numnum = len(numstr)

    imgarray = []
    labeldim = []

    for i in range(numnum):
        left = imgwidth * i // numnum
        right = imgwidth * (i + 1) // numnum

        data = gray[0:imgheight, left:right]
        dataout = cv2.resize(data, (OUT_SIZE, OUT_SIZE))

        dataout = dataout - mean(dataout)
        stdd = std(dataout)
        dataout = dataout / stdd

        if numstr[i].isdigit():
            imgarray.append(dataout[newaxis,:,:])
            labeldim.append(int(numstr[i]))

    test_i = paddle.to_tensor(imgarray, dtype='float32')
    test_label = array(labeldim)
    test_l = paddle.to_tensor(test_label[:, newaxis], dtype='int64')

    return test_i, test_l

picdir = '/home/aistudio/work/7seg/seg7all'

filedim = [s for s in os.listdir(picdir) if s.upper().find('BMP') > 0 or s.upper().find('JPG') > 0]
filedim = sorted(filedim)

def checkimglabel(imgfile):
    inp, label = pic2netinput(imgfile)
    preout = model(inp)
    preid = paddle.argmax(preout, axis=1).numpy().flatten()
    label = label.numpy().flatten()
    error = where(label != preid)[0]

    printt(preid:, label:)
'''
    inp = inp.numpy()

    plt.figure(figsize=(10, 5))

    n = inp[0][0]
    x = list(range(0, 24, 4))
    printt(type(n), shape(n), x)
    for id,xx in enumerate(x):
        mm = roll(n, xx)
        plt.subplot(1, len(x), id+1)
        plt.imshow(mm)

'''

    return error, preid

'''
imgfile = os.path.join(picdir, filedim[-1])
error,id = checkimglabel(imgfile)
printt(error:, id:)
'''

for f in filedim:
    imgfile = os.path.join(picdir, f)
    error,id = checkimglabel(imgfile)

    if len(error) > 0:
        printt(error, f, id)

        img = cv2.imread(imgfile)[:,:,::-1]
        plt.clf()
        plt.figure(figsize=(8,8))
        plt.axis("off")
        plt.imshow(img)
        plt.show()


■ 相关文献链接:

● 相关图表链接:

标签:gray,plt,num,self,cv2,paddle,数字模型,数码,七段
来源: https://blog.csdn.net/zhuoqingjoking97298/article/details/122405875

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有