车牌识别

在此先感谢以下博主提供的数据集和一些思路

 https://blog.csdn.net/yang1159/article/details/88303461

 https://blog.csdn.net/shadown1ght/article/details/78571187#comments

由于博主的代码风格和模型框架不太符合我的个人习惯,因此,自行搭建了网络结构,重新训练得到了参数,当然最重要的还是了解车牌识别的基本流程,因为技术实在是烂大街上了。。。

 

流程:

  1. 训练模型预测省份,由于数据只有五个省的,因此,模型只能在该五个省下预测,当然如果数据完整,模型只需重新训练一次即可
  2. 城市代号的识别——即车牌的第一个字母,由于不会出现数字,因此需要单独训练,以提高预测精度。
  3. 剩余五个字符的识别

一: 省份预测

1 数据的处理  我采用的是tf-records制作数据集,当然中间也出现了一些小插曲,在标签转成one_hot向量时,将dict类型enumerate后发现标签和预设置的不太一致,所以在做预测时总是错误的,测试了好久才发现是dict的无序性导致,在此作个总结。话不多说,上代码:

province_generate.py  ——省份数据集制作和读取

import tensorflow as tf
import os
from PIL import Image
import matplotlib.pyplot as plt



path = r'dataset\train_images\training-set\chinese-characters'
train_record_path = r"province_train.tfrecords"
test_record_path = r"province_test.tfrecords"
#classes = {'0', '1', '2', '3', '4', '5'}
list1 = ['0', '1', '2', '3', '4', '5']

def _byteslist(value):
    return tf.train.Feature(bytes_list=tf.train.BytesList(value=[value]))

def _int64list(value):
    return tf.train.Feature(int64_list=tf.train.Int64List(value=[value]))

def creat_train_record():
    writer = tf.python_io.TFRecordWriter(train_record_path)
    NUM = 1
    for index, name in enumerate(list1):
        class_path = path + "/" + name + '/'
        l = int(len(os.listdir(class_path))*0.7)
        for image_name in os.listdir(class_path)[:l]:
            image_path = class_path + image_name
            img = Image.open(image_path)
            img = img.resize((32, 40))
            img_raw = img.tobytes()
            example = tf.train.Example(
                    features=tf.train.Features(feature={
                            'label': _int64list(index),
                            'img_raw': _byteslist(img_raw)}))
            writer.write(example.SerializeToString())
            print('creat train record in ', NUM)
            NUM += 1
    writer.close()
    print('creat_train_record success !')

    
def creat_test_record():
    writer = tf.python_io.TFRecordWriter(test_record_path)
    NUM = 1
    for index, name in enumerate(list1):
        print(index, name)
        class_path = path + '/' + name +'/'
        l = int(len(os.listdir(class_path))*0.7)
        for image_name in os.listdir(class_path)[l:]:
            image_path = class_path + image_name
            img = Image.open(image_path)
            img = img.resize((32,40))
            img_raw = img.tobytes()
            example = tf.train.Example(
                    features=tf.train.Features(feature={
                                               'label': _int64list(index),
                                               'img_raw': _byteslist(img_raw)}))
            writer.write(example.SerializeToString())
            print('creat test record in', NUM)
            NUM += 1
    writer.close()
    print('creat test record finished !')

def read_record(filename, img_w, img_h):
    filename_queue = tf.train.string_input_producer([filename])
    reader = tf.TFRecordReader()
    _, serialize_example = reader.read(filename_queue)
    feature = tf.parse_single_example(
            serialize_example,
            features={
                    'label': tf.FixedLenFeature([], tf.int64),
                    'img_raw': tf.FixedLenFeature([], tf.string)})
    label = feature['label']
    img = feature['img_raw']
    img = tf.decode_raw(img, tf.uint8)
    img = tf.reshape(img, (32, 40, 1))
    #img = tf.image.resize_image_with_crop_or_pad(img, img_w, img_h) #裁剪图片
    img = tf.cast(img, tf.float32)/255
    label = tf.cast(label, tf.int32)
    return img, label

def get_batch_record(filename, batch_size, img_W, img_H):
    image, label = read_record(filename, img_W, img_H)
    image_batch, label_batch= tf.train.shuffle_batch([image, label],
                                                     batch_size=batch_size,
                                                     capacity=200+batch_size*60,
                                                     min_after_dequeue=1)
    label_batch = tf.one_hot(label_batch,depth=6)
    return image_batch, label_batch


'''
img, label = get_batch_record('province_train.tfrecords', 550, 32, 40)
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    sess.run(tf.local_variables_initializer())
    coord = tf.train.Coordinator()
    threads = tf.train.start_queue_runners(sess, coord)
    print('开始')
    for i in range(10):
        i, l = sess.run([img, label])
        print('label', l)
        img = i[499].reshape((40, 32))
        plt.imshow(img)
    plt.show()

'''
'''
creat_train_record()
creat_test_record()
#img, label = get_batch_record('province_record.records', 60, 32, 40)
#print(img[0].shape)  #32x40
#print(type(label))
#print(label.shape)
''' 

2  province_model——省份识别模型

'''
province_model
'''
import tensorflow as tf
import numpy as np
import time
from PIL import Image


def weight_variable(shape, name):
    init = tf.truncated_normal(shape, stddev = 0.1)
    return tf.Variable(init, name = name)

def bias_variable(shape, name):
    init = tf.constant(0.1, shape = shape)
    return tf.Variable(init, name=name)

def conv2d(x, w):
    return tf.nn.conv2d(x, w, strides=[1, 1, 1, 1], padding='SAME')

def max_pool_2x2(x, name):
    return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2,1], padding='SAME', name=name)

def max_pool_1x1(x, name):
    return tf.nn.max_pool(x, ksize=[1,1,1,1], strides=[1,1,1,1], padding='SAME', name=name)

def fc(inputs, W, b, name):
    return tf.nn.relu(tf.matmul(inputs, W) + b, name=name)



