The Web H.Q. Of

Fluffy McDeath's X-Lapse Technology

As mentioned in my X-Lapse video on my youtube channel (if you don't know what I'm talking about then it's the video down below), I would have some technical details about X-Lapse on my web page. Well, down below the video, there it is.

I got my OBD kit from obddiag.net – AllPro

The python log script was dead simple and just piggybacked on pyOBD, though you could build something for pyOBD-II but they are different projects and pyOBD was just simpler. It was a delicate implementation though and there is no decent recovery from errors – just sudden death by uncaught exception.

Just reading the vehicle speed I was able to log about 5 times per second, or once every 6 frames of video (or, for my interlaced camera, one speed reading for every 12 fields).

#!/usr/bin/env python

#sensors i want to log:
#Sensor 5  Coolant Temperature", "0105", temp              ,"C"      ),
#Sensor 6  Short Term Fuel Trim", "0106", fuel_trim_percent ,"%"      ),
#Sensor 7  Long Term Fuel Trim", "0107", fuel_trim_percent ,"%"      ),
#Sensor 12 Engine RPM", "010C", rpm               ,""       ),
#Sensor 13 Vehicle Speed", "010D", speed             ,"MPH"    ),
#Sensor 14 Timing Advance", "010E", timing_advance    ,"degrees"),
#Sensor 15 Intake Air Temp", "010F", temp              ,"C"      ),
#Sensor 16 Air Flow Rate (MAF)", "0110", maf               ,"lb/min" ),
#Sensor 17 Throttle Position", "0111", throttle_pos      ,"%"      ),

import time
import obd_io

log_sensors=[13]
filename="pyLog.txt"
def start_log():
    port = obd_io.OBDPort("/dev/ttyACM0",None,5,5)
    if port.State==0: #Cant open serial port
        print "Couldn't connect\n"
        return None
    print "Connected\n"
    file = open(filename, "w")
    start_time = time.time() 
    if file:
        while 1:
            now = time.time()
            for i in log_sensors:
                data = port.sensor(i)
                line = "%.6f,\t%s\t%s %s\n" % (now - start_time, data[0], data[1], data[2])
                print "->%s"%line 
                file.write(line)
                file.flush()


start_log()

The perl script I wrote was also pretty simplistic. It reads in the times and speed and calculates (crudely) the cumulative distance. The speed readings are pretty close in time so I interpolate them linearly. The video fields don't happen often enough to get too much precision any way. At 50 km/h each field is about 0.23 m apart and the accuracy of my speedo is unknown.

On the output side, I just stepped through outputting frame numbers that should be the right distance apart more or less and the algorithm assumes that the input speed will always be less than the output.

$MILESPERHOUR_TO_METERPERSEC = 0.44704;
$FRAMES_PER_SEC = 60000/1001;
$SECS_PER_FRAME = 1/$FRAMES_PER_SEC;

$inputDistance = 0;
$nweInputDistance = 0;
$inputTime0 = 0;
$inputTime1 = 0;
$mps0 = 0;
$mps1 = 0;

#should take args - start frame, log file.
$startTime = 9.904689;
# start time occurs on frame 130 after 01:09 !!
$startFrame = int(130 + 69 * $FRAMES_PER_SEC);
$wantedDeltaD = 3; #m/s ~ 54 km/h
$currentFrame = $startFrame;

$outputTime = 0;
$outputDistance = 0;
$lastOutputFrameTime = 0;
$lastOutputFrameDistance = 0;

$outFrameCount = 0;

# We will output the frame numbers we want rendered in the final movie.

# On a per timestamp basis figure out where we are in distance.
# First time through we are just getting t0. Second time through we can start
# outputting frame numbers that satisfy our distance requirement.

while (<STDIN>)
{
	@parts = split;
	$inputTime1 = $parts[0];
	$mps1 = $parts[3];
	if ($mps1 eq "NODATA"){
		last;
	}
	$inputTime1 = $inputTime1 - $startTime;
	$mps1 = $mps1 * $MILESPERHOUR_TO_METERPERSEC;
	if ($inputTime0){
		$deltaT = ($inputTime1 - $inputTime0);
		$inputDistance += (($mps0 + $mps1) * ($deltaT))/2;
		# we have a new distance waypoint and end time.
		# Step off the frames that meet our deltaD until current end time.
		#my $frameEquiv = $inputTime1 * $FRAMES_PER_SEC;
		#print "$inputTime1 $frameEquiv\n";
		while($inputTime1 > ($outputTime + $SECS_PER_FRAME)){
			# find linear distance between last output time and distance and current input time and distance.
			$prevOutputTime = $outputTime;
			$prevOutputDeltaD = $outputDeltaD;

			$outputTime += $SECS_PER_FRAME;
			$outputDeltaD = $inputDistance - $lastOutputFrameDistance;

			if ($outputDeltaD > $wantedDeltaD) {
				$outFrameCount++;
				# which is closer to desired delta? This position or the previous one?
				if (abs($wantedDeltaD - $outputDeltaD) < abs($wantedDeltaD - $prevOutputDeltaD)){
					$lastOutputFrameTime = $outputTime;
					$lastOutputFrameDistance += $outputDeltaD;
					$ED = $wantedDeltaD - $outputDeltaD;
				} else {
					$lastOutputFrameTime = $prevOutputTime;
					$lastOutputFrameDistance += $prevOutputDeltaD;
					$ED = $wantedDeltaD - $prevOutputDeltaD;
				}
					#print "\t$currentFrame $outFrameCount $ED $lastOutputFrameDistance $lastOutputFrameTime\n";
					print "$currentFrame\n";
			}
			$currentFrame++;
		}
	}
	$inputTime0 = $inputTime1;
	$mps0 = $mps1;
}

The first frame is determined by dumping the frames at the start of the video until you get one where you can clearly see a new timestamp come up on the computer screen – then count the frames and match that to the timestamp in the perl script so that you can get the proper frames out.

Once the perl has dumped the frame numbers you want then you can use ffmpeg do pipe out all the frames to a simple filter which will only pass the frames we want to the input of a second ffmpeg that will build the output movie.

I wrote my filter in C

#include <stdio.h>
#include <stdlib.h>

#define LINE_BUFF 256

char line[LINE_BUFF];
char *frameBuffer = NULL;
FILE *fin = NULL;

int outputFrame(int w, int h, char* buf)
{
	int line;
	int stride = w * 3;
	puts("P6");
	printf("%d %d\n",w,h);
	puts("255");
// got a loop from legacy. Could just put the whole buffer
	for (line = 0; line < h; line ++)
	{
		fwrite(buf + stride * line, stride, 1, stdout);
	}
}

int main(int argc, char** argv)
{
	int bytesIn = 0;
	int good = 1;
	int w,h,m, size, inSize;
	int frameCount = 0;
	int nextFrame = 0;
	size = 0;

	// read frame numbers we want
	fin = fopen("frames.dat","r");
	if (!fin) exit(-1);
	if (!fscanf(fin,"%d", &nextFrame)) exit(-2);
	while (good)
	{
		if(fgets(line, LINE_BUFF, stdin) &&
		   !strcmp(line, "P6\n") && 
		   fgets(line, LINE_BUFF, stdin) &&
		   sscanf(line,"%d %d\n", &w, &h) == 2 &&
		   fgets(line, LINE_BUFF, stdin) &&
		   sscanf(line, "%d\n", &m) == 1
		   && m == 255)
		{
			if (size != w * h * 3)
			{
				size = w * h * 3;
				free((void*)frameBuffer);
				frameBuffer = (char*)malloc(size);
			}
			frameCount++;
			if (frameBuffer)
			{	
				inSize = 0;
				while(inSize < size && !feof(stdin))
				{
							inSize += fread(frameBuffer + inSize, 1, size - inSize, stdin);
							// fprintf(stderr, "sz = %d\n",inSize);
				}
				if (frameCount == nextFrame)
				{
					fputs("proceeding to output\n",stderr);
					outputFrame(w,h,frameBuffer);
					if (!fscanf(fin,"%d", &nextFrame)) exit(-3);
					fprintf(stderr, "frame %d done\n", frameCount);
				}
			}
		}
		else good = 0;
	}
	free(frameBuffer);
	fclose(fin);
	return 0;
}

The final step with ffmpeg was spelled:

ffmpeg -i 00135.MTS -vf yadif=1 -f image2pipe -vcodec ppm - | ./filt | ffmpeg -f image2pipe -vcodec ppm -i - -r 60000/1001 -aspect 16:9 -b 20M out2.mpg  -vcodec ppm -i - -r 60000/1001 -aspect 16:9

This page uses ScriptHighlighter by Alex Gorbatchev