【CSDN 编者按】本项目主要从两方面出发,一是搭建目标检测系统,利用hog+svm的方法,从网络摄像头读取数据,目标检测找出校园卡的位置;二是在找到校园卡位置后,保存下单独校园卡图片,然后进行图像处理,找到关键文字位置,利用百度文字识别进行提取文字信息。

作者 | 李秋健     责编 | 张红月

头图 | 下载于视觉中国

校园卡目标检测

1.1 环境要求

本次环境使用的是python3.6.5+windows平台,主要用的库是图像处理库opencv,包括用来目标检测和图像处理等操作。

1.2 数据集处理

其中数据集由自己利用手机摄像头拍照获得,因为要使用的分类算的是SVM算法,故需要定义两种类别,一种是需要寻找的目标图片,即校园卡图片存储在positive文件夹下,如图1可见;第二种类别是干扰的其它图片,存放在negative文件夹下,如图2所示。

图1 positive文件夹数据集  

图2 negative文件夹数据集

通过os模块加载本地文件夹中的图片,分别以pos_dir,neg_dir和test_dir变量用来存储正样本数据、负样本数据和测试集数据。具体代码如下:

 1pwd = os.getcwd()
 2logger.info('Current path is:{}'.format(pwd))
 3# 提取正样本
 4pos_dir = os.path.join(pwd, 'Positive')
 5if os.path.exists(pos_dir):
 6    logger.info('Positive data path is:{}'.format(pos_dir))
 7    pos = os.listdir(pos_dir)
 8    logger.info('Positive samples number:{}'.format(len(pos)))
 9# 提取负样本
10neg_dir = os.path.join(pwd, 'Negative')
11if os.path.exists(neg_dir):
12    logger.info('Negative data path is:{}'.format(neg_dir))
13    neg = os.listdir(neg_dir)
14    logger.info('Negative samples number:{}'.format(len(neg)))
15# 提取测试集
16test_dir = os.path.join(pwd, 'TestData')
17if os.path.exists(test_dir):
18    logger.info('Test data path is:{}'.format(test_dir))
19    test = os.listdir(test_dir)
20    logger.info('Test samples number:{}'.format(len(test)))

其中训练的数据需要将训练集和测试室合在一起,同时定义标签数组与之对应,即属于正样本时标签就是为1;属于负样本数据时标签就是变为-1。

具体代码如下:

 1pwd = os.getcwd()
 2pos_dir = os.path.join(pwd, 'Positive')
 3neg_dir = os.path.join(pwd, 'Negative')
 4samples = []
 5labels = []
 6for f in pos:
 7    file_path = os.path.join(pos_dir, f)
 8    if os.path.exists(file_path):
 9        samples.append(file_path)
10        labels.append(1.)
11for f in neg:
12    file_path = os.path.join(neg_dir, f)
13    if os.path.exists(file_path):
14        samples.append(file_path)
15        labels.append(-1.)
16# labels 要转换成numpy数组,类型为np.int32
17labels = np.int32(labels)
18labels_len = len(pos) + len(neg)
19labels = np.resize(labels, (labels_len, 1))

1.3 特征提取

其中特征的提取主要通过从训练数据集中提取HOG特征作为训练特征,其中函数HOGDescriptor一共有4个构造函数,其中分别是参数winSize(64,128), blockSize(16,16), blockStride(8,8), cellSize(8,8), nbins(9)。这些都是HOGDescriptor的成员变量,括号里的数值是它们的默认值,它们反应了HOG描述子的参数。其中winSize指的是窗口大小 ,blockSize指的是块大小 ,cellSize指的是胞元大,nbins指的是梯度方向数,nBins表示在一个胞元(cell)中统计梯度的方向数目,例如nBins=9时,在一个胞元内统计9个方向的梯度直方图,每个方向为180/9=20度。

具体代码如下:

 1train = []
 2logger.info('Extracting HOG Descriptors...')
 3num = 0.
 4total = len(samples)
 5for f in samples:
 6    num += 1.
 7    logger.info('Processing {} {:2.1f}%'.format(f, num/total*100))
 8    hog = cv2.HOGDescriptor((64,128), (16,16), (8,8), (8,8), 9)
 9    # hog = cv2.HOGDescriptor()