def CNN(x, NUM_CLASSES, keep_prob):
    #第一卷积层
    with tf.variable_scope('conv1') as scope:
        w_conv1 = weight_variable([8, 8, 1, 16], name='w_conv1')
        b_conv1 = bias_variable([16], name = 'b_conv1')
        h_conv1 = tf.nn.relu(conv2d(x, w_conv1)+b_conv1, name='conv1')
        #print(h_conv1.shape)  #(?, 32, 40, 16)
    #第一池化层
    with tf.variable_scope('pool1') as scope:
        h_pool1 = max_pool_2x2(h_conv1, name='pool1')
        #print(h_pool1.shape)  #(?, 16, 20, 16)
    
    #第二卷积层
    with tf.variable_scope('conv2') as scope:
        w_conv2 = weight_variable([5, 5, 16, 32], name='w_pool2')
        b_conv2 = bias_variable([32], name ='b_pool2')
        h_conv2 = tf.nn.relu(conv2d(h_pool1, w_conv2)+b_conv2, name = 'conv2')
        #print(h_conv2.shape)  #(?, 16, 20, 32)
    
    #第二池化层
    with tf.variable_scope('pool2') as scope:
        h_pool2 = max_pool_1x1(h_conv2, name='pool2')  #(batch, 16, 20, 32)
        #print(h_pool2.shape)  #(?, 16, 20, 32)
    
    h_pool2_hat = tf.reshape(h_pool2, [-1, 16*20*32])
    
    #全连接层
    with tf.variable_scope('fc') as scope:
        w_fc = weight_variable([16*20*32, 512], name='w_fc')
        b_fc = bias_variable([512], name='b_fc')
        h_fc = fc(h_pool2_hat, w_fc, b_fc, 'fc')
    
    h_dropout = tf.nn.dropout(h_fc, keep_prob=keep_prob)
    #print(h_dropout.shape)  #(?, 512)
    
    #softmax层
    with tf.variable_scope('softmax') as scope:
        w_sm = weight_variable([512, NUM_CLASSES], name='w_sm')
        b_sm = bias_variable([NUM_CLASSES], name='b_sm')
        pred = tf.nn.softmax(tf.matmul(h_dropout, w_sm)+b_sm, name='softmax')
        #print(pred.shape)  #(?, 6)
    return pred

def loss(pred, y):
    with tf.variable_scope('loss') as scope:
        loss = tf.reduce_mean(-tf.reduce_sum(y*tf.log(pred)))
    return loss
def accuracy(pred, y):
    with tf.variable_scope('accuracy') as scope:
        correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(pred, 1))
        accuracy = tf.reduce_mean(tf.cast(correct_prediction, 'float'))
    return accuracy

def optimizer(learning_rate, cost, global_step):
    optimizer = tf.train.AdamOptimizer(learning_rate).minimize(cost, global_step=global_step)
    return optimizer

3 province_train——省份模型训练及识别

注:添加了检查点,方便调用和预测

'''
province_model_train
'''
from province_model import CNN, loss, optimizer, accuracy
from province_generate import get_batch_record
import tensorflow as tf
import numpy as np
import os
from PIL import Image
import matplotlib.pyplot as plt
classes = {'0':'京', '1':'闽', '2':'粤', '3':'苏', '4': '沪', '5':'浙'}

batch_size = 60
NUM_CLASSES = 6
SIZE = 1280
img_h = 40
img_w = 32
learning_rate = 0.0001
train_epoch = 2000
display_step = 100
tf.reset_default_graph()


x = tf.placeholder(tf.float32, [None, img_w, img_h, 1])
y = tf.placeholder(tf.float32, [None, NUM_CLASSES])
keep_prob = tf.placeholder(tf.float32)
 
x_image = tf.reshape(x, [-1, img_w, img_h, 1])

global_step = tf.Variable(0, trainable=None)
decay_learning_rate=tf.train.exponential_decay(learning_rate, global_step=global_step, decay_steps=100, decay_rate=0.9)

pred = CNN(x_image, NUM_CLASSES, keep_prob)
loss = loss(pred, y)
optimizer = optimizer(decay_learning_rate, loss, global_step)
accuracy = accuracy(pred, y)

image_batch , label_batch = get_batch_record(r'province_train.tfrecords', 60, 32, 40)
img_test, y_test = get_batch_record(r'province_test.tfrecords', 60, 32, 40)

saver = tf.train.Saver(max_to_keep=1)
saver_dir = 'log/'
if not os.path.exists(saver_dir):
            print ('不存在训练数据保存目录,现在创建保存目录')
            os.makedirs(saver_dir)
'''
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    sess.run(tf.local_variables_initializer())
    coord = tf.train.Coordinator()
    threads = tf.train.start_queue_runners(sess, coord)
    print('开始训练 !')
    for i in range(train_epoch):
        image, label = sess.run([image_batch, label_batch])
        _, cost, acc = sess.run([optimizer, loss, accuracy],
                                feed_dict={x:image, y:label, keep_prob:0.8})
        if i % display_step == 0:
            print('epoch %4d, cost %.4f, accuracy %.4f' %(i, cost, acc))
        saver.save(sess, saver_dir+'province_model.ckpt', global_step = i)
        
    image_test, label_test = sess.run([img_test, y_test])
    _, test_acc = sess.run([optimizer, accuracy],
                           feed_dict ={x:image_test, y:label_test, keep_prob:1})
    print('test accuracy %.4f'%(test_acc))
        
    coord.request_stop()
'''
load_epoch = 1999

path = 'img_cut1.bmp'
#path = '1509817609_633_1.bmp'
#path = '1510069820_615_1.bmp'
#path = '1510069853_348_1.bmp'

#image = Image.open(path)
img = Image.open(path)
#print(img.shape)

img1 = np.array(img)/255

img2 = img1.reshape((-1, 32, 40, 1))
#img = np.transpose(img)
'''
'''
#img2 = img1.reshape((32, 40, 1))
#plt.imshow(img2)
#plt.show()


with tf.Session() as sess2:
    sess2.run(tf.global_variables_initializer())
    sess2.run(tf.local_variables_initializer())
    saver.restore(sess2, saver_dir+ 'province_model.ckpt-' +str(load_epoch))
    out = sess2.run([pred], feed_dict ={x:img2, keep_prob:1})
    lt = list(out[0][0])
    m = lt.index(max(lt))
    m1 =str(m)
    print(classes[m1])

