Archive for January, 2009

Webcam viewer

Saturday, January 3rd, 2009

Basic webcam viewer written in python + gtk + gstreamer.
I spent time making the gst pipe to work properly because I was finding a way to both display the stream on the screen and analyze it.
The latter would be interesting because of the use one can do with it in terms of patter recognition or motion tracking (maybe one day ..).
Anyway here is the resulting gst-pipe even tought only the first branch is implemented in the script below:

v4l2sr
|
ccaps(image/jpeg)
|
videoflip
|
videobalance
|
tee-------------+
|               |
queue1          ffmpegcolorspace
|               |
xvimagesink     caps(video/xraw-rgb)
                |
                fakesink

Implemented pipe:

v4l2sr ---> ccaps(image/jpeg) ---> videoflip ---> videobalance ---> xvimagesink

The script contains few hardcoded settings such as device number, width .. You can manipulate these during application startup:
let’s suppose i want to display /dev/video2 with a resolution of 640×480 pixels and a framerate of 15fps:

$ python webcam.py 2 640 480 15

Attention: the script doesn’t check whether the device exists or it supports the specified settings.
Here is the code:

#!/usr/bin/env python

import sys
import gtk
import gst

# Webcam settings
DEVICE = 0
WIDTH = 320
HEIGHT = 240
FRAMERATE = 15

# Widget params
VIDEO_PROPERTIES = [("contrast", 0, 2, 1),
                ("brightness", -1, 1, 0),
                ("hue", -1, 1, 0),
                ("saturation", 0, 2, 1)]

class Webcam:
        def __init__(self):
                # Gui init
                window = gtk.Window(gtk.WINDOW_TOPLEVEL)
                window.connect("destroy", self.destroy_cb)
                darea = gtk.DrawingArea()
                darea.set_size_request(WIDTH, HEIGHT)
                controls = gtk.VBox()
                labels = gtk.VBox()
                for prop, lower, upper, default in VIDEO_PROPERTIES:
                        adj = gtk.Adjustment(default, lower, upper)
                        widget = gtk.HScale(adj)
                        label = gtk.Label(prop)
                        widget.connect("value-changed", self.value_changed_cb, prop)
                        controls.pack_start(widget, True, True)
                        labels.pack_start(label, True, False)
                configs = gtk.HBox()
                configs.pack_start(labels, False, False)
                configs.pack_end(controls, True, True)
                layout = gtk.VBox()
                layout.pack_start(darea)
                layout.pack_end(configs, False, False)
                window.add(layout)

                # Pipeline configuration
                self.pipeline = gst.Pipeline()
                source = gst.element_factory_make("v4l2src")
                source.set_property('device', "/dev/video%d" % DEVICE)
                decoder = gst.element_factory_make("jpegdec")
                flip = gst.element_factory_make("videoflip")
                flip.set_property('method', 'horizontal-flip')
                self.balance = gst.element_factory_make("videobalance")
                videosink = gst.element_factory_make("xvimagesink")
                videosink.set_property('force-aspect-ratio', True)
                self.pipeline.add(source, decoder, flip, self.balance, videosink)
                window.show_all()

                # Pipeline blocks linking
                caps = "image/jpeg,width=%d,height=%d,framerate=%d/1" %
                                (WIDTH, HEIGHT, FRAMERATE)
                source.link(decoder, gst.caps_from_string(caps))
                decoder.link(flip)
                flip.link(self.balance)
                self.balance.link(videosink)

                # Callback for display the webcam over the darea
                bus = self.pipeline.get_bus()
                bus.add_signal_watch()
                bus.enable_sync_message_emission()
                bus.connect('sync-message', self.sync_message_cb, darea)

                # Start the flow
                self.pipeline.set_state(gst.STATE_PLAYING)

        def destroy_cb(self, widget):
                self.pipeline.set_state(gst.STATE_NULL)
                gtk.main_quit()

        def value_changed_cb(self, widget, prop):
                self.balance.set_property(prop, widget.get_value())

        def sync_message_cb(self, bus, message, darea):
                if message.structure is None:
                        return
                message_name = message.structure.get_name()
                if message_name == 'prepare-xwindow-id':
                        # Assign the viewport
                        imagesink = message.src
                        imagesink.set_xwindow_id(darea.window.xid)

