过年去福建玩的前一天,临时搭建了一个简单的bash脚本在HTPC上跑,作用就是检测摄像头的画面是否有运动,如果有运动就发送通知到我手机上,程序工作的很理想,不受光线和天气变化干扰,准确的捕捉到了亲戚来我家开灯的每一个瞬间。
该程序的基本工作流是:
1)捕捉摄像头图片
2)比较和上一张捕捉的图片
3)如果比较发现图片有变化,则将有变化的图片发送到指定邮箱
bash本身不具有访问摄像头的能力,不过我们可以利用第三方的程序来捕捉并生成图片文件,经过我测试过几个程序,发现mplayer捕捉比较方便,既可以使用命令行来捕捉,颜色也相对正常,捕捉时间也还可以接受。mplayer捕捉摄像头并保存为png的语法:
$ mplayer -vo png -frames 2 tv://
该命令将会捕捉2帧内容并依次保存到00000001.png和00000002.png,这里捕捉两帧的原因是有时候会发现00000001.png的颜色不正常或者全黑,可能别人设备是正常的。
比较图片,最开始的想法是用OpenCV,但是bash不能直接用这个,又想到OpenCV写个程序给bash调用,考虑到当时时间不够第二天早上就要赶飞机,就又没考虑这个,后来经过研究发现可以使用imagemagick的命令行套件来做图像比较:
$ compare -fuzz 10% prev.jpg current.jpg diff.jpg
将可以比较prev.jpg和current.jpg,并将比较结果存入diff.jpg,-fuzz 10%是告诉imagemagick允许10%的模糊误差,这将有助于消除传感器和自然光微弱变化导致的噪音。
然后将图片进行色彩量化处理,降低为2色,方便做数据统计:
convert diff.jpg +dither -colors 2 -normalize diff_norm.jpg
如果两张图一样的,那么diff_norm.jpg 算出来的结果会是一个单色图,两张图的区别越大则diff_norm.jpg里不一样的地方的颜色就越多,得到这一步,做图片对比就容易了。
然后使用identity命令来对diff_norm.jpg做分析:
lexchou@lex-work:/media/nas/bak/webcam$ identify -verbose diff_norm.jpg
Image: diff_norm.jpg
Format: JPEG (Joint Photographic Experts Group JFIF format)
Class: DirectClass
Geometry: 640x480+0+0
Resolution: 72x72
Print size: 8.88889x6.66667
Units: PixelsPerInch
Type: TrueColor
Endianess: Undefined
Colorspace: sRGB
Depth: 8-bit
Channel depth:
red: 8-bit
green: 8-bit
blue: 8-bit
Channel statistics:
Red:
min: 174 (0.682353)
max: 255 (1)
mean: 245.124 (0.961272)
standard deviation: 18.3704 (0.0720409)
kurtosis: 0.143256
skewness: -1.41191
Green:
min: 164 (0.643137)
max: 255 (1)
mean: 238.432 (0.935029)
standard deviation: 30.9488 (0.121368)
kurtosis: -0.140717
skewness: -1.36023
Blue:
min: 60 (0.235294)
max: 255 (1)
mean: 219.262 (0.85985)
standard deviation: 58.6342 (0.229938)
kurtosis: -0.108736
skewness: -1.35959
Image statistics:
Overall:
min: 60 (0.235294)
max: 255 (1)
mean: 234.273 (0.918717)
standard deviation: 39.721 (0.155769)
kurtosis: 4.71448
skewness: -2.43105
Rendering intent: Perceptual
Gamma: 0.454545
Chromaticity:
red primary: (0.64,0.33)
green primary: (0.3,0.6)
blue primary: (0.15,0.06)
white point: (0.3127,0.329)
Interlace: None
Background color: white
Border color: srgb(223,223,223)
Matte color: grey74
Transparent color: black
Compose: Over
Page geometry: 640x480+0+0
Dispose: Undefined
Iterations: 0
Compression: JPEG
Quality: 92
Orientation: Undefined
Properties:
date:create: 2014-02-08T18:51:45+08:00
date:modify: 2014-02-08T18:51:45+08:00
jpeg:colorspace: 2
jpeg:sampling-factor: 2x2,1x1,1x1
signature: e2125f04982978c8938b5587cd49a5e3d5e013c34b011871774be979019d9c59
Artifacts:
filename: diff_norm.jpg
verbose: true
Tainted: False
Filesize: 32.7KB
Number pixels: 307K
Pixels per second: 30.72MB
User time: 0.000u
Elapsed time: 0:01.010
Version: ImageMagick 6.7.7-10 2013-12-22 Q16 http://www.imagemagick.org
在这里有每个色彩通道的对比,不过做色彩量化后并不能保证一定是R/G/B通道的颜色为主(试过指定色彩空间,但效果并不好),不过输出结果中也提供了Image statistics Overall,可以不用去管单个色彩通道,输出结果中的standard deviation即方差,可以用这个数值做比较。
经过我的对比,垃圾摄像头本身采集的数据误差,以及自然光的微弱变化(比如摄像头观察区域外远处人走过或者云慢慢飘过导致的环境光的微弱变化),这个standard deviation的值一般在10以内,按照10来做判断对结果比较可以接受,完整的判断函数:
cmp()
{
compare -fuzz 10% $1 $2 diff.jpg
convert diff.jpg +dither -colors 2 -normalize diff_norm.jpg
deviation=`identify -verbose diff_norm.jpg | grep 'deviation' | tail -1 | awk '{print $3}'`
[[ `echo "$deviation > 10" | bc` -eq '1' ]] && return 1
return 0
}
检测到连续两帧图不一样后,可以将变化了的图片当附件发送邮件通知:
echo "Motion detected at $DATE" |mutt -s "WARNING: Invasion detected" lex@chou.it -a $ARCHIVE
完整的代码:
#!/bin/bash
ARCHIVE=~/webcam
mkdir -p archives
while [[ 1 ]]
do
mplayer -vo png -frames 2 tv:// > /dev/null 2>&1
convert 00000002.png 00000002.jpg
DIR=`date +'%Y-%m-%d/%H/%M'`
DATE=`date`
ARCHIVE=archives/`date +'%Y-%m-%d_%H_%M_%S'`.jpg
[[ ! -d $DIR ]] && mkdir -p $DIR
FILE=$DIR/`date +'%S'`.jpg
mv 00000002.jpg $FILE
rm -f latest.jpg
ln -s $FILE latest.jpg
cmp()
{
compare -fuzz 10% $1 $2 diff.jpg
convert diff.jpg +dither -colors 2 -normalize diff_norm.jpg
deviation=`identify -verbose diff_norm.jpg | grep 'deviation' | tail -1 | awk '{print $3}'`
[[ `echo "$deviation > 10" | bc` -eq '1' ]] && return 1
return 0
}
if [[ -r $LAST_FILE ]]; then
cmp $LAST_FILE $FILE
if [[ $? == 1 ]]; then
echo "Motion detected, deviation = $deviation"
cp $FILE $ARCHIVE
rm -f latest_archive.jpg
ln -s $ARCHIVE latest_archive.jpg
PREV_FILE=$LAST_FILE
scp -C $ARCHIVE chou.it:~/public/webcam &
echo "Motion detected at $DATE, URL: http://chou.it/public/webcam/`basename $ARCHIVE`" |mutt -s "WARNING: Invasion detected" lex@chou.it -a $ARCHIVE
fi
fi
LAST_FILE=$FILE
sleep 1
done
Last modified on 2014-02-13