4 结果:

epoch    0, cost 110.4452, accuracy 0.5000
epoch  100, cost 0.0081, accuracy 1.0000
epoch  200, cost 0.0031, accuracy 1.0000
epoch  300, cost 0.0005, accuracy 1.0000
epoch  400, cost 0.0010, accuracy 1.0000
epoch  500, cost 0.0007, accuracy 1.0000
epoch  600, cost 0.0014, accuracy 1.0000
epoch  700, cost 0.0007, accuracy 1.0000
epoch  800, cost 0.0003, accuracy 1.0000
epoch  900, cost 0.0001, accuracy 1.0000
epoch 1000, cost 0.0003, accuracy 1.0000
epoch 1100, cost 0.0011, accuracy 1.0000
epoch 1200, cost 0.0001, accuracy 1.0000
epoch 1300, cost 0.0002, accuracy 1.0000
epoch 1400, cost 0.0003, accuracy 1.0000
epoch 1500, cost 0.0008, accuracy 1.0000
epoch 1600, cost 0.0003, accuracy 1.0000
epoch 1700, cost 0.0004, accuracy 1.0000
epoch 1800, cost 0.0001, accuracy 1.0000
epoch 1900, cost 0.0000, accuracy 1.0000
test accuracy 1.0000

 

二:城市预测

1:数据的制作和读取

city_generate.py

'''
generate cities data
'''
import tensorflow as tf
import os
from PIL import Image
import matplotlib.pyplot as plt



path = r'dataset\train_images\training-set\letters'
train_record_path = r"city_train.tfrecords"
test_record_path = r"city_test.tfrecords"
#classes = {'0', '1', '2', '3', '4', '5'}
list1 = ['10', '11', '12', '13', '14', '15', '16', '17', '18',
         '19', '20', '21', '22', '23', '24', '25', '26', '27',
         '28', '29', '30', '31', '32', '33']

def _byteslist(value):
    return tf.train.Feature(bytes_list=tf.train.BytesList(value=[value]))

def _int64list(value):
    return tf.train.Feature(int64_list=tf.train.Int64List(value=[value]))

def creat_train_record():
    writer = tf.python_io.TFRecordWriter(train_record_path)
    NUM = 1
    for index, name in enumerate(list1):
        class_path = path + "/" + name + '/'
        l = int(len(os.listdir(class_path))*0.7)
        for image_name in os.listdir(class_path)[:l]:
            image_path = class_path + image_name
            img = Image.open(image_path)
            img = img.resize((32, 40))
            img_raw = img.tobytes()
            example = tf.train.Example(
                    features=tf.train.Features(feature={
                            'label': _int64list(index),
                            'img_raw': _byteslist(img_raw)}))
            writer.write(example.SerializeToString())
            print('creat train record in ', NUM)
            NUM += 1
    writer.close()
    print('creat_train_record success !')

    
def creat_test_record():
    writer = tf.python_io.TFRecordWriter(test_record_path)
    NUM = 1
    for index, name in enumerate(list1):
        print(index, name)
        class_path = path + '/' + name +'/'
        l = int(len(os.listdir(class_path))*0.7)
        for image_name in os.listdir(class_path)[l:]:
            image_path = class_path + image_name
            img = Image.open(image_path)
            img = img.resize((32,40))
            img_raw = img.tobytes()
            example = tf.train.Example(
                    features=tf.train.Features(feature={
                                               'label': _int64list(index),
                                               'img_raw': _byteslist(img_raw)}))
            writer.write(example.SerializeToString())
            print('creat test record in', NUM)
            NUM += 1
    writer.close()
    print('creat test record finished !')

def read_record(filename, img_w, img_h):
    filename_queue = tf.train.string_input_producer([filename])
    reader = tf.TFRecordReader()
    _, serialize_example = reader.read(filename_queue)
    feature = tf.parse_single_example(
            serialize_example,
            features={
                    'label': tf.FixedLenFeature([], tf.int64),
                    'img_raw': tf.FixedLenFeature([], tf.string)})
    label = feature['label']
    img = feature['img_raw']
    img = tf.decode_raw(img, tf.uint8)
    img = tf.reshape(img, (32, 40, 1))
    #img = tf.image.resize_image_with_crop_or_pad(img, img_w, img_h) #裁剪图片
    img = tf.cast(img, tf.float32)/255
    label = tf.cast(label, tf.int32)
    return img, label

def get_batch_record(filename, batch_size, img_W, img_H):
    image, label = read_record(filename, img_W, img_H)
    image_batch, label_batch= tf.train.shuffle_batch([image, label],
                                                     batch_size=batch_size,
                                                     capacity=200+batch_size*60,
                                                     min_after_dequeue=1)
    label_batch = tf.one_hot(label_batch,depth=24)
    return image_batch, label_batch


'''
img, label = get_batch_record('province_train.tfrecords', 550, 32, 40)
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    sess.run(tf.local_variables_initializer())
    coord = tf.train.Coordinator()
    threads = tf.train.start_queue_runners(sess, coord)
    print('开始')
    for i in range(10):
        i, l = sess.run([img, label])
        print('label', l)
        img = i[499].reshape((40, 32))
        plt.imshow(img)
    plt.show()

'''
'''
creat_train_record()
creat_test_record()
#img, label = get_batch_record('province_record.records', 60, 32, 40)
#print(img[0].shape)  #32x40
#print(type(label))
#print(label.shape)
'''

2  模型

'''
city model
'''

import tensorflow as tf
import numpy as np
import time
from PIL import Image


def weight_variable(shape, name):
    init = tf.truncated_normal(shape, stddev = 0.1)
    return tf.Variable(init, name = name)

def bias_variable(shape, name):
    init = tf.constant(0.1, shape = shape)
    return tf.Variable(init, name=name)

def conv2d(x, w):
    return tf.nn.conv2d(x, w, strides=[1, 1, 1, 1], padding='SAME')

def max_pool_2x2(x, name):
    return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2,1], padding='SAME', name=name)

def max_pool_1x1(x, name):
    return tf.nn.max_pool(x, ksize=[1,1,1,1], strides=[1,1,1,1], padding='SAME', name=name)