if __name__ == '__main__':
        i = 1

        for arg in sys.argv[1:]:
                if i == 1: DEVICE = int(arg)
                elif i == 2: WIDTH = int(arg)
                elif i == 3: HEIGHT = int(arg)
                elif i == 4: FRAMERATE = int(arg)
                else: break
                i += 1
        Webcam()
        gtk.main()

Rotating cube

Saturday, January 3rd, 2009

Simulation of a rotating cube using python + GTk.
Moving the mouse while the button1 is pressed will produce a rotation of the cube along XZ, and YX axis. Otherwise you will shift the point of view on the XY plane. Still looking for a more efficient way of drawing on the screen.

#!/usr/bin/env python

import gtk
import gobject
from math import pi, cos, sin

# perspective
ZOOM = 100.0
FOCUS = 100.0

# misc
FPS=60
DEPTH = 16
PALETTE = {
        'black': (0, 0, 0),
        'blue': (0, 0, 65535),
        'green': (0, 65535, 0),
        'cyan': (0, 65535, 65535),
        'red': (65335, 0, 0),
        'magenta': (65535, 0, 65535),
        'yellow': (65535, 65535, 0),
        'white': (65535, 65535, 65535),
}
ZSHIFT = 3
ZMOD = 200/DEPTH

class Scene():
        def __init__(self):
                window = gtk.Window()
                window.set_size_request(640, 480)
                window.add_events(gtk.gdk.BUTTON_PRESS_MASK
                                | gtk.gdk.POINTER_MOTION_MASK
                                | gtk.gdk.POINTER_MOTION_HINT_MASK)
                window.connect('delete_event', self.delete_event_cb)
                window.connect('button_press_event', self.button_press_event)
                window.connect('motion_notify_event', self.motion_notify_event)
                self.darea = gtk.DrawingArea()
                self.darea.connect('configure_event', self.configure_event)
                self.darea.connect('expose_event', self.expose_event)
                window.add(self.darea)
                self.darea.show()
                window.show()
                self.refresh = 0
                self.refresh = gobject.timeout_add(1000/FPS, self.draw_scene)

        def delete_event_cb(self, widget, event):
                if self.refresh != 0:
                        gobject.source_remove(self.refresh)
                        self.refresh = 0
                gtk.main_quit()
        def button_press_event(self, widget, event):
                if event.button == 1:
                        x, y, state = event.window.get_pointer()
                        self.oldx, self.oldy = self.convert_dev_to_user(x, y)
                return True

        def motion_notify_event(self, widget, event):
                if event.is_hint:
                        x, y, state = event.window.get_pointer()
                else:
                        x = event.x
                        y = event.y
                        state = event.state
                # the origin is in the middle of the windonw
                x, y = self.convert_dev_to_user(x, y)
                if state & gtk.gdk.BUTTON1_MASK:
                        angle_xz = angle_yz = 0
                        if x > self.oldx:
                                angle_xz = .05
                        elif x < self.oldx:
                                angle_xz = -.05
                        if y > self.oldy:
                                angle_yz = .05
                        elif y < self.oldy:
                                angle_yz = -.05
                        self.cube.rotate(angle_xz, angle_yz)
                        self.oldx = x
                        self.oldy = y
                else:
                        self.vx = x
                        self.vy = y
                return True

        def configure_event(self, widget, event):
                x, y, self.width, self.height = widget.get_allocation()
                self.cx = self.width/2
                self.cy = self.height/2
                self.unit = min(self.cx, self.cy)/2
                self.vx = self.vy = 0
                self.oldx = self.oldy = 0
                self.pixmap = gtk.gdk.Pixmap(widget.window, self.width,
                                self.height)
                self.cube = Cube(self)
                self.draw_scene()
                return True

        def expose_event(self, widget, event):
                x , y, width, height = event.area
                self.darea_realize(x, y, width, height)
                return False

        def darea_realize(self, x, y, width, height):
                self.darea.window.draw_drawable(
                                self.darea.get_style().fg_gc[gtk.STATE_NORMAL],
                                self.pixmap, x, y, x, y, width, height)

        def convert_dev_to_user(self, x, y):
                return x - self.cx, (self.height - 1 - y) - self.cy

        def z_scale(self, z):
                return ZOOM/(FOCUS + z)

        def convert_3d_to_2d(self, point):
                px, py, pz = point
                px *= self.unit
                py *= self.unit
                z = (pz + ZSHIFT)*ZMOD
                x = self.cx + self.vx + (px - self.vx)*self.z_scale(z)
                y = self.cy + self.vy + (py - self.vy)*self.z_scale(z)
                return int(x), int(y)

        def color_shading(self, color, z):
                z += ZSHIFT
                r, g, b = PALETTE[color]
                # quadratic shading
                ratio = (float(DEPTH) - z)**2/(DEPTH**2)
                r = int(r*ratio)
                g = int(g*ratio)
                b = int(b*ratio)
                return r, g, b

        def draw_line(self, src, dst, color='white', dashed=False):
                gc = self.darea.window.new_gc()
                r, g, b = self.color_shading(color, src[2])
                gc.foreground = self.darea.get_colormap().alloc_color(r, g, b)
                if dashed:
                       gc.set_line_attributes(1, gtk.gdk.LINE_DOUBLE_DASH,
                                       gtk.gdk.CAP_BUTT, gtk.gdk.JOIN_BEVEL)
                x0, y0 = self.convert_3d_to_2d(src)
                x1, y1 = self.convert_3d_to_2d(dst)
                self.pixmap.draw_line(gc,
                                x0, self.height - 1 - (y0),
                                x1, self.height - 1 - (y1))

        def draw_scene(self):
                self.pixmap.draw_rectangle(self.darea.get_style().black_gc,
                                True, 0, 0, self.width, self.height)
                self.cube.draw()
                self.darea_realize(0, 0, -1, -1)
                return True

