使用bash来实现一个简单的摄像头运动检测监控程序

过年去福建玩的前一天,临时搭建了一个简单的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