def fc(inputs, W, b, name):
    return tf.nn.relu(tf.matmul(inputs, W) + b, name=name)



def CNN(x, NUM_CLASSES, keep_prob):
    #第一卷积层
    with tf.variable_scope('conv1') as scope:
        w_conv1 = weight_variable([8, 8, 1, 16], name='w_conv1')
        b_conv1 = bias_variable([16], name = 'b_conv1')
        h_conv1 = tf.nn.relu(conv2d(x, w_conv1)+b_conv1, name='conv1')
        #print(h_conv1.shape)  #(?, 32, 40, 16)
    #第一池化层
    with tf.variable_scope('pool1') as scope:
        h_pool1 = max_pool_2x2(h_conv1, name='pool1')
        #print(h_pool1.shape)  #(?, 16, 20, 16)
    
    #第二卷积层
    with tf.variable_scope('conv2') as scope:
        w_conv2 = weight_variable([5, 5, 16, 32], name='w_pool2')
        b_conv2 = bias_variable([32], name ='b_pool2')
        h_conv2 = tf.nn.relu(conv2d(h_pool1, w_conv2)+b_conv2, name = 'conv2')
        #print(h_conv2.shape)  #(?, 16, 20, 32)
    
    #第二池化层
    with tf.variable_scope('pool2') as scope:
        h_pool2 = max_pool_1x1(h_conv2, name='pool2')  #(batch, 16, 20, 32)
        #print(h_pool2.shape)  #(?, 16, 20, 32)
    
    h_pool2_hat = tf.reshape(h_pool2, [-1, 16*20*32])
    
    #全连接层
    with tf.variable_scope('fc') as scope:
        w_fc = weight_variable([16*20*32, 512], name='w_fc')
        b_fc = bias_variable([512], name='b_fc')
        h_fc = fc(h_pool2_hat, w_fc, b_fc, 'fc')
    
    h_dropout = tf.nn.dropout(h_fc, keep_prob=keep_prob)
    #print(h_dropout.shape)  #(?, 512)
    
    #softmax层
    with tf.variable_scope('softmax') as scope:
        w_sm = weight_variable([512, NUM_CLASSES], name='w_sm')
        b_sm = bias_variable([NUM_CLASSES], name='b_sm')
        pred = tf.nn.softmax(tf.matmul(h_dropout, w_sm)+b_sm, name='softmax')
        #print(pred.shape)  #(?, 6)
    return pred

def loss(pred, y):
    with tf.variable_scope('loss') as scope:
        loss = tf.reduce_mean(-tf.reduce_sum(y*tf.log(pred)))
    return loss
def accuracy(pred, y):
    with tf.variable_scope('accuracy') as scope:
        correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(pred, 1))
        accuracy = tf.reduce_mean(tf.cast(correct_prediction, 'float'))
    return accuracy

def optimizer(learning_rate, cost, global_step):
    optimizer = tf.train.AdamOptimizer(learning_rate).minimize(cost, global_step=global_step)
    return optimizer

    

3 训练测试

city_train.py

from city_model import CNN, loss, optimizer, accuracy
from city_generate import get_batch_record
import tensorflow as tf
import numpy as np
import os
from PIL import Image
import matplotlib.pyplot as plt
classes = {'0':'A', '1':'B', '2':'C', '3':'D', '4': 'E', '5':'F', '6':'G',
           '7':'H', '8':'J', '9':'K', '10':'L', '11':'M', '12':'N', '13':'P',
           '14':'Q', '15':'R', '16':'S', '17':'T', '18':'U', '19':'V',
           '20':'W', '21':'X', '22':'Y', '23':'Z'}

batch_size = 60
NUM_CLASSES = 24
SIZE = 1280
img_h = 40
img_w = 32
learning_rate = 0.0001
train_epoch = 2000
display_step = 100
tf.reset_default_graph()


x = tf.placeholder(tf.float32, [None, img_w, img_h, 1])
y = tf.placeholder(tf.float32, [None, NUM_CLASSES])
keep_prob = tf.placeholder(tf.float32)
 
x_image = tf.reshape(x, [-1, img_w, img_h, 1])

global_step = tf.Variable(0, trainable=None)
decay_learning_rate=tf.train.exponential_decay(learning_rate, global_step=global_step, decay_steps=100, decay_rate=0.9)

pred = CNN(x_image, NUM_CLASSES, keep_prob)
loss = loss(pred, y)
optimizer = optimizer(decay_learning_rate, loss, global_step)
accuracy = accuracy(pred, y)

image_batch , label_batch = get_batch_record(r'city_train.tfrecords', 60, 32, 40)
img_test, y_test = get_batch_record(r'city_test.tfrecords', 60, 32, 40)

saver = tf.train.Saver(max_to_keep=1)
saver_dir = 'log/'
if not os.path.exists(saver_dir):
            print ('不存在训练数据保存目录,现在创建保存目录')
            os.makedirs(saver_dir)
'''
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    sess.run(tf.local_variables_initializer())
    coord = tf.train.Coordinator()
    threads = tf.train.start_queue_runners(sess, coord)
    print('开始训练 !')
    for i in range(train_epoch):
        image, label = sess.run([image_batch, label_batch])
        _, cost, acc = sess.run([optimizer, loss, accuracy],
                                feed_dict={x:image, y:label, keep_prob:0.8})
        if i % display_step == 0:
            print('epoch %4d, cost %.4f, accuracy %.4f' %(i, cost, acc))
        saver.save(sess, saver_dir+'city_model.ckpt', global_step = i)
        
    image_test, label_test = sess.run([img_test, y_test])
    _, test_acc = sess.run([optimizer, accuracy],
                           feed_dict ={x:image_test, y:label_test, keep_prob:1})
    print('test accuracy %.4f'%(test_acc))
        
    coord.request_stop()
'''
load_epoch = 1999

path = r'save_cut_for_char/little/img_cut2.bmp'
#path = '1509817609_633_1.bmp'
#path = '1510069820_615_1.bmp'
#path = '1510069853_348_1.bmp'

#image = Image.open(path)
img = Image.open(path)
#print(img.shape)

img1 = np.array(img)/255