10    img = cv2.imread(f, -1)
11    img = cv2.resize(img, (64,128))
12    descriptors = hog.compute(img)
13    logger.info('hog feature descriptor size: {}'.format(descriptors.shape))    # (3780, 1)
14    train.append(descriptors)
15train = np.float32(train)
16train = np.resize(train, (total, 3780))

1.4 SVM分类

通过使用SVM函数cv2.ml.SVM_create()建立SVM分类器,其中所使用的核函数为cv2.ml.SVM_LINEAR线性核函数。并在训练完成后保存成svm模型。 

图3 不同SVM对比效果

代码如下:

 1logger.info('Configuring SVM classifier.')
 2svm = cv2.ml.SVM_create()
 3svm.setCoef0(0.0)
 4svm.setDegree(3)
 5criteria = (cv2.TERM_CRITERIA_MAX_ITER + cv2.TERM_CRITERIA_EPS, 1000, 1e-3)
 6svm.setTermCriteria(criteria)
 7svm.setGamma(0)
 8svm.setKernel(cv2.ml.SVM_LINEAR)
 9svm.setNu(0.5)
10svm.setP(0.1)  # for EPSILON_SVR, epsilon in loss function?
11svm.setC(0.01)  # From paper, soft classifier
12svm.setType(cv2.ml.SVM_EPS_SVR)
13logger.info('Starting training svm.')
14svm.train(train, cv2.ml.ROW_SAMPLE, labels)
15logger.info('Training done.')
16pwd = os.getcwd()
17model_path = os.path.join(pwd, 'svm.xml')
18svm.save(model_path)
19logger.info('Trained SVM classifier is saved as: {}'.format(model_path))
20

1.5 模型测试

通过IP摄像头读入数据,然后利用模型检测输入的视频流。

 1hog = cv2.HOGDescriptor()
 2hog.setSVMDetector(cv2.HOGDescriptor_getDefaultPeopleDetector())
 3pwd = os.getcwd()
 4test_dir = os.path.join(pwd, 'TestData')
 5cap=cv2.VideoCapture("http://admin:admin@192.168.137.124:8081/")
 6while True:
 7    _, frame = cap.read()
 8    rects, _ = hog.detectMultiScale(frame, winStride=(4, 4), padding=(8, 8), scale=1.05)
 9    for (x, y, w, h) in rects:
10        cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 0, 255), 2)
11    cv2.imshow('Detect', frame)
12    c = cv2.waitKey(1) & 0xff
13    if c == 27:
14        break

最终达成的测试效果如下图所见: 

图4 模型测试效果图

校园卡信息提取

在得到视频检测到校园卡的位置之后,对校园卡进行图像处理操作。

操作流程如下可见:

(1)读入图片,并设定成一定尺寸

1img=cv2.imread("TestData/0.jpg")
2img=cv2.resize(img,(400,300))

(2)初始化几个结构化内核,构造了两个这样的内核 - 一个矩形和一个正方形。我们将使用矩形的一个用于Top-hat形态运算符,将方形一个用于关闭操作。

1rectKernel=cv2.getStructuringElement(cv2.MORPH_RECT,(12,12))
2sqKernel=cv2.getStructuringElement(cv2.MORPH_RECT,(5,5))

(3)将图片就行灰度化操作,然后执行Top-hat形态操作,将结果存储为 tophat,Top-hat操作显示了深色背景下的亮区。

1gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
2#执行Top-hat形态操作,将结果存储为 tophat,Top-hat操作显示了深色背景下的亮区
3tophat=cv2.morphologyEx(gray,cv2.MORPH_TOPHAT,rectKernel)

(4)计算沿x方向的渐变在计算gradX   数组中每个元素的绝对值之后 ,我们采取一些步骤将值缩放到范围[0-255](因为图像当前是浮点数据类型)。要做到这一点,我们计算 MINVAL和 MAXVAL的gradX,然后由我们的缩放方程上显示(即,最小/最大归一化)。最后一步是将gradX转换为 uint8,其范围为[0-255]。然后执行gradX 图像的Otsu和二进制阈值,然后是另一个关闭操作,对数字分段。

