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

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

关键词 数码管LENETCNN数据增强

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

 

§01 字1的问题


1.1 问题来源

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

  下面的图片被识别成“07729”
对于七段数码数字模型进行改进:一个关键的数字1的问题

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

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

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

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

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

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

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

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

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

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

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

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

1.2 如何解决?

  上面的这种情况对于数字“1”比较特殊,一种简单的解决方案,就是直接对于样本中所有的“1”的样本,都进行左右平移的扩充,使得模型对于“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。

对于七段数码数字模型进行改进:一个关键的数字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)

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

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

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

  处理完之后,总共的数字个数: 6548,各个数字的分布如下,可以看到其中数字1已经倍增了3倍。
对于七段数码数字模型进行改进:一个关键的数字1的问题

▲ 图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,智尊版本。

对于七段数码数字模型进行改进:一个关键的数字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

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

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

3.3 测试网络

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

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

▲ 图A3.3.1 测试训练样本

  总共有两个图片识别存在错误:
对于七段数码数字模型进行改进:一个关键的数字1的问题

▲ 图3.3.1 识别为:1824

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

▲ 图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()


■ 相关文献链接:

● 相关图表链接:

上一篇:基于Opencv的图像卡通化


下一篇:基于python的opencv项目实战笔记(六)—— 图像金字塔与轮廓检测