img2 = img1.reshape((-1, 32, 40, 1))
#img = np.transpose(img)



#img2 = img1.reshape((32, 40, 1))
#plt.imshow(img2)
#plt.show()


with tf.Session() as sess2:
    sess2.run(tf.global_variables_initializer())
    sess2.run(tf.local_variables_initializer())
    saver.restore(sess2, saver_dir+ 'city_model.ckpt-' +str(load_epoch))
    out = sess2.run([pred], feed_dict ={x:img2, keep_prob:1})
    lt = list(out[0][0])
    m = lt.index(max(lt))
    m1 =str(m)
    print(classes[m1])

4:结果:

。。。忘了保存

三 后五位字符识别

1 数据制作和读取

number_letter_generate.py

'''
number and letter model
'''

import tensorflow as tf
import os
from PIL import Image
import matplotlib.pyplot as plt



path = r'dataset\train_images\training-set\number_letters'
train_record_path = r"number_letter_train.tfrecords"
test_record_path = r"number_letter_test.tfrecords"
#classes = {'0', '1', '2', '3', '4', '5'}
list1 = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10',
        '11', '12', '13', '14', '15', '16', '17', '18', '19',
        '20', '21', '22', '23', '24', '25', '26', '27', '28', 
        '29', '30', '31', '32', '33']

def _byteslist(value):
    return tf.train.Feature(bytes_list=tf.train.BytesList(value=[value]))

def _int64list(value):
    return tf.train.Feature(int64_list=tf.train.Int64List(value=[value]))

def creat_train_record():
    writer = tf.python_io.TFRecordWriter(train_record_path)
    NUM = 1
    for index, name in enumerate(list1):
        class_path = path + "/" + name + '/'
        l = int(len(os.listdir(class_path))*0.7)
        for image_name in os.listdir(class_path)[:l]:
            image_path = class_path + image_name
            img = Image.open(image_path)
            img = img.resize((32, 40))
            img_raw = img.tobytes()
            example = tf.train.Example(
                    features=tf.train.Features(feature={
                            'label': _int64list(index),
                            'img_raw': _byteslist(img_raw)}))
            writer.write(example.SerializeToString())
            print('creat train record in ', NUM)
            NUM += 1
    writer.close()
    print('creat_train_record success !')

    
def creat_test_record():
    writer = tf.python_io.TFRecordWriter(test_record_path)
    NUM = 1
    for index, name in enumerate(list1):
        print(index, name)
        class_path = path + '/' + name +'/'
        l = int(len(os.listdir(class_path))*0.7)
        for image_name in os.listdir(class_path)[l:]:
            image_path = class_path + image_name
            img = Image.open(image_path)
            img = img.resize((32,40))
            img_raw = img.tobytes()
            example = tf.train.Example(
                    features=tf.train.Features(feature={
                                               'label': _int64list(index),
                                               'img_raw': _byteslist(img_raw)}))
            writer.write(example.SerializeToString())
            print('creat test record in', NUM)
            NUM += 1
    writer.close()
    print('creat test record finished !')

def read_record(filename, img_w, img_h):
    filename_queue = tf.train.string_input_producer([filename])
    reader = tf.TFRecordReader()
    _, serialize_example = reader.read(filename_queue)
    feature = tf.parse_single_example(
            serialize_example,
            features={
                    'label': tf.FixedLenFeature([], tf.int64),
                    'img_raw': tf.FixedLenFeature([], tf.string)})
    label = feature['label']
    img = feature['img_raw']
    img = tf.decode_raw(img, tf.uint8)
    img = tf.reshape(img, (32, 40, 1))
    #img = tf.image.resize_image_with_crop_or_pad(img, img_w, img_h) #裁剪图片
    img = tf.cast(img, tf.float32)/255
    label = tf.cast(label, tf.int32)
    return img, label

def get_batch_record(filename, batch_size, img_W, img_H):
    image, label = read_record(filename, img_W, img_H)
    image_batch, label_batch= tf.train.shuffle_batch([image, label],
                                                     batch_size=batch_size,
                                                     capacity=200+batch_size*60,
                                                     min_after_dequeue=100)
    label_batch = tf.one_hot(label_batch, depth=34)
    return image_batch, label_batch


'''
img, label = get_batch_record('number_letter_train.tfrecords', 100, 32, 40)
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    sess.run(tf.local_variables_initializer())
    coord = tf.train.Coordinator()
    threads = tf.train.start_queue_runners(sess, coord)
    print('开始')
    for i in range(10):
        i, l = sess.run([img, label])
        print('label', l)
        img = i[97].reshape((40, 32))
        plt.imshow(img)
    plt.show()
'''


#creat_train_record()
#creat_test_record()
#img, label = get_batch_record('province_record.records', 60, 32, 40)
#print(img[0].shape)  #32x40
#print(type(label))
#print(label.shape)

2 模型

number_letter_model.py

'''
number letter model
'''

import tensorflow as tf
import numpy as np
import time
from PIL import Image


def weight_variable(shape, name):
    init = tf.truncated_normal(shape, stddev = 0.1)
    return tf.Variable(init, name = name)

def bias_variable(shape, name):
    init = tf.constant(0.1, shape = shape)
    return tf.Variable(init, name=name)

def conv2d(x, w):
    return tf.nn.conv2d(x, w, strides=[1, 1, 1, 1], padding='SAME')

def max_pool_2x2(x, name):
    return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2,1], padding='SAME', name=name)

def max_pool_1x1(x, name):
    return tf.nn.max_pool(x, ksize=[1,1,1,1], strides=[1,1,1,1], padding='SAME', name=name)

def fc(inputs, W, b, name):
    return tf.nn.relu(tf.matmul(inputs, W) + b, name=name)



