目录
一、简介:
二、软硬件应用:
三、模块介绍:
(一)人脸识别:
(二)、自定义物体识别:
(三)、图片展示:
(四)、python框架:
(五)、执行器--单片机:
四、效果展示:
五、总结 :
现如今机器视觉的应用可谓是遍地开花,人脸识别更可谓是随处可见。opencv作为视觉库被普遍应用,此项目正是基于opencv设计的一个既可以识别人脸也可以识别自定义物体的系统。
pycharm(python版本3.10)
opencv-contrib-python()
opencv3.4.8(这个自带训练分类器的exe--opencv_createsamples.exe,opencv_traincascade.exe)
kile5
正点原子STM32F103mini
pc(在我的笔记本进行项目)
摄像头(我的笔记本上没有内置摄像头,所以用了一个外置摄像头)
这个项目中我分别体验了face_recognition和opencv自带的Haar特征分类器。
对于face_recognition:单个人的时候face_recognition准确度很可靠,但是有一个明显的缺点就是太慢(可能是我代码的问题),检测的帧率只有3次/秒。如果检测多个人,并且两个人很相似时准确度就有点说不过去了(血和泪的经历,项目写到一半发现实在太慢,才决定狼狈地重写人脸识别代码)。
对于opencv自带的Haar特征分类器:准确度虽不如face_recognition但还可以,他的速度很快,我的摄像头时30帧的,它检测的帧率可以达到20次/秒(我已经感到满足了)
人脸识别部分使用了opencv自带的Haar特征分类器--haarcascade_frontalface_alt2.xml ,相较于其他几个,这个我感觉很好。下面是我的代码,
check_time=0
def face_Detector(img,ser):
gary=cv.cvtColor(img,cv.COLOR_BGR2GRAY)
face_detect=cv.CascadeClassifier('C:/opencv/opencv/sources/data/haarcascades/haarcascade_frontalface_alt2.xml')#haarcascade_frontalface_default.xml
face=face_detect.detectMultiScale(gary,1.1,5,cv.CASCADE_SCALE_IMAGE,(100,100),(300,300))
for x,y,w,h in face:
cv.rectangle(img,(x,y),(x+w,y+h),color=(0,0,255),thickness=2)
ids,confidence = recognizer.predict(gary[y:y+h,x:x+w])
if confidence<80:
global check_time
check_time += 1
if check_time > 36:
check_time = 0
# save人员信息
save(name[ids-1])
ser.write('1'.encode())
cv.putText(img, name[ids-1]+str(confidence), (x + 10, y - 10), cv.FONT_HERSHEY_COMPLEX, 0.75, (0, 255, 0), 1)
else:
ser.write('0'.encode())
cv.putText(img, 'Unknown' , (x + 10, y - 10), cv.FONT_HERSHEY_COMPLEX, 0.75, (0, 255, 0),1)
这个过程中遇到了pycharm中opencv不提示代码的问题。
整了半天,才在这个大佬的文章中找到解决方案,http://t.csdn.cn/sQdN3
在这个模块中我使用了自己训练的分类器
这是我的正样本 :
这是我的负样本 :
这是我最后的成果cascade.xml文件,也就是可以用的分类器:
自己训练分类器时,我用opencv4.5.3版本,借鉴网上网上大佬发的文章自己实践,结果发现只有opencv_createsamples.exe可以勉强编译出来,但opencv_traincascade.exe确实编译不出来,为此我不得不又下载了opencv3.4.8的版本,使用其自带的那两个可执行程序进行训练。
我使用了240张正样本,720张负样本,(正:负)==(1:3)(负样本一定要比正样本多,正负样本数量比例可以在1:3到1:5之间)。虽说样本数量不是很多,但效果还能接受。
下面是我的正样本描述文件,说一下文件里面1 0 0 40 40 后缀的含义。
我的正样本图片统一处理成了40*40,所以这里的1是指正样本图片中只有一个自定义的物体, 0 0 40 40的意思是覆盖整张图片
网上相关自己训练分类器的文章很多,具体的实现过程我也不在此过多地赘述了。
分类器训练完了之后,就可以直接使用了,下面展示我的代码:
def object_Detector(image):
gray = cv.cvtColor(image,cv.COLOR_BGR2GRAY)
bottle_cascade = cv.CascadeClassifier('C:/opencv/Object/xml/cascade.xml')
bottle = bottle_cascade.detectMultiScale(gray,1.1,5,cv.CASCADE_SCALE_IMAGE,(50,50),(300,300))
num = len(bottle)
for x,y,w,h in bottle :
cv.circle(image,center=(x+w//2,y+h//2),radius=w//2,color=(0,255,0),thickness=1)
cv.putText(image,str(num),(20,20),cv.FONT_HERSHEY_COMPLEX, 0.75, (0, 0, 255),1)
由于项目要求,我的项目中opencv处理后的图片需要再我的单片机上显示,但这方面有点力不从心,所以我使用了socket实现opencv处理后的图片从客户端上传用服务端接受并展示。
这是客户端代码,从opencv中不断获取处理后的图片,进行编码、发送
#从客户端向服务端发送处理后的re_img图片
def SendVideo():
global re_img, ret, state
# IP地址和端口号
IP = '127.0.0.1'
PORT = 8002
try:
# 实例化:AF_INET:ip协议,SOCK_STREAM:tcp协议
dataSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 开始连接
dataSocket.connect((IP, PORT))
# 错误处理
except socket.error as msg:
print(msg)
sys.exit(1)
# 压缩参数,对于jpeg来说,15代表图像质量,越高代表图像质量越好为 0-100,默认95
encod_param = [int(cv.IMWRITE_JPEG_QUALITY), 15]
while ret:
# 停止0.05秒,防止发送过快服务的处理不过来
time.sleep(0.05)
# 将图片格式编码成流数据,方便网络传输
# result:True/False
# imgencode:numpy.ndarray类型的图像文件内容
result, imgencode = cv.imencode('.jpg', re_img,encod_param)
# 建立矩阵
data = np.array(imgencode)
# 将numpy矩阵转换成字符形式,以便在网络中传输
stringData = data.tobytes()
# ljust() 方法返回一个原字符串左对齐,并使用空格填充至指定长度的新字符串
dataSocket.send(str.encode(str(len(stringData)).ljust(16)))
# 发送数据
dataSocket.send(stringData)
if state == True:
break
dataSock.close()
这是服务端代码,不断从客户端里获取流数据,再进行一系列的转化、解码,最后展示到笔记本上
def ReceiveVideo():
global state
# ip地址'0.0.0.0'绑定本机所有IP地址
IP = '0.0.0.0'
# 端口号
PORT = 8002
# 实例化
listenSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 进行IP和端口号的绑定
# 元组方式写入
# 可以啊address=('0.0.0.0',50000)
listenSocket.bind((IP, PORT))
# 最多接受1个客户端
listenSocket.listen(1)
# 消息边界
def recallbyt(sock, cnt):
byt = b'' # byt是一个byte类型,用于接受流数据。(传输的数据就是byte类型的)
while cnt:
newbyt = sock.recv(cnt)
if not newbyt: return None
byt += newbyt
cnt -= len(newbyt)
return byt
dataSocket, addr = listenSocket.accept()
print('连接到:' + str(addr))
while True:
# 获得图片文件长度,16代表获取长度
length = recallbyt(dataSocket, 16)
# 根据获取的文件长度,获取图片文件
StringData = recallbyt(dataSocket, int(length))
# 将流数据转换成numpy 1维数组数据
data = np.frombuffer(StringData, np.uint8)
# 将数组数据进行解码,形成图像
deco_img = cv.imdecode(data, cv.IMREAD_COLOR)
cv.imshow('SERVER', deco_img)
# 按键检测
if cv.waitKey(1) == 81:
dataSocket.close()
state = True
listenSocket.close()
cv.destroyAllWindows()
break
整体采用了多线程的运行方式,并且在最开始通过一个判断来选择进行人脸识别模式还是自定义模块识别模式
thread_Rec = threading.Thread(target=ReceiveVideo)
thread_Sen = threading.Thread(target=SendVideo)
thread_take = threading.Thread(target=teak_img)
'''*************************模式判断*************************'''
mode = input('请选择模式:')
if mode == '1':
thread_face = threading.Thread(target=face_det)
thread_face.start()
elif mode == '2':
thread_obj = threading.Thread(target=object_det)
thread_obj.start()
'''**************************************************'''
thread_take.start()
thread_Rec.start()
thread_Sen.start()
因为想在一个python程序上就集成两个模式,对多线程的学习还没有很深入所以不得已做成这样的判断,来识别需要运行的模式
项目中,单片机通过串口与python程序相通,进行判断是否是数据库中的人,从而进行显示通过或失败。
void USART2_IRQHandler(void)
{
u8 i,ret;
if (USART_GetITStatus (USART2,USART_IT_RXNE ))
{
ret = USART_ReceiveData(USART2);
if (ret == '1' && (sate == 'N' || sate == 'E'))
{
LCD_Clear (WHITE);
POINT_COLOR=GREEN;
for(i=0;i<8;i++){
LCD_Draw_Circle( 120, 160, 102-i);}
LCD_Fill( 110, 120 , 130,220 , GREEN);
for (i=0;i<25;i++){
LCD_DrawLine(80+i, 160,120, 110+i);}
for (i=0;i<25;i++){
LCD_DrawLine(160-i, 160,120, 110+i);}
POINT_COLOR=BLACK;
LCD_ShowString (65,20,200,16,16,"Knwon Pass");
LCD_ShowString(80,280,200,16,16,"2022/7/24");
sate = 'Y';
}
else if (ret == '0' && (sate == 'Y' || sate == 'E'))
{
LCD_Clear (WHITE);
POINT_COLOR=RED;
for(i=0;i<8;i++){
LCD_Draw_Circle( 120, 160, 102-i);}
for (i=0;i<25;i++){
LCD_DrawLine(70+i, 210,146+i, 110);}
for (i=0;i<25;i++){
LCD_DrawLine(70+i, 110,146+i, 210);}
LCD_ShowString (65,20,200,16,16,"Unknown fail");
LCD_ShowString(80,280,200,16,16,"2022/7/24");
sate = 'N';
}
}
}
LCD上其实还有一些字 ,只是亮度太高,看不见了
这次的项目由于开始后的前两天一直在整face_recognition的代码到后来转到opencv自带的Haar特征分类器时有点“狼狈”,时间很紧张,来不及做其他的执行器模块。
这个项目对我来说最难的是画面展示的部分,因为电脑和单片机之间的通信我不太熟悉,问了一下我的师兄其他方法,随后了解到flask可以将视频传输到网页上,但是整了两天也没整会如何将网页上的视频流信息捕捉回来,到了结束的前一天才了解到socket可以进行服务端和客户端的信息传输,就赶紧听课、学习、写代码。