class Cube(Scene):
        def __init__(self, scene):
                self.scene = scene
                self.front = [(-1, 1, -1), (1, 1, -1), (1, -1, -1), (-1, -1, -1)]
                self.rear = [(-1, 1, 1), (1, 1, 1), (1, -1, 1), (-1, -1, 1)]

        def rotate(self, angle_XZ, angle_YZ):
                scene = self.scene
                for i in range(4):
                        # Front face
                        x, y, z = self.front[i]
                        theta = angle_XZ
                        x, y, z = (x*cos(theta) - z*sin(theta), y, x*sin(theta) + z*cos(theta))
                        theta = angle_YZ
                        x, y, z = (x, y*cos(theta) - z*sin(theta), y*sin(theta) + z*cos(theta))
                        self.front[i] = (x, y, z)
                        # Rear face
                        x, y, z = self.rear[i]
                        theta = angle_XZ
                        x, y, z = (x*cos(theta) - z*sin(theta), y, x*sin(theta) + z*cos(theta))
                        theta = angle_YZ
                        x, y, z = (x, y*cos(theta) - z*sin(theta), y*sin(theta) + z*cos(theta))
                        self.rear[i] = (x, y, z)

        def draw(self):
                scene = self.scene
                for i in range(4):
                        scene.draw_line(self.front[i], self.front[(i + 1)%4])
                        scene.draw_line(self.rear[i], self.rear[(i + 1)%4])
                        scene.draw_line(self.front[i], self.rear[i])

if __name__ == '__main__':
        Scene()
        gtk.main()