更新视频演示~~室内搜台数量有限,依然无语中。。。。。。还是河北音乐广播给力啊!超稳定的输出!!
首先,这次的主角,摄像头(路边摊¥25) ,不知道什么牌子的。。。
Plug~~用lsusb指令查看一下是否能识别,嘿,看来有门儿~~
估计应该属于标准的UVC协议,目前的Raspbian是集成了这部分驱动的,输入ls /dev,查看dev中是否存在一个video0这个设备,如果有了说明系统识别了,如果不能识别,可以试试更新一下系统
安装一个camorama的小工具,可以查看,修改和保存摄像头捕获的图片
sudo apt-get install camorama
然后在远程桌面的开始菜单中找到这个工具
320*240的尺寸,平均每秒11帧,没有期望太高。。。
还试了一下mplayer和camorama,640*480的尺寸,卡得不行,平均每秒3帧,丢帧很严重!
不过摄像头是可以用了,下面安装opencv
sudo apt-get update sudo apt-get install libopencv-dev sudo apt-get install python-opencv
利用opencv写一段简单的代码,获取当前摄像头的图像,实时显示出来
# coding=utf-8 import cv2 cv2.namedWindow("video") # 命名一个窗口 capture = cv2.VideoCapture(0) # 打开默认摄像头 success, frame = capture.read() # 读取一帧图像,第一个返回值是成功标志,第二个返回值是图像本身 while True: success, frame = capture.read() cv2.imshow("video", frame) # 显示图像到窗口 key = cv2.waitKey(10) # 利用按键退出 c = chr(key & 255) if c in ['q', 'Q', chr(27)]: break cv2.destroyWindow("video") # 停止窗口
160*120的尺寸,效果还是可以接受的,至少没什么卡顿现象了,CPU也只占了80%左右
上传视频,大家看看效果~~
【实验番外篇】任务三:人脸识别~~续 - PYTHON OPENCV2
人脸识别,咋一听上去很高深的样子,其实通过opencv中的一些模块和强大的Haarcascades Repositories实现起来还是很简单的!
这里用的是CV2.X版本,因为CV1.X版本中摄像头获取的帧图像默认是iplimage格式的,在进行人脸识别的时候需要转成numpy array格式,同时识别完成后还需要转换回去才能将结果显示出来,而CV2.X版本则默认就是numpy array格式的图像,应用起来更加快速、方便一些。
1、调用OpenCV2.X及numpy
import cv2 import numpy as np
2、这里我们进行一些初始化的工作,建立一个窗口用于显示图像,打开默认的摄像头,读取一帧图像并取得读取成功标志位,设置人脸框颜色,以及设置分类器的haar特征数据库
tips:cv2.VideoCapture() - 当该函数的第一个参数为数字时表示摄像头摄像头id,当只有一个摄像头时,传入0,OpenCV会打开该摄像头。该功能仅限笔记本、USB摄像头,对于工业的网络摄像头(如海康摄像头、AXIS摄像头等)需要针对SDK进行相应开发
cv2.namedWindow("video") # 命名一个窗口 capture = cv2.VideoCapture(0) # 打开默认摄像头 success, frame = capture.read() # 读取一帧图像,第一个返回值是成功标志,第二个返回值是图像本身 color = (255, 255, 255) # 设置人脸框的颜色 classfier = cv2.CascadeClassifier("/usr/share/opencv/haarcascades/" "haarcascade_frontalface_alt_tree.xml") # 定义分类器:正脸
官方同时还提供了许多特征库供我们使用
而且我们还可以到 http://opencvlibrary.svn.sourceforge.net/viewvc/opencvlibrary/trunk/opencv/data/haarcascades/ 去下载其他的特征库
3、如果图像获取成功,则循环执行人脸识别,并对每一个成功识别的人脸画出矩形框,将最终结果显示到之前建立好的窗口中
# 图像获取成功,循环执行 while success: success, frame = capture.read() size = frame.shape[:2] # 获取当前帧彩色图像的大小 image = np.zeros(size, dtype=np.float16) # 定义一个与当前帧图像大小相同的的灰度图像矩阵 image = cv2.cvtColor(frame, cv2.cv.CV_BGR2GRAY) # 将当前帧图像转换成灰度图像 cv2.equalizeHist(image, image) # 灰度图像进行直方图等距化 # 设定最小人脸图像的大小,1/8 divisor = 8 h, w = size minSize = (w/divisor, h/divisor) faceRects = classfier.detectMultiScale(image, 1.2, 2, cv2.CASCADE_SCALE_IMAGE, minSize) # 人脸检测 if len(faceRects) > 0: # 如果人脸数组长度大于0 for faceRect in faceRects: # 对每一个人脸画矩形框 x, y, w, h = faceRect cv2.rectangle(frame, (x, y), (x+w, y+h), color) cv2.imshow("video", frame) # 显示图像到窗口
并不是每次都能成功定位到人脸的,这个跟特征数据以及训练的参数有关,也跟测试时候参数的设置、环境光线强度等有关。例如,采用了正脸的特征库,一般侧脸就很难识别出来;同时,如果面部特征不全或有遮挡的话,也很难进行识别
4、通过指定的按键退出循环,并停止窗口响应,退出程序
while success: ...... key = cv2.waitKey(10) # 等待按键退出 c = chr(key & 255) if c in ['q', 'Q', chr(27)]: break cv2.destroyWindow("video") # 停止窗口
tips:cv.waitKey() - 该函数等待用户键盘事件,参数为等待时长,单位是ms。注意,窗体显示必须调用一下该函数,否则窗口中无画面,当参数为0时,窗口将无限等待,该函数有返回值,没接受到键盘事件时的返回值为-1
上传完整代码:
# coding=utf-8 import cv2 import numpy as np cv2.namedWindow("video") # 命名一个窗口 capture = cv2.VideoCapture(0) # 打开默认摄像头 success, frame = capture.read() # 读取一帧图像,第一个返回值是成功标志,第二个返回值是图像本身 color = (255, 255, 255) # 设置人脸框的颜色 classfier = cv2.CascadeClassifier("/usr/share/opencv/haarcascades/" "haarcascade_frontalface_alt_tree.xml") # 定义分类器:正脸 # 图像获取成功,循环执行 while success: success, frame = capture.read() size = frame.shape[:2] # 获取当前帧彩色图像的大小 image = np.zeros(size, dtype=np.float16) # 定义一个与当前帧图像大小相同的的灰度图像矩阵 image = cv2.cvtColor(frame, cv2.cv.CV_BGR2GRAY) # 将当前帧图像转换成灰度图像 cv2.equalizeHist(image, image) # 灰度图像进行直方图等距化 # 设定最小图像的大小,1/8 divisor = 8 h, w = size minSize = (w/divisor, h/divisor) faceRects = classfier.detectMultiScale(image, 1.2, 2, cv2.CASCADE_SCALE_IMAGE, minSize) # 人脸检测 if len(faceRects) > 0: # 如果人脸数组长度大于0 for faceRect in faceRects: # 对每一个人脸画矩形框 x, y, w, h = faceRect cv2.rectangle(frame, (x, y), (x+w, y+h), color) cv2.imshow("video", frame) # 显示图像到窗口 key = cv2.waitKey(10) # 等待按键退出 c = chr(key & 255) if c in ['q', 'Q', chr(27)]: break cv2.destroyWindow("video") # 停止窗口
上传视频,大家看看效果~~晚上录的,光线强度差点儿,找了一张高中时候的毕业照,人还算比较多的,识别效果还可以接受。。。¥25的摄像头CMOS实在不敢恭维
tips:
如果程序执行过程中出现:“Xlib: extension "RANDR" missing on display xxx”的提示信息。一般来说,这个信息是可以忽略的,但是,由于RANDR扩展的不存在,会导致在使用的过程中无法动态改变屏幕的大小。
RandR的wikipedia中说道:“The X Resize, Rotate and Reflect Extension (RandR)[2] allows clients to dynamically change X screens, so as to resize, rotate and reflect the root window of a screen.”
【实验番外篇】任务四:基于树莓派的网络考勤系统及刷卡门禁 - 第一阶段
背景:公司的打卡机目前还是比较低端的,虽然是指纹识别的,但是每月统计打卡或临时查看记录的时候都要摘下来连接到电脑上,非常的不方便,而且刷卡门禁与考勤系统是分开的两套系统,又不方便统一管理。
虽然手中没有指纹识别模组,但是我们有物美价廉的RFID模块啊~~抱着把手中的RFID模块和树莓派充分利用起来以及Impossible is nothing的信念,决定自己做一套基于RFID模块和树莓派的网络版考勤系统,同时将刷卡门禁功能集成进来,方便统一管理。
任务阶段划分:
第一阶段:驱动RFID模块(RC522)
第二阶段:搭建RPi网络服务器
第三阶段:完善刷卡门禁功能及终端显示功能
第四阶段:搭建后台管理系统
第五阶段:完善后台管理系统与硬件接口的衔接
第六阶段:软、硬件功能系统测试
~~本次任务不同于之前几次(任务做完,过来发帖),由于内容比较多,所以需要分阶段完成~~
第一阶段:驱动RFID模块(RC522)
工具:RFID模块 -- RC522(通讯接口:SPI)
非接触式IC卡 -- Mifare One卡
1、启用SPI接口
从黑名单中将SPI接口释放出来,之前在I2C实验中做过类似的操作
sudo nano /etc/modprobe.d/raspi-blacklist.conf # blacklist spi and i2c by default (many users don't need them) #blacklist spi-bcm2708 #blacklist i2c-bcm2708
确认内核支持
pi@raspberrypi ~ $ ls -la /dev/spi* crw-rw---T 1 root spi 153, 0 Jan 1 1970 /dev/spidev0.0 crw-rw---T 1 root spi 153, 1 Jan 1 1970 /dev/spidev0.1
2、这次使用的编程语言是比较熟悉的C,安装wiringPi,方便调用SPI接口
使用GIT工具下载wiringPi,如果你的RPi还没有GIT,可以使用下面的指令获取
sudo apt-get install git-core通过GIT获得wiringPi的源代码
git clone git://git.drogon.net/wiringPi
进入wiringPi目录并安装wiringPi
cd wiringPi ./build测试wiringPi是否安装成功
gpio -v gpio readall
注意:需要用下面的指令将SPI驱动程序加载到内核
gpio load spi3、接线方式
4、驱动编写
RC522 Pin Name
RPi Header Pin Num
wiringPi Pin Num
VCC
#17(3.3v)
RST
#12(GPIO.1)
1
GND
#25(0v)
MISO
#21(MISO)
13
MOSI
#19(MOSI)
12
SCK(CLK)
#23(SCLK)
14
SDA(NSS)
#24(CE0)
10
(1)首先,需要初始化SPI接口,并了解其函数的使用方法,下面是一些比较重要的设置与代码举例:
#include <wiringPi.h>
#include <wiringPiSPI.h>
#define RST 1 #define channel 0 #define speed 1000000 unsigned char ucAddr; unsigned char data[2] = {0, 0}; wiringPiSetup(); //wiringPi初始化 wiringPiSPISetup(channel, speed); //设置SPI通道 0,速率 1000000 - 1Mbit/s wiringPiSPIDataRW(channel, &ucAddr, 1); wiringPiSPIDataRW(channel, data, 2);
我将RC522的片选(NSS)接到了CE0上,所以这里要设置为通道0,从RC522的DATASHEET中我们可以了解到,模块的SPI 接口可处理高达10Mbit/s的数据速率,这里我仅使用了1Mbit/s的数据速率,已经足够我的需求了。
根据官网的介绍RPi的SPI接口是全双工的工作方式,通过wiringPiSPI接口进行数据读写的时候,SPI总线会自动将返回的数据覆盖到之前发送的变量上。
"This performs a simultaneous write/read transaction over the selected SPI bus. Data that was in your buffer is overwritten by data returned from the SPI bus."
(2)解决通讯基础函数
这里其实遇到了一些问题,导致很多次实验没有成功。首先,我们来看一下DATASHEET中的介绍。
地址字节格式:
读数据字节顺序:
写数据字节顺序:
从中我们可以发现读写的命令是包含在地址字节格式中的,而地址是通过主机发送出去的字节0。
读数据过程中字节0是没有真正返回寄存器的实际值的,而真正返回是从下一个字节开始的,这里我们直接发送0x00地址,获取真实数据。
另外,我在调试的过程中,在运行到if(!(ReadRawRC(ErrorReg)&0x1B))这句时,读ErrorReg寄存器返回值是0x45,然后直接跳转到MI_ERR,正是由于读取寄存器方式错误引起的。
///////////////////////////////////////////////////////////////////// //功 能:读RC522寄存器 //参数说明:Address[IN]:寄存器地址 //返 回:读出的值 ///////////////////////////////////////////////////////////////////// unsigned char ReadRawRC(unsigned char Address) { unsigned char ucAddr; unsigned char data = 0x00; ucAddr = ((Address<<1)&0x7E)|0x80; //address format:1XXXXXX0 wiringPiSPIDataRW(channel, &ucAddr, 1); wiringPiSPIDataRW(channel, &data, 1); //读两次才能拿到真正的RC522的返回值,非常重要 return data; } ///////////////////////////////////////////////////////////////////// //功 能:写RC522寄存器 //参数说明:Address[IN]:寄存器地址 // value[IN]:写入的值 ///////////////////////////////////////////////////////////////////// void WriteRawRC(unsigned char Address, unsigned char value) { unsigned char ucAddr; unsigned char data[2] = {0, 0}; ucAddr = ((Address<<1)&0x7E); //address format:0XXXXXX0 data[0] = ucAddr; data[1] = value; wiringPiSPIDataRW(channel, data, 2); }
当我们建立起通讯基础函数后,就可以放心的根据程序流程开始编写功能函数了。
上传完整代码:
https://github.com/chronosMaker/RPiRFID.git
看一下实际运行效果,试了一张MF1 S50的空白卡,读写都正常
下面是刚才读的扇区0的具体信息
Mifare 1 卡内部有 1 个 EEPROM,分成 0~15 共 16 个扇区,每个扇区分成 0~3 共 4 块,每块 16 字节。
1、扇区 0 的块 0 是厂商标志字节,保存着只读的卡信息及厂商信息。
22 8B BE B5 A2 08 04 00 69 73 73 69 35 36 34 30
前面四个字节 22 8B BE B5 是卡序列号,08 是卡容量,04 00 是卡类型,后面是厂商自定义的一些信息。
2、每个扇区的块 0 保存着该块的密钥 A 与密钥 B 及该块的访问条件,每个扇区都有自己的一套密钥及访问条件,其中,4 个字节的访问条件是对每个扇区 4 个块的读写定义。比如块 3 的 16 字节如下:
00 00 00 00 00 00 FF 07 80 69 FF FF FF FF FF FF
前面 6 个字节是密钥 A,因为 Read 永远为 Never,所以读到的都是 0x00,最后的 6 字节是密钥 B,其值为 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF,中间的 4 个字节是访问条件。对应DATASHEET相关表格,可得出对该扇区块的存取控制条件。
试了一张北京地铁车票
又试了一下公司的门禁卡
以上两张卡都由于密钥错误导致无法读取内部数据,如果能够获取对应密钥,那么。。。。。。^_^
tips:原装的 Philps S50 芯片在出厂时设置每个分区的的第四块 A 密钥是“FFFFFFFFFFFF”,控制字是:“FF078069”,B 密钥是:“FFFFFFFFFFFF”,A 密钥是供用户读写操作的,利用 A 密钥可对对除 0 区外其它所有扇区块进行读写操作。 B 密钥不可操作,这些用的都是逻辑加密算法加密,而且密钥都是不可见的,我们在读的时候能看到的 A 密钥都是显示为“000000000000”,B 密钥显示:“FFFFFFFFFFFF”,这些都是出厂时厂家设定的默认值。
回复
有奖活动 | |
---|---|
【有奖活动——B站互动赢积分】活动开启啦! | |
【有奖活动】分享技术经验,兑换京东卡 | |
话不多说,快进群! | |
请大声喊出:我要开发板! | |
【有奖活动】EEPW网站征稿正在进行时,欢迎踊跃投稿啦 | |
奖!发布技术笔记,技术评测贴换取您心仪的礼品 | |
打赏了!打赏了!打赏了! |