]> www.vanbest.org Git - motioneye-debian/commitdiff
initial work on mask support
authorCalin Crisan <ccrisan@gmail.com>
Wed, 6 Apr 2016 19:57:56 +0000 (22:57 +0300)
committerCalin Crisan <ccrisan@gmail.com>
Wed, 6 Apr 2016 19:57:56 +0000 (22:57 +0300)
motioneye/config.py
motioneye/static/js/main.js
motioneye/templates/main.html
motioneye/utils.py

index 97b8a9c448beac0dcfd84fc6e84853fccf460240..0b9ec4d472e8691c85a8bf3babd2758b2fc9485b 100644 (file)
@@ -57,7 +57,8 @@ _KNOWN_MOTION_OPTIONS = set([
     'quality', 'rotate', 'saturation', 'snapshot_filename', 'snapshot_interval', 'stream_auth_method', 'stream_authentication', 'stream_localhost', 'stream_maxrate',
     'stream_motion', 'stream_port', 'stream_quality', 'target_dir', 'text_changes', 'text_double', 'text_left', 'text_right', 'threshold', 'videodevice', 'width',
     'webcam_localhost', 'webcam_port', 'webcam_maxrate', 'webcam_quality', 'webcam_motion', 'ffmpeg_cap_new', 'output_normal', 'output_motion', 'jpeg_filename', 'output_all',
-    'gap', 'locate', 'netcam_url', 'netcam_userpass', 'netcam_http', 'netcam_tolerant_check', 'netcam_keepalive', 'rtsp_uses_tcp'
+    'gap', 'locate', 'netcam_url', 'netcam_userpass', 'netcam_http', 'netcam_tolerant_check', 'netcam_keepalive', 'rtsp_uses_tcp',
+    'mask_file', 'smart_mask_speed'
 ])
 
 
@@ -687,6 +688,8 @@ def motion_camera_ui_to_dict(ui, old_config=None):
         'pre_capture': int(ui['pre_capture']),
         'post_capture': int(ui['post_capture']),
         'minimum_motion_frames': int(ui['minimum_motion_frames']),
+        'smart_mask_speed': 0,
+        'mask_file': '',
         
         # working schedule
         '@working_schedule': '',
@@ -849,7 +852,18 @@ def motion_camera_ui_to_dict(ui, old_config=None):
     max_val = min(max_val, 9999999)
 
     data['ffmpeg_bps'] = int(int(ui['movie_quality']) * max_val / 100)