def CNN(x, NUM_CLASSES, keep_prob):
    #第一卷积层
    with tf.variable_scope('conv1') as scope:
        w_conv1 = weight_variable([8, 8, 1, 16], name='w_conv1')
        b_conv1 = bias_variable([16], name = 'b_conv1')
        h_conv1 = tf.nn.relu(conv2d(x, w_conv1)+b_conv1, name='conv1')
        #print(h_conv1.shape)  #(?, 32, 40, 16)
    #第一池化层
    with tf.variable_scope('pool1') as scope:
        h_pool1 = max_pool_2x2(h_conv1, name='pool1')
        #print(h_pool1.shape)  #(?, 16, 20, 16)
    
    #第二卷积层
    with tf.variable_scope('conv2') as scope:
        w_conv2 = weight_variable([5, 5, 16, 32], name='w_pool2')
        b_conv2 = bias_variable([32], name ='b_pool2')
        h_conv2 = tf.nn.relu(conv2d(h_pool1, w_conv2)+b_conv2, name = 'conv2')
        #print(h_conv2.shape)  #(?, 16, 20, 32)
    
    #第二池化层
    with tf.variable_scope('pool2') as scope:
        h_pool2 = max_pool_1x1(h_conv2, name='pool2')  #(batch, 16, 20, 32)
        #print(h_pool2.shape)  #(?, 16, 20, 32)
    
    h_pool2_hat = tf.reshape(h_pool2, [-1, 16*20*32])
    
    #全连接层
    with tf.variable_scope('fc') as scope:
        w_fc = weight_variable([16*20*32, 512], name='w_fc')
        b_fc = bias_variable([512], name='b_fc')
        h_fc = fc(h_pool2_hat, w_fc, b_fc, 'fc')
    
    h_dropout = tf.nn.dropout(h_fc, keep_prob=keep_prob)
    #print(h_dropout.shape)  #(?, 512)
    
    #softmax层
    with tf.variable_scope('softmax') as scope:
        w_sm = weight_variable([512, NUM_CLASSES], name='w_sm')
        b_sm = bias_variable([NUM_CLASSES], name='b_sm')
        pred = tf.nn.softmax(tf.matmul(h_dropout, w_sm)+b_sm, name='softmax')
        #print(pred.shape)  #(?, 6)
    return pred

def loss(pred, y):
    with tf.variable_scope('loss') as scope:
        loss = tf.reduce_mean(-tf.reduce_sum(y*tf.log(pred)))
    return loss
def accuracy(pred, y):
    with tf.variable_scope('accuracy') as scope:
        correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(pred, 1))
        accuracy = tf.reduce_mean(tf.cast(correct_prediction, 'float'))
    return accuracy

def optimizer(learning_rate, cost, global_step):
    optimizer = tf.train.AdamOptimizer(learning_rate).minimize(cost, global_step=global_step)
    return optimizer

3 训练和测试

number_letter_train.py

'''
number letter train
'''
from number_letter_model import CNN, loss, optimizer, accuracy
from number_letter_generate import get_batch_record
import tensorflow as tf
import numpy as np
import os
from PIL import Image
import matplotlib.pyplot as plt
classes = {'0':'0', '1':'1', '2':'2', '3':'3', '4': '4', '5':'5', '6':'6',
           '7':'7', '8':'8', '9':'9', '10':'A', '11':'B', '12':'C', '13':'D',
           '14':'E', '15':'F', '16':'G', '17':'H', '18':'J', '19':'K',
           '20':'L', '21':'M', '22':'N', '23':'P', '24':'Q', '25':'R',
           '26':'S', '27':'T', '28':'U', '29':'V', '30':'W', '31':'X',
           '32':'Y', '33':'Z'}

batch_size = 60
NUM_CLASSES = 34
SIZE = 1280
img_h = 40
img_w = 32
learning_rate = 0.0001
train_epoch = 2300
display_step = 100

tf.reset_default_graph()


x = tf.placeholder(tf.float32, [None, img_w, img_h, 1])
y = tf.placeholder(tf.float32, [None, NUM_CLASSES])
keep_prob = tf.placeholder(tf.float32)
 
x_image = tf.reshape(x, [-1, img_w, img_h, 1])

global_step = tf.Variable(0, trainable=None)
decay_learning_rate=tf.train.exponential_decay(learning_rate, global_step=global_step, decay_steps=100, decay_rate=0.9)

pred = CNN(x_image, NUM_CLASSES, keep_prob)
loss = loss(pred, y)
optimizer = optimizer(decay_learning_rate, loss, global_step)
accuracy = accuracy(pred, y)

image_batch , label_batch = get_batch_record(r'number_letter_train.tfrecords', 60, 32, 40)
img_test, y_test = get_batch_record(r'number_letter_test.tfrecords', 60, 32, 40)

saver = tf.train.Saver(max_to_keep=1)
saver_dir = 'log/'
if not os.path.exists(saver_dir):
            print ('不存在训练数据保存目录,现在创建保存目录')
            os.makedirs(saver_dir)
'''
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    sess.run(tf.local_variables_initializer())
    coord = tf.train.Coordinator()
    threads = tf.train.start_queue_runners(sess, coord)
    print('开始训练 !')
    for i in range(train_epoch):
        image, label = sess.run([image_batch, label_batch])
        _, cost, acc = sess.run([optimizer, loss, accuracy],
                                feed_dict={x:image, y:label, keep_prob:0.8})
        if i % display_step == 0:
            print('epoch %4d, cost %.4f, accuracy %.4f' %(i, cost, acc))
        saver.save(sess, saver_dir+'number_letter_model.ckpt', global_step = i)
        
    image_test, label_test = sess.run([img_test, y_test])
    _, test_acc = sess.run([optimizer, accuracy],
                           feed_dict ={x:image_test, y:label_test, keep_prob:1})
    print('test accuracy %.4f'%(test_acc))
        
    coord.request_stop()
'''
load_epoch = 2999


with tf.Session() as sess2:
    sess2.run(tf.global_variables_initializer())
    sess2.run(tf.local_variables_initializer())
    saver.restore(sess2, saver_dir+ 'number_letter_model.ckpt-' +str(load_epoch))
    path = r'save_cut_for_char/little/img_cut'
    for i in range(3, 8):
        img_path = path + '%s.bmp'%(i)
        img = Image.open(img_path)
        #plt.imshow(img)
        #plt.show()
        img1 = np.array(img)/255
        img2 = img1.reshape((-1, 32, 40, 1))
        out = sess2.run([pred], feed_dict ={x:img2, keep_prob:1})
        lt = list(out[0][0])
        m = lt.index(max(lt))
        m1 =str(m)
        print(classes[m1])