1gradx=cv2.Sobel(tophat,ddepth=cv2.CV_32F,dx=1,dy=0,ksize=-1)
2gradx=np.absolute(gradx)
3(minval,maxval)=(np.min(gradx),np.max(gradx))
4gradx=(255*((gradx-minval)/(maxval-minval)))
5gradx=gradx.astype("uint8")
6#执行gradX 图像的Otsu和二进制阈值,然后是另一个关闭操作,对数字分段
7gradx=cv2.morphologyEx(gradx,cv2.MORPH_CLOSE,rectKernel)

 

图5 执行关闭操作图片效果

(5)图像阈值处理,二值化操作

1thresh=cv2.threshold(gradx,0,255,cv2.THRESH_BINARY|cv2.THRESH_OTSU)[1]

图6 执行二值化操作图片效果

(6)执行膨胀操作,扩大噪音或者连接物体

1kernel=np.ones((7,7),np.uint8)
2dilate=cv2.dilate(thresh,kernel,iterations=1)

 

图7 执行膨胀操作图片效果

(7)找到轮廓并初始化数字分组位置列表。然后循环遍历轮廓,同时根据每个的宽高比进行过滤,允许我们从信用卡的其他不相关区域修剪数字组位置,其中我们需要提取的区域长宽比是大于1,去除杂项。然后从左到右对分组进行排序,并初始化信用卡数字列表。接着利用for循环依次显示和识别。其中文字识别使用的是百度接口。

 1#找到轮廓并初始化数字分组位置列表
 2cnts=cv2.findContours(thresh,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
 3cnts=imutils.grab_contours(cnts)
 4locs = []
 5#循环遍历轮廓,同时根据每个的宽高比进行过滤,允许我们从信用卡的其他不相关区域修剪数字组位置
 6for (i, c) in enumerate(cnts):
 7    (x, y, w, h) = cv2.boundingRect(c)
 8    #我们需要提取的区域长宽比是大于1,去除杂项
 9    ar = w/h
10    if ar > 1:
11        locs.append((x, y, w, h))
12#从左到右对分组进行排序,并初始化信用卡数字列表
13locs = sorted(locs, key=lambda x:x[0])
14print(locs)
15for i in locs:
16    print(i)
17    image=gray[i[1]:i[1]+i[3],i[0]:i[0]+i[2]]
18    cv2.imwrite("temp.jpg",image)
19    APP_ID = '23109663'  # 刚才获取的 ID,下同
20    API_KEY = '4rWRc7ensuq0Bf8NGs8cGuaz'
21    SECRECT_KEY = 'bWWS8ugAs2wGGx78yTUiMccpQpWt0UlY'
22    client = AipOcr(APP_ID, API_KEY, SECRECT_KEY)
23    tt = open("temp.jpg", 'rb')
24    img = tt.read()
25    message = client.basicGeneral(img)  #通用文字识别
26    print(message)
27    cv2.imshow(str(i)+"2",image)
28    cv2.waitKey(1)
29cv2.waitKey(0)

 

图8 识别提取效果图

总结与讨论

此次校园卡目标检测和图像处理信息提取的功能设计,使用的是传统的模式识别方法进行图像识别,其中涉及到的知识主要是hog特征+SVM分类,以及图片处理的一些常规操作和百度API文字识别的调用。

作者简介:李秋键,CSDN博客专家,CSDN达人课作者。硕士在读于中国矿业大学,开发有taptap竞赛获奖等。



☞美团优选、多多买菜等五家社区团购被罚650万元;打车手机越贵,接单车型越贵;微软推出低代码语言 Power Fx | 极客头条
☞你还有学了三年建模的朋友吗?他有救了
☞亚马逊力推以太坊,微软谷歌准备跟进!
☞三年白干!程序员孙某因违反《竞业协议》赔偿腾讯 97.6 万元,返还 15.8 万元
Logo

20年前,《新程序员》创刊时,我们的心愿是全面关注程序员成长,中国将拥有新一代世界级的程序员。20年后的今天,我们有了新的使命:助力中国IT技术人成长,成就一亿技术人!

更多推荐