简 介: 对于训练集合进行扩增,需要根据图片本身在应用中可能遇到的变化进行。对于图片中的数码管数字识别,一个最重要的问题是字符的平移,特别是对于字符1来说,遇到的可能性最大。比如在一些三位半,四位半的数字表中,最前面的数字可能只有1,0两个数字,所以分割过程中,1的位置有可能位于图片的最左。针对这种情况,对于训练数据集合进行平移扩充,通过测试结果可以看出,模型的精度得到了提高。
关键词
: 数码管,LENET,CNN,数据增强
§01 数字1的问题
1.1 问题来源
在 一个中等规模的七段数码数据库以及利用它训练的识别网络 中,利用了近200张网络数码图片训练出LeNet网络,可以达到了很好的数字(LCD,LED七段数字)识别的效果,网络的适应性比较强。但是在 测试LCDNet对于万用表读数识别效果 测试中,对于如下的图片识别的结果出现了问题:
下面的图片被识别成“07729”
▲ 图1.1 图片内容被识别成07729
问题出现在对于字符分割的问题上,明显,对于最左侧的“1”,实际上它的左侧部分被切割出去了。因此,将上述图片按照5等分,所获得到的图片如下。如果注视到这个分割结果,对于第一个字符应该说,还是分割的不错的。主要原因是“1”所在的位置偏向中心。
▲ 图1.1.2 图片分为5等分对应的图片
为了验证这个问题,对原来图片左侧进行填补背景颜色,对应的图片如下。
▲ 图1.1.3 对原来图片左侧进行填补背景之后的图片
然后再进行五等分,对应的图片为:
▲ 图1.1.4 补充分割之后的图片
在这种情况下,所获得的识别结果就正确了。
这说明在原来训练模型中,对于“1”这个字符,更多的样本对应“1”它是在图片的左侧,而不是在中间或者右边。
1.2 如何解决?
上面的这种情况对于数字“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。
▲ 图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
可以看到实际上,只需要平移12,24两个即可。
处理完之后,总共的数字个数: 6548,各个数字的分布如下,可以看到其中数字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
:1000Lr
:0.001Epoch
:200训练时间
:192s
- 训练环境: AI Studio,智尊版本。
▲ 图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.3 测试网络
利用该模型,对于303个七段数码管数字识别,进行测试。
▲ 图A3.3.1 测试训练样本
总共有两个图片识别存在错误:
▲ 图3.3.1 识别为:1824
▲ 图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()
■ 相关文献链接:
- 一个中等规模的七段数码数据库以及利用它训练的识别网络
- 测试LCDNet对于万用表读数识别效果
- AI Studio数据集合:扩增后的数据集合
- 七段数码管测试数据集合,LENET训练好的模型-深度学习文档类资源-CSDN文库
● 相关图表链接:
- 图1.1 图片内容被识别成07729
- 图1.1.2 图片分为5等分对应的图片
- 图1.1.3 对原来图片左侧进行填补背景之后的图片
- 图1.1.4 补充分割之后的图片
- 图1.2.1 将1图片左右平移
- 图2.1.1 十个数字的不同频次分布
- 图2.1.2 往右分别平移 12,24,36
- 图2.1.3 处理完之后的数字分布
- 图2.2.1 训练曲线:训练精度和测试精度
- 图3.2.1 训练过程的精度变化曲线
- 图A3.3.1 测试训练样本
- 图3.3.1 识别为:1824
- 图3.3.2 识别为:1466