4:结果:

开始训练 !
epoch    0, cost 945.6122, accuracy 0.0000
epoch  100, cost 59.4770, accuracy 0.6500
epoch  200, cost 9.8405, accuracy 0.9667
epoch  300, cost 3.3208, accuracy 0.9833
epoch  400, cost 3.9368, accuracy 0.9833
epoch  500, cost 2.4447, accuracy 0.9833
epoch  600, cost 1.4337, accuracy 1.0000
epoch  700, cost 1.3684, accuracy 1.0000
epoch  800, cost 2.8665, accuracy 0.9833
epoch  900, cost 0.2498, accuracy 1.0000
epoch 1000, cost 0.3968, accuracy 1.0000
epoch 1100, cost 0.1463, accuracy 1.0000
epoch 1200, cost 0.4505, accuracy 1.0000
epoch 1300, cost 0.3249, accuracy 1.0000
epoch 1400, cost 0.5653, accuracy 1.0000
epoch 1500, cost 0.2643, accuracy 1.0000
epoch 1600, cost 2.6213, accuracy 0.9833
epoch 1700, cost 0.1917, accuracy 1.0000
epoch 1800, cost 0.5325, accuracy 1.0000
epoch 1900, cost 0.1596, accuracy 1.0000
test accuracy 0.9833
四:预测新样本

当然最重要的不是分类,而是车牌的处理,包括车牌的定位和分割,事实上以下程序采用的第三方库,个人觉得结果不是很好,定位差,分割也不是很好,有很大的改进空间

cut_for_char.py ——车牌定位及分割,得到预测图片

import tensorflow as tf
import cv2
import numpy as np
import shutil
from PIL import Image
import matplotlib.pyplot as plt
import os

def cut_license(read_path, save_path, i):
    #wath _cascade = cv2.CascadeClassifier('cascade.xml') #加载第三方库
    watch_cascade = cv2.CascadeClassifier('cascade.xml')
    image = cv2.imread(read_path)
    resize_h = 1000
    height , width= image.shape[0], image.shape[1]
    scale = width / height  #宽高比例
    image = cv2.resize(image, (int(scale*resize_h), resize_h))
    img_gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY) #转成二值灰度图
    
    watches = watch_cascade.detectMultiScale(img_gray, 1.2, 2, minSize=(36, 9), maxSize=(36 * 40, 9 * 40))
    print('检测到的车牌数量:', len(watches))
    
    for (x, y, w, h) in watches:
        cv2.rectangle(image,(x, y), (x+w, y+h), (0, 0, 255), 1)
        cut_img = image[y+5:y-5+h, x+8:x-15+w]
        cut_gray = cv2.cvtColor(cut_img, cv2.COLOR_RGB2GRAY) #裁剪的车牌转成二值灰度图
        save_cut_gray_path = save_path + 'cut_gray'+ str(i)+ '.jpg'
        cv2.imwrite(save_cut_gray_path, cut_gray)
        im = Image.open(save_cut_gray_path)
        size = 720, 180
        image = im.resize(size, Image.ANTIALIAS)
        image.save(save_path+'\car_license'+str(i)+'.jpg', quality =96)
        break

def find_end(start_, white, black, arg, white_max, black_max, width):
    '''
    找到字符结束的边界,具备的特征是,该列的白色像素点的总和> 白色像素点列最大值的95%
    即判断为非字符列
    '''
    end_ = start_ + 1
    for m in range(start_ + 1, width - 1):
        if (black[m] if arg else white[m]) > (0.91 * black_max if arg else 0.91 * white_max):  # 0.95这个参数请多调整,对应下面的0.05
            #由于可能存在噪点,如果某一列上的像素点的总和 》 该类别的的列总和的最大值,判断为找到了字符的边界
            end_ = m
            break
    return end_
 

def car_license_for_char(read_path, save_path_big, save_path_little,i):
    img = cv2.imread(read_path)
    img_gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
    #高斯去噪,二值化
    blur = cv2.GaussianBlur(img_gray, (5, 5), 0)
    _, thre = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
    
    cv2.imwrite('car_license_thre.jpg', thre)
    
    white = []
    black = []
    height = thre.shape[0]
    width = thre.shape[1]
    white_max = 0
    black_max = 0
    
    for i in range(width):
        s = 0
        t = 0
        for j in range(height):
            if thre[j][i] == 255:
                s += 1
            if thre[j][i] == 0:
                t += 1
        white_max = max(white_max, s)
        black_max = max(black_max, t)
        white.append(s)
        black.append(t)
    
    arg = False
    if black_max > white_max :
        arg = True
    n = 1
    start = 1
    end = 2
    temp = 1
    
    while n < width - 2:
        n += 1
        if (white[n] if arg else black[n]) > (0.09 * white_max if arg else 0.09 * black_max):
        #if (white[n] if arg else black[n]) > (0.0.5*white_max if arg else 0.05*white_max):
            start = n
            end = find_end(start, white, black, arg, white_max, black_max, width)
            n = end
            
            if end - start > 5:
                
                if temp ==1 and end - start <20:
                    pass
                elif temp >3 and end - start <20:
                    #判定为1
                    shutil.copy(os.path.join(r"dataset\train_images\training-set\1\1509806878_75_5.bmp"),
                                os.path.join(save_path_big, str(temp)+'.bmp'))
                    pass
                else:
                    cj = thre[1:height, start:end]
                    cv2.imwrite(save_path_big +"\img_cut_not_3240"  + str(temp) + ".jpg", cj)
                    im = Image.open(save_path_big +"\img_cut_not_3240"  + str(temp) + ".jpg")
                    size = 32, 40
                    mmm = im.resize(size, Image.ANTIALIAS) #统一成32x40的shape
                    mmm.save(save_path_little+"\img_cut" +str(temp) +  ".bmp", quality=95)
                    #保存统一shape后的图片
                    # cv2.imshow('裁剪后:', mmm)
                    # cv2.imwrite("./py_car_num_tensor/img_cut/"+str(temp)+".bmp", cj)
                    temp = temp + 1
                    # cv2.waitKey(0)