-    
+
+    # motion detection
+    if ui['mask']:
+        if ui['mask_type'] == 'smart':
+            data['smart_mask_speed'] = 10 - int(ui['smart_mask_slugginess'])
+
+        elif ui['mask_type'] == 'editable':
+            data['mask_file'] = utils.build_editable_mask_file(ui['editable_mask'])
+
+        else:
+            data['mask_file'] = ui['mask_file']
+
     # working schedule
     if ui['working_schedule']:
         data['@working_schedule'] = (
@@ -1027,6 +1041,11 @@ def motion_camera_dict_to_ui(data):
         'pre_capture': int(data['pre_capture']),
         'post_capture': int(data['post_capture']),
         'minimum_motion_frames': int(data['minimum_motion_frames']),
+        'mask': False,
+        'mask_type': 'smart',
+        'smart_mask_slugginess': 5,
+        'mask_file': '',
+        'editable_mask': '',
         
         # motion notifications
         'email_notifications_enabled': False,
@@ -1210,6 +1229,23 @@ def motion_camera_dict_to_ui(data):
         max_val = min(max_val, 9999999)
         
         ui['movie_quality'] = min(100, int(round(ffmpeg_bps * 100.0 / max_val)))
+        
+    # motion detection
+    if data['smart_mask_speed'] or data['mask_file']:
+        ui['mask'] = True
+        if data['smart_mask_speed']:
+            ui['mask_type'] = 'smart'
+            ui['smart_mask_slugginess'] = 10 - int(data['smart_mask_speed'])
+
+        else:
+            editable_mask = utils.parse_editable_mask_file(data['mask_file'])
+            if editable_mask:
+                ui['mask_type'] = 'editable'
+                ui['editable_mask'] = editable_mask
+
+            else:
+                ui['mask_type'] = 'file'
+                ui['mask_file'] = data['mask_file']
 
     # working schedule
     working_schedule = data['@working_schedule']
@@ -1759,6 +1795,8 @@ def _set_default_motion_camera(camera_id, data):
     data.setdefault('noise_level', 32)
     data.setdefault('lightswitch', 0)
     data.setdefault('minimum_motion_frames', 20)
+    data.setdefault('smart_mask_speed', 0)
+    data.setdefault('mask_file', '')
     
     data.setdefault('pre_capture', 1)
     data.setdefault('post_capture', 1)
index 2d7da7cfb80138c47689cb8fe9126189f811f7e8..a0ba78837682b9263b3442ff2330fa05e2dfd0f7 100644 (file)
@@ -1545,6 +1545,10 @@ function cameraUi2Dict() {
         'pre_capture': $('#preCaptureEntry').val(),
         'post_capture': $('#postCaptureEntry').val(),
         'minimum_motion_frames': $('#minimumMotionFramesEntry').val(),
+        'mask': $('#maskSwitch')[0].checked,
+        'mask_type': $('#maskTypeSelect').val(),
+        'smart_mask_slugginess': $('#smartMaskSlugginessSlider').val(),
+        'mask_file': $('#maskFileEntry').val(),
         
         /* motion notifications */
         'email_notifications_enabled': $('#emailNotificationsEnabledSwitch')[0].checked,
@@ -1882,6 +1886,10 @@ function dict2CameraUi(dict) {
     $('#preCaptureEntry').val(dict['pre_capture']); markHideIfNull('pre_capture', 'preCaptureEntry');
     $('#postCaptureEntry').val(dict['post_capture']); markHideIfNull('post_capture', 'postCaptureEntry');
     $('#minimumMotionFramesEntry').val(dict['minimum_motion_frames']); markHideIfNull('minimum_motion_frames', 'minimumMotionFramesEntry');
+    $('#maskSwitch')[0].checked = dict['mask']; markHideIfNull('mask', 'maskSwitch');
+    $('#maskTypeSelect').val(dict['mask_type']); markHideIfNull('mask_type', 'maskTypeSelect');
+    $('#smartMaskSlugginessSlider').val(dict['smart_mask_slugginess']); markHideIfNull('smart_mask_slugginess', 'smartMaskSlugginessSlider');
+    $('#maskFileEntry').val(dict['mask_file']); markHideIfNull('mask_file', 'maskFileEntry');
     
     /* motion notifications */
     $('#emailNotificationsEnabledSwitch')[0].checked = dict['email_notifications_enabled']; markHideIfNull('email_notifications_enabled', 'emailNotificationsEnabledSwitch');
index c994a8177f4a2b10688946bd2841af21838ae95a..1c00f99bdb9524cb8bcd830da62f4f48fbc63d9d 100644 (file)
                     <tr class="settings-item" min="1" max="4" snap="1" ticksnum="4" decimals="0" id="fitFramesVerticallyRow">
                         <td class="settings-item-label"><span class="settings-item-label">Fit Frames Vertically</span></td>
                         <td class="settings-item-value"><input type="checkbox" class="styled prefs" id="fitFramesVerticallySwitch"></td>
-                        <td><span class="help-mark" title="controls whether frame sizes can be reduced to vertically fit the window or not">?</span></td>
+                        <td><span class="help-mark" title="controls whether frame sizes can be reduced to fit the window vertically">?</span></td>
                     </tr>
                     <tr class="settings-item" min="0" max="100" snap="2" ticksnum="6" decimals="0">
                         <td class="settings-item-label"><span class="settings-item-label">Frame Rate Dimmer</span></td>
                         <td class="settings-item-value"><input type="text" class="number styled motion-detection camera-config" id="minimumMotionFramesEntry"><span class="settings-item-unit">frames</span></td>
                         <td><span class="help-mark" title="sets the minimum number of successive motion frames required to start a motion event">?</span></td>
                     </tr>
+                    <tr class="settings-item advanced-setting">
+                        <td colspan="100"><div class="settings-item-separator"></div></td>
+                    </tr>
+                    <tr class="settings-item advanced-setting">
+                        <td class="settings-item-label"><span class="settings-item-label">Mask</span></td>
+                        <td class="settings-item-value"><input type="checkbox" class="styled motion-detection camera-config" id="maskSwitch"></td>
+                        <td><span class="help-mark" title="enables image masking for a more selective and precise motion detection">?</span></td>
+                    </tr>
+                    <tr class="settings-item advanced-setting" depends="mask">
+                        <td class="settings-item-label"><span class="settings-item-label">Mask Type</span></td>
+                        <td class="settings-item-value">
+                            <select class="styled motion-detection camera-config" id="maskTypeSelect">
+                                <option value="smart">Smart</option>
+                                <option value="editable">Editable</option>
+                                <option value="file">File</option>
+                            </select>
+                        </td>
+                        <td><span class="help-mark" title="the smart option automatically detects regions with regular motion and builds an internal mask, the editable mask allows you to manually build it and the file option allows you to specify a mask file (not recommended)">?</span></td>
+                    </tr>
+                    <tr class="settings-item advanced-setting" min="1" max="10" snap="1" ticksnum="10" decimals="0" unit="" depends="mask maskType=smart">
+                        <td class="settings-item-label"><span class="settings-item-label">Smart Mask Slugginess</span></td>
+                        <td class="settings-item-value"><input type="text" class="range styled motion-detection camera-config" id="smartMaskSlugginessSlider"></td>
+                        <td><span class="help-mark" title="lower values result in a longer-lasting smart mask with a slower building process">?</span></td>
+                    </tr>
+                    <tr class="settings-item advanced-setting" required="true" depends="mask maskType=file" strip="true">
+                        <td class="settings-item-label"><span class="settings-item-label">Mask File</span></td>
+                        <td class="settings-item-value"><input type="text" class="styled motion-detection camera-config" id="maskFileEntry"></td>
+                        <td><span class="help-mark" title="full path to the PGM mask image file">?</span></td>
+                    </tr>
                     {% for config in camera_sections.get('motion-detection', {}).get('configs', []) %}
                         {{config_item(config)}}
                     {% endfor %}
index 212379fce344616edc0fb1960bc6d51478df7669..d3f0765f89cdd140e8355d3b58083dfa2b463984 100644 (file)
@@ -23,10 +23,13 @@ import logging
 import os
 import re
 import socket
+import struct
 import time
 import urllib
 import urlparse
 
+from PIL import Image, ImageDraw
+
 from tornado.httpclient import AsyncHTTPClient, HTTPRequest
 from tornado.iostream import IOStream
 from tornado.ioloop import IOLoop
@@ -714,3 +717,50 @@ def build_digest_header(method, url, username, password, state):
     state['nonce_count'] = nonce_count
 
     return 'Digest %s' % (base)
+
+
+def build_editable_mask_file(editable_mask):
+    width = editable_mask[0]
+    height = editable_mask[1]
+    nx = editable_mask[2]
+    ny = editable_mask[3]
+    lines = editable_mask[4:]
+    
+    data = struct.pack('<HHBB', width, height, nx, ny)
+    for line in lines:
+        data += struct.pack('<I', line)
+    
+    name = base64.b64encode(data, '-_')
+    
+    # draw the actual mask image content
+    im = Image.new('L', (width, height), 255) # all white
+    dr = ImageDraw.Draw(im)
+
+    rw = width / nx
+    rh = height / ny
+
+    for y in xrange(ny):
+        line = lines[y]
+        for x in xrange(nx):
+            if line & (31 - x):
+                print line & (31 - x)
+                dr.rectangle((x * rw, y * rh, (x + 1) * rw, (y + 1) * rh), fill=0)
+
+    file_name = os.path.join(settings.CONF_PATH, name) + '.pgm'
+    im.save(file_name, 'ppm')
+
+    return name
+
+
+def parse_editable_mask_file(file_name):
+    name = os.path.splitext(os.path.basename(file_name))[0]
+    try:
+        data = base64.b64decode(name, '-_')
+        width, height, nx, ny = struct.unpack('<HHBB', data[:6])
+        fmt = '<' + 'I' * ny
+        lines = struct.unpack(fmt, data[6:])
+        
+        return [width, height, nx, ny] + list(lines)
+
+    except Exception:
+        return None