'''
read_path = 'ceshi\car.jpg'
##
save_path = 'save_cut'
cut_license(read_path, save_path, 1)

car_license_for_char(r'save_cut\car_license1.jpg', r"save_cut_for_char\big", r"save_cut_for_char\little", 1)

'''

原车牌图:

裁剪后的图片:

预测结果:

INFO:tensorflow:Restoring parameters from log/province_model.ckpt-1999

runfile('C:/Users/HUANG/Desktop/car_license_recognition/city_train.py', wdir='C:/Users/HUANG/Desktop/car_license_recognition')
Reloaded modules: province_model, province_generate
INFO:tensorflow:Restoring parameters from log/city_model.ckpt-1999
E

runfile('C:/Users/HUANG/Desktop/car_license_recognition/number_letter_train.py', wdir='C:/Users/HUANG/Desktop/car_license_recognition')
Reloaded modules: city_model, city_generate
INFO:tensorflow:Restoring parameters from log/number_letter_model.ckpt-2999
S V 2 8 2 

苏E SV282

比较傻:将5识别为S,事实上数据太少,调参了好久结果还是这样,以为样本数据才几十。。。

总体来说还是了解了该过程

六:以下为不适用第三方库的定位代码

import cv2
import numpy as np
import math
import matplotlib.pyplot as plt
import os
from PIL import Image
 
def stretch(img):
    max = float(img.max())
    min = float(img.min())
 
    for i in range(img.shape[0]):
        for j in range(img.shape[1]):
            img[i, j] = (255/(max-min))*img[i,j]-(255*min)/(max-min)
             
    return img
     
def dobinaryzation(img):
    max = float(img.max())
    min = float(img.min())
     
    x = max - ((max-min) / 2)
    ret, threshedimg = cv2.threshold(img, x, 255, cv2.THRESH_BINARY)
     
    return threshedimg
 
def find_retangle(contour):
	y, x = [], []
	
	for p in contour:
		y.append(p[0][0])
		x.append(p[0][1])
		
	return [min(y), min(x), max(y), max(x)]
 
def locate_license(img, orgimg):
	img, contours, hierarchy = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
 
	# 找出最大的三个区域
	blocks = []
	for c in contours:
		# 找出轮廓的左上点和右下点,由此计算它的面积和长宽比
		r = find_retangle(c)
		a = (r[2]-r[0]) * (r[3]-r[1])
		s = (r[2]-r[0]) / (r[3]-r[1])
		
		blocks.append([r, a, s])
		
	# 选出面积最大的3个区域
	blocks = sorted(blocks, key=lambda b: b[2])[-3:]
	
	# 使用颜色识别判断找出最像车牌的区域
	maxweight, maxindex = 0, -1
	for i in range(len(blocks)):
		b = orgimg[blocks[i][0][1]:blocks[i][0][3], blocks[i][0][0]:blocks[i][0][2]]
		# RGB转HSV
		hsv = cv2.cvtColor(b, cv2.COLOR_BGR2HSV)
		# 蓝色车牌范围
		lower = np.array([100,50,50])
		upper = np.array([140,255,255])
		# 根据阈值构建掩模
		mask = cv2.inRange(hsv, lower, upper)
 
		# 统计权值
		w1 = 0
		for m in mask:
			w1 += m / 255
		
		w2 = 0
		for w in w1:
			w2 += w
			
		# 选出最大权值的区域
		if w2 > maxweight:
			maxindex = i
			maxweight = w2
		
	return blocks[maxindex][0]
	
def find_license(img):
	'''预处理'''
	# 压缩图像
	img = cv2.resize(img, (400, int(400*img.shape[0]/img.shape[1])))
	 
	# RGB转灰色
	grayimg = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
	 
	# 灰度拉伸
	stretchedimg = stretch(grayimg)
	 
	# 进行开运算,用来去噪声
	r = 16
	h = w = r * 2 + 1
	kernel = np.zeros((h, w), dtype=np.uint8)
	cv2.circle(kernel, (r, r), r, 1, -1)
	 
	openingimg = cv2.morphologyEx(stretchedimg, cv2.MORPH_OPEN, kernel)
	strtimg = cv2.absdiff(stretchedimg,openingimg)
	 
	# 图像二值化
	binaryimg = dobinaryzation(strtimg)
	 
	# 使用Canny函数做边缘检测
	cannyimg = cv2.Canny(binaryimg, binaryimg.shape[0], binaryimg.shape[1])
	 
	''' 消除小区域,保留大块区域,从而定位车牌'''
	# 进行闭运算
	kernel = np.ones((5,19), np.uint8)
	closingimg = cv2.morphologyEx(cannyimg, cv2.MORPH_CLOSE, kernel)
	 
	# 进行开运算
	openingimg = cv2.morphologyEx(closingimg, cv2.MORPH_OPEN, kernel)
	 
	# 再次进行开运算
	kernel = np.ones((11,5), np.uint8)
	openingimg = cv2.morphologyEx(openingimg, cv2.MORPH_OPEN, kernel)
 
	# 消除小区域,定位车牌位置
	rect = locate_license(openingimg, img)
	
	return rect, img


	# 读取图片
orgimg = cv2.imread('car4.jpg')
    
rect, img = find_license(orgimg)
for i in range(4):
    print(rect[i])
 
	# 框出车牌
cv2.rectangle(img, (rect[0], rect[1]), (rect[2], rect[3]), (0,255,0),2)
    
w = rect[3]-rect[1]
h = rect[2]- rect[0]
img1 = img[rect[0]:rect[1], rect[2]: rect[3],:]
img1 = img[rect[1]:rect[1]+w, rect[0]:rect[0]+h]
cv2.imshow('img1', img1)
cv2.imshow('img', img)
cv2.imwrite('img_1.jpg', img1)
    
 
cv2.waitKey(0)
cv2.destroyAllWindows()

结果:该图片在第三方库下直接裁剪时,定位存在很严重的问题,以下为上述代码下的结果,当然图片也需要做角度的调整,从而方便分割,但是相比第三方库已经好很多了

由于才疏学浅,以上如果出现错误,还望指正 !!!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值