Photo Speed Radar – Description of Code
The project uses a Bushnell Speedster Radar Gun wedded to a Raspberry Pi 3, Model B with a telephoto lens-mounted pi camera. This is part three of four parts. In part one an overview of this project is given, describing what the system consists of and how it works and performs. Part two describes the hardware and in particular describes in detail the Bushnell radar gun and its signal outputs. In this part the code that runs the whole show is described.
Building and Executing the Code
C++ versus Python
The application involves accurately measuring pulses while at the same time capturing video frames and processing them. To avoid any timing issues that may arise with an interpreted language such as Python, C++ was chosen as the programming language.
PIGPIO Library
This is a C library for the Raspberry Pi (and also has a Python interface). It was chosen since it is capable of measuring periods down to ~10 microseconds with reasonable accuracy even though the operating system (Ubuntu-Mate) is not a RTOS. The library is extensively documented and is easily downloaded and installed.
OpenCV and the Pi Cam
OpenCv library runs the videocapture and processes image operations. All video operations are done in a thread separate from the main thread. The CSI camera is used by opencv through the loadable kernel module bcm2835_v4l2 that is automatically loaded at boot time so that one does not need to manually reload it each time after a reboot. This module allows opencv to access the pi camera and at the same time to benefit from the power of the Pi’s GPU.
Code Framework
The pigpio example code freq_count_2.c is used as the framework for the main thread while all video operations are done in a secondary thread. Code was compiled as C++. Main thread and video thread use a mutex when global data was communci by either.
How PIGPIO Measures Frequency
In the code a demon is launched that monitors all 32 GPIO’s at once at a specified sample rate, for example 2 microseconds. The level of each and all GPIO ports is encoded in a 32 bit integer and saved in a gpioSample_t struct, and at the end of every one milliseconds the array of such structs is passed to a callback function where they are analyzed (500 structs in this example). The number of high to low transitions on the pin carrying the doppler frequency is accumulated for a time period set by the code. This is 0.2 seconds in the case at hand. Thus, the accumulated number of pulses divided by 0.2 seconds is the frequency.
Code Details
The program entails many includes.
/*run_cap2.cpp*/
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <sys/select.h>
#include <termios.h>
#include <string.h>
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
//#include "opencv2/imgproc/imgcodecs.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <time.h> /* time_t, struct tm, time, localtime */
#include <sstream>
#include <ostream>
#include <iostream>
#include <pigpio.h>
#include <cstdlib>
#include <math.h>
using namespace cv;
using namespace std;
Defines
The defines.
#define THRESH
#define PNG
#define PRINT
#define MAX_GPIOS 32
#define OPT_P_MIN 1
#define OPT_P_MAX 1000
#define OPT_P_DEF 20
#define OPT_R_MIN 1
#define OPT_R_MAX 10
#define OPT_R_DEF 5
#define OPT_S_MIN 1
#define OPT_S_MAX 10
#define OPT_S_DEF 5
#define QUIT signals.quit
#define SNAP signals.snap
#define SAVE signals.save
#define SPEED signals.speed
Globals
Global parameters. (note: ints Close and Enable are not used but Open is used to define a port pin used to flash a light .)
static volatile int g_pulse_count[MAX_GPIOS];
static volatile int g_reset_counts;
static uint32_t g_mask;
static int g_num_gpios;
static int g_gpio[MAX_GPIOS];
static int g_opt_p = OPT_P_DEF;
static int g_opt_r = OPT_R_DEF;
static int g_opt_s = OPT_S_DEF;
static int g_opt_t = 0;
static float reduce_repeat_rate = 1; //this variable was used when a lower
//repeat rate was tested
static unsigned int Open = 21;
static unsigned int Close = 20;
static unsigned int Enable = 16;
struct termios orig_termios;
/*global flags for the threads*/
typedef struct{
int quit;
int snap;
int save;
float speed;
}cflags;
static cflags signals;
pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
Utility Functions
Function used to print to the screen for troubleshooting.
/*this function is used for troubleshooting*/
void print_cflags(int q, int sn, int sa, float sp, char thread[]){
printf("thread: %s quit=%d snap=%d save=%d speed=%3f\r\n", thread,q,sn,sa,sp);
}
Set of functions to act similar to the Windows kbhit() function in order to exit the program with a key press.
/*here are a set of functions that will implement a version of kbhit()*/
void reset_terminal_mode()
{
tcsetattr(0, TCSANOW, &orig_termios);
}
void set_conio_terminal_mode()
{
struct termios new_termios;
/* take two copies - one for now, one for later */
tcgetattr(0, &orig_termios);
memcpy(&new_termios, &orig_termios, sizeof(new_termios));
/* register cleanup handler, and set the new terminal mode */
atexit(reset_terminal_mode);
cfmakeraw(&new_termios);
tcsetattr(0, TCSANOW, &new_termios);
}
int kbhit()
{
struct timeval tv = { 0L, 0L };
fd_set fds;
FD_ZERO(&fds);
FD_SET(0, &fds);
return select(1, &fds, NULL, NULL, &tv);
}
int getch()
{
int r;
unsigned char c;
if ((r = read(0, &c, sizeof(c))) < 0) {
return r;
} else {
return c;
}
}
The below functions are not used in the full sense. One of the pins (Open) is used to flash a light.
/*these functions will operate the relay to trigger radar on/off*/
/*this functionality is optional*/
void open_solenoid(){
gpioWrite(Enable, PI_ON);
gpioWrite(Open, PI_ON);
gpioDelay(100000); //100 ms
gpioWrite(Open, PI_OFF);
gpioWrite(Enable, PI_OFF);
}
void close_solenoid(){
gpioWrite(Enable, PI_ON);
gpioWrite(Close, PI_ON);
gpioDelay(100000); //100 ms
gpioWrite(Close, PI_OFF);
gpioWrite(Enable, PI_OFF);
}
The executable requires arguments passed to it at start up. These functions handle the arguments.
/*main thread functions - to detect freq. count/mph*/
void usage()
{
fprintf
(stderr,
"\n" \
"Usage: sudo ./freq_count_2 gpio ... [OPTION] ...\n" \
" -p value, sets pulses every p micros, %d-%d, TESTING only\n" \
" -r value, sets refresh period in deciseconds, %d-%d, default %d\n" \
" -s value, sets sampling rate in micros, %d-%d, default %d\n" \
"\nEXAMPLE\n" \
"sudo ./freq_count_2 4 7 -r2 -s2\n" \
"Monitor gpios 4 and 7. Refresh every 0.2 seconds. Sample rate 2 micros.\n" \
"\n",
OPT_P_MIN, OPT_P_MAX,
OPT_R_MIN, OPT_R_MAX, OPT_R_DEF,
OPT_S_MIN, OPT_S_MAX, OPT_S_DEF
);
}
void fatal(int show_usage, char *fmt, ...)
{
char buf[128];
va_list ap;
va_start(ap, fmt);
vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
fprintf(stderr, "%s\n", buf);
if (show_usage) usage();
fflush(stderr);
exit(EXIT_FAILURE);
}
Initialization of options passed by the arguments.
static int initOpts(int argc, char *argv[])
{
int i, opt;
while ((opt = getopt(argc, argv, "p:r:s:")) != -1)
{
i = -1;
switch (opt)
{
case 'p':
i = atoi(optarg);
if ((i >= OPT_P_MIN) && (i <= OPT_P_MAX))
g_opt_p = i;
else fatal(1, "invalid -p option (%d)", i);
g_opt_t = 1;
break;
case 'r':
i = atoi(optarg);
if ((i >= OPT_R_MIN) && (i <= OPT_R_MAX))
g_opt_r = i;
else fatal(1, "invalid -r option (%d)", i);
break;
case 's':
i = atoi(optarg);
if ((i >= OPT_S_MIN) && (i <= OPT_S_MAX))
g_opt_s = i;
else fatal(1, "invalid -s option (%d)", i);
break;
default: /* '?' */
usage();
exit(-1);
}
}
return optind;
}
The Callback Function
Callback function used by the demon. Counts only the low to high transitions.
void samples(const gpioSample_t *samples, int numSamples)
{
static uint32_t state = 0;
uint32_t high, level;
int g, s;
if (g_reset_counts)
{
g_reset_counts = 0;
for (g=0; g<g_num_gpios; g++) g_pulse_count[g] = 0;
}
for (s=0; s<numSamples; s++)
{
level = samples[s].level;
high = ((state ^ level) & g_mask) & level;
state = level;
/* only interested in low to high */
if (high)
{
for (g=0; g<g_num_gpios; g++)
{
if (high & (1<<g_gpio[g])) g_pulse_count[g]++;
}
}
}
}
Video thread will be presented and discussed after the main thread.
void* video_thread(void* data);
Main Thread
Main thread. Local variables are defined and global variables that communicate with the video thread initialized. Video thread is created and the mutex is initialized.
int main(int argc, char *argv[])
{
int i, rest, g, wave_id, mode;
gpioPulse_t pulse[2];
int count[MAX_GPIOS];
int save_count; /*value of previous count*/
int current_value;
static float freq, period, mph, max_speed, rate;
int rc; /* return value */
pthread_t thread_id; /* thread's ID (just an integer) */
int t = 11; /* data passed to the new thread */
int count_cycles = 0;
char thread[10] = "main";
int target;
/*initialize the global flags*/
signals.quit = 0; /*tells thread to quit*/
signals.snap = 0; /*tells thread to take a frame and hold it*/
signals.save = 0; /*tells thread to save the held frame*/
signals.speed = 0.0; /*mph*/
/* create a new thread that will execute the video camera */
rc = pthread_create(&thread_id, NULL, video_thread, (void*)&max_speed);
if(rc) /* could not create thread */
{
printf("\n ERROR: return code from pthread_create is %d \n", rc);
exit(1);
}
printf("\n Created new thread (%lu) ... \r\n", thread_id);
/*init the mutex - mutex is always used when working with the global variables*/
pthread_mutex_init(&mutex1,NULL);
Setting up and initializing all relevant GPIO. The relay operation ports are not used for their original purpose, but one of these port pins is used to turn on a flasher when a speeder exceeds a speed value.
/*now attend to the pigpio's*/
if (gpioInitialise()<0) return 1;
/*initialize relay operation ports*/
gpioSetMode(Open, PI_OUTPUT);
gpioWrite(Open, PI_OFF);
gpioSetMode(Close, PI_OUTPUT);
gpioWrite(Close, PI_OFF);
gpioSetMode(Enable, PI_OUTPUT);
gpioWrite(Enable, PI_OFF);
/* make sure relay is OFF*/
close_solenoid();
/* command line parameters */
rest = initOpts(argc, argv);
/* get the gpios to monitor */
g_num_gpios = 0;
for (i=rest; i<argc; i++)
{
g = atoi(argv[i]);
if ((g>=0) && (g<32))
{
g_gpio[g_num_gpios++] = g;
g_mask |= (1<<g);
}
else fatal(1, "%d is not a valid g_gpio number\n", g);
}
if (!g_num_gpios) fatal(1, "At least one gpio must be specified");
/*the parameter 'target' is used later as the threshold to begin monitoring car speed*/
target = (int) ( 100 * g_opt_r * reduce_repeat_rate / 2 );
printf("target = %d\r\n", target);
printf("Monitoring gpios\r\n");
for (i=0; i<g_num_gpios; i++) printf(" %d", g_gpio[i]);
printf("\nSample rate %d micros, refresh rate %d deciseconds\n",
g_opt_s, g_opt_r);
More initialization and set up starting with command that starts the demon clock. Also note the instantiation of the callback function.
gpioCfgClock(g_opt_s, 1, 1);
gpioWaveClear(); //not used
pulse[0].gpioOn = g_mask;
pulse[0].gpioOff = 0;
pulse[0].usDelay = g_opt_p;
pulse[1].gpioOn = 0;
pulse[1].gpioOff = g_mask;
pulse[1].usDelay = g_opt_p;
gpioWaveAddGeneric(2, pulse); //not used
wave_id = gpioWaveCreate(); //not used
/* monitor g_gpio level changes */
gpioSetGetSamplesFunc(samples, g_mask);
mode = PI_INPUT;
//not used
if (g_opt_t)
{
gpioWaveTxSend(wave_id, PI_WAVE_MODE_REPEAT);
mode = PI_OUTPUT;
}
for (i=0; i<g_num_gpios; i++) gpioSetMode(g_gpio[i], mode);
/*this is the rate at which the thread cycles (in seconds)*/
rate = (float) g_opt_r * 0.1 * reduce_repeat_rate; /*ie, 0.1*/
set_conio_terminal_mode();
The While Loop
Below is the while loop and the end of the main thread. A number of points are now described about its operation.
- At the beginning of the loop “counts” (representing the period) is obtained and then the reset flag is set telling the callback to start counting over again.
- Speed is then calculated using the count of pulses and the appropriate constants including adjustment for angle of incidence.
- Then a segment of evaluation code containing a series of if statements is evaluated but only if the count number is greater than a target (typically =5) or if the code segment has already been entered on the previous loop. Having a target threshold causes very slow vehicles to be ignored.
- In the evaluation code segment any time the vehicle speed equals or exceeds 34.0 MPH a flashing light is activated.
- A cycle count is initiated the first time through the evaluation code segment. On each cycle through the currently measured value of speed is set to “max_speed” but only if the present value of “max_speed” is less than or equal to the currently measured value.
- On the third cycle the video thread is flagged through the mutex to “snap” a picture, which means to hold onto the currently captured frame and stop capturing further frames.
- On any cycle after the first “max_speed” is updated and through the mutex made available to the video thread.
- When the pulse count falls below 15, the video thread is flagged to process the image being held and save it to a file. Cycle count is set to zero and the flashing light is turned off.
- A new loop begins after a wait period of 0.2 seconds.
- The remainder of the code is clean up when exiting the program.
while (!kbhit()) /*press ENTER or any key to exit*/
{
for (i=0; i<g_num_gpios; i++) count[i] = g_pulse_count[i];
g_reset_counts = 1; //tells callback to reset g_pulse_count[] to zero
current_value = count[0];
freq = (float) count[0];
for (i=0; i<g_num_gpios; i++)
{
pthread_mutex_lock(&mutex1);
// printf(" %d=%d\r\t", g_gpio[i], count[i]);
pthread_mutex_unlock(&mutex1);
}
period = (1/freq)*1000000*rate; //measured period in microseconds
mph = (0.01392 / period) * 1000000; //speed in mph using doppler shift formula
// mph = mph / 0.98; //10 degrees
// mph = mph / 0.866; //30 degrees - adjust for cosine law given gun placement
// mph = mph / 0.77; //40 deg
mph = mph / 0.64; //50 deg
// mph = mph / 0.819; //35 deg
if( ( count[0] > target ) || count_cycles > 0)
{
/*we are going to hold one of the sequential images and save it after
the returned value of count[0] drops to near zero, Meanwhile, in the sequence of speeds
being measured the maximum value is being saved. Then we will send this speed to the video thread
and tell it to save the held image with this speed added to it*/
/*Which image to retain is determined by the lag between when the gun records a car and the
time in which the car is centered in the camera's field of view.*/
if ( isgreaterequal(mph, (float) 34.0))
{
//light ON
gpioWrite(Open, PI_ON);
}
count_cycles += 1; printf("count_cycles = %d\r\n", count_cycles); //a counter is started
if (count_cycles == 1) max_speed = mph;
if (count_cycles == 3) /*tell the video thread to hold the next image*/
{
pthread_mutex_lock(&mutex1);
signals.snap = 1;
signals.speed = mph;
// max_speed = mph;
// print_cflags(signals.quit, signals.snap, signals.save, signals.speed, thread);
pthread_mutex_unlock(&mutex1);
}
if ((count_cycles >= 2)) /* */
{
if ( isgreaterequal(mph, max_speed)) {
/*swap*/
pthread_mutex_lock(&mutex1);
signals.speed = mph;
max_speed = mph;
// print_cflags(signals.quit, signals.snap, signals.save, signals.speed, thread);
// printf("swap max_speed = %3f\r\n", mph);
pthread_mutex_unlock(&mutex1);
}
}
if (count[0] < 15) /*tell thread to save the image */
{
printf("sending save, count[0]=%d\r\n", count[0]);
pthread_mutex_lock(&mutex1);
signals.speed = max_speed;
// print_cflags(signals.quit, signals.snap, signals.save, signals.speed, thread );
signals.save = 1;
pthread_mutex_unlock(&mutex1);
count_cycles = 0;
//light OFF
gpioWrite(Open, PI_OFF);
}
#ifdef PRINT
printf(" period = %4f microseconds mph = %3f\r\n", period, mph);
pthread_mutex_lock(&mutex1);
signals.speed = mph;
// signals.save = 1;
pthread_mutex_unlock(&mutex1);
#endif
}else
{
}
// printf("\r\n");
gpioDelay(g_opt_r * 100000 * reduce_repeat_rate); /*for ex. 0.1*/
}
pthread_mutex_lock(&mutex1);
signals.quit = 1;
pthread_mutex_unlock(&mutex1);
(void)getch(); /* consume the character */
gpioDelay(100000);
pthread_mutex_destroy(&mutex1);
// pthread_exit(NULL); /* terminate the thread */
gpioTerminate();
}
Video Thread
The video thread is presented below in its entirety. Its operation is described.
- At the beginning there are number of string instantiations related to stamping the file name of images with date, time, and speed.
- After invoking thread detach the video capture stream is started and resolution of frames is set along with setting the compression of PNG images. Finally, the communication data from the main thread is printed out.
- The while loop is then entered. A frame is captured so long as the stop flag is not set. After a frame is captured a series of checks are made to determine what to do with that frame. The first is to determine if the frame should be held and further frame capturing suspended (thus the stop flag is set). The second check is to determine when this frame shall be processed and saved to a file. The third check is to determine if a flag to quit and exit the thread is received.
- When the image is to be processed and saved, a series to things occur. First the image is rotated 180 degrees. Speed is obtained from the main thread (which will be the max_speed) and added as text to the image that will be saved. Then date and time is obtained followed by formatting year, month, day, hour, minute, seconds, and speed in a string that becomes the file name for the image.
- The image is then saved and all of the strings are emptied for re-use.
- The while loop then continues or the thread exits.
void* video_thread(void* data)
{
const string filename;
time_t rawtime;
struct tm * timeinfo;
stringstream ss; //this will be the file name for the saved image
stringstream ff; //used for adding speed text to image
int save_image = 0;
float my_speed = 0;
int stop = 0;
char thread[10] = "video";
stringstream month;
string mo;
stringstream day;
string d;
stringstream hour;
string h;
stringstream min;
string mi;
stringstream sec;
string s;
const string zero = "0";
pthread_detach(pthread_self());
printf("Hello from new thread - got %d\n", save_image);
VideoCapture cap(0);
if (!cap.isOpened()) {
cerr << "ERROR: Unable to open the camera" << endl;
}else{
cout << "seems that camera opened OK" << endl;
}
cap.set(CV_CAP_PROP_FRAME_WIDTH, 1920);
cap.set(CV_CAP_PROP_FRAME_HEIGHT, 1080);
vector<int> compression_params;
/*since we want to read license plates, we want lossless images*/
/*PNG is the choice.*/
#ifdef PNG
compression_params.push_back(CV_IMWRITE_PNG_COMPRESSION);
compression_params.push_back(7);
#else
compression_params.push_back(CV_IMWRITE_JPEG_QUALITY);
compression_params.push_back(90);
#endif
Mat frame;
// clock_t tick;
// clock_t lasttick = clock();
pthread_mutex_lock(&mutex1);
print_cflags( QUIT, SNAP, SAVE, SPEED, thread );
pthread_mutex_unlock(&mutex1);
float num;
/*this thread will loop until main thread sends a QUIT flag*/
while(!QUIT){
if (!stop){
bool bSuccess = cap.read(frame);
if (!bSuccess)
{
cout << "Cannot read a frame from camera" << endl;
}
}
pthread_mutex_lock(&mutex1);
// print_cflags( QUIT, SNAP, SAVE, SPEED, thread );
if (SNAP == 1) //tells thread to hold the current image
{
// print_cflags( QUIT, SNAP, SAVE, SPEED, thread );
stop = 1; //stop taking new frames until further notice
SNAP = 0;
SAVE = 0;
printf("snap=%d save=%d\r\n", SNAP, SAVE);
}
pthread_mutex_unlock(&mutex1);
// tick = clock();
// num = (float) (tick - lasttick);
// lasttick = tick;
/*get shared data*/
pthread_mutex_lock(&mutex1);
save_image = SAVE; //if = 1, tells thread to save the image that is being held
if (save_image == 1)
{
print_cflags( QUIT, SNAP, SAVE, SPEED, thread );
SAVE = 0;
my_speed = SPEED;
stop = 0;
}
if (QUIT == 1) break; //always check for a quit
pthread_mutex_unlock(&mutex1);
if(save_image){
/*rotate image 180*/
double angle = -180;
// get rotation matrix for rotating the image around its center in pixel coordinates
cv::Point2f center((frame.cols-1)/2.0, (frame.rows-1)/2.0);
cv::Mat rot = cv::getRotationMatrix2D(center, angle, 1.0);
// determine bounding rectangle, center not relevant
cv::Rect bbox = cv::RotatedRect(cv::Point2f(), frame.size(), angle).boundingRect();
// adjust transformation matrix
rot.at<double>(0,2) += bbox.width/2.0 - frame.cols/2.0;
rot.at<double>(1,2) += bbox.height/2.0 - frame.rows/2.0;
cv::Mat dst;
cv::warpAffine(frame, dst, rot, bbox.size());
/*image is now in "dst"*/
save_image = 0;
my_speed = *( (float*) data); // from main thread, speed to be printed
ff<<my_speed<<" MPH";
/* add speed to upper left hand corner of image*/
cv::putText(dst,
ff.str(),
cv::Point(50,50), // Coordinates
cv::FONT_HERSHEY_COMPLEX_SMALL, // Font
2.0, // Scale. 2.0 = 2x bigger
cv::Scalar(255,255,255), // BGR Color
1, // Line Thickness (Optional)
CV_AA, false);
cv::putText(dst,
ff.str(),
cv::Point(50,100), // Coordinates
cv::FONT_HERSHEY_COMPLEX_SMALL, // Font
2.0, // Scale. 2.0 = 2x bigger
cv::Scalar(0,0,0), // BGR Color
1, // Line Thickness (Optional)
CV_AA, false);
// tick = clock();
// num = (float) (lasttick - tick);
// lasttick = tick;
/* now the time stamp is created for the file name of image*/
/* the speed is also appended to the fila name*/
time(&rawtime);
timeinfo = localtime(&rawtime);
/*do month*/
month << timeinfo->tm_mon + 1;
if ( (timeinfo->tm_mon + 1) < 10 )
{
mo = zero + month.str();
}else
{
mo = month.str();
}
/*do day*/
day << timeinfo->tm_mday;
if ( timeinfo->tm_mday < 10 )
{
d = zero + day.str();
}else
{
d = day.str();
}
/*do hour*/
hour << timeinfo->tm_hour;
if ( timeinfo->tm_hour < 10 )
{
h = zero + hour.str();
}else
{
h = hour.str();
}
/*do min*/
min << timeinfo->tm_min;
if ( timeinfo->tm_min < 10 )
{
mi = zero + min.str();
}else
{
mi = min.str();
}
/*do sec*/
sec << timeinfo->tm_sec;
if ( timeinfo->tm_sec < 10 )
{
s = zero + sec.str();
}else
{
s = sec.str();
}
ss << (timeinfo->tm_year + 1900) << '-' << mo << '-' << d << '-'
<< h << '-'
<< mi << '-'
<< s << '-' << my_speed << '.' << 'p' << 'n' << 'g';
/*print out file name*/
pthread_mutex_lock(&mutex1);
// print_cflags( QUIT, SNAP, SAVE, SPEED, thread );
cout<<ss.str()<<endl;
pthread_mutex_unlock(&mutex1);
imwrite(ss.str(), dst, compression_params); //save image
/*empty the string*/
ss.str(string());
month.str(string());
day.str(string());
hour.str(string());
min.str(string());
sec.str(string());
ff.str(std::string());
}
}
cout << "exiting thread" << endl;
pthread_exit(NULL); /* terminate the thread */
}
Building and Executing the Code
Below is the build command for the Raspberry Pi 3, Model B with Ubuntu-Mate installed.
g++ -ggdb -o run_cap2 run_cap2.cpp -lpigpio -lpthread -lrt -pthread -I/usr/include/opencv /usr/lib/arm-linux-gnueabihf/libopencv_calib3d.so -lopencv_calib3d /usr/lib/arm-linux-gnueabihf/libopencv_contrib.so -lopencv_contrib /usr/lib/arm-linux-gnueabihf/libopencv_core.so -lopencv_core /usr/lib/arm-linux-gnueabihf/libopencv_features2d.so -lopencv_features2d /usr/lib/arm-linux-gnueabihf/libopencv_flann.so -lopencv_flann /usr/lib/arm-linux-gnueabihf/libopencv_gpu.so -lopencv_gpu /usr/lib/arm-linux-gnueabihf/libopencv_highgui.so -lopencv_highgui /usr/lib/arm-linux-gnueabihf/libopencv_imgproc.so -lopencv_imgproc /usr/lib/arm-linux-gnueabihf/libopencv_legacy.so -lopencv_legacy /usr/lib/arm-linux-gnueabihf/libopencv_ml.so -lopencv_ml /usr/lib/arm-linux-gnueabihf/libopencv_objdetect.so -lopencv_objdetect /usr/lib/arm-linux-gnueabihf/libopencv_ocl.so -lopencv_ocl /usr/lib/arm-linux-gnueabihf/libopencv_photo.so -lopencv_photo /usr/lib/arm-linux-gnueabihf/libopencv_stitching.so -lopencv_stitching /usr/lib/arm-linux-gnueabihf/libopencv_superres.so -lopencv_superres /usr/lib/arm-linux-gnueabihf/libopencv_ts.so -lopencv_ts /usr/lib/arm-linux-gnueabihf/libopencv_video.so -lopencv_video /usr/lib/arm-linux-gnueabihf/libopencv_videostab.so -lopencv_videostab
divya@fdolj-desktop2:~/radar_hack$
To start up the executable super user access is needed as well as the arguments shown.
sudo ./run_cap2 4 5 -r2 -s2
The 4 is the port pin number to which the doppler signal from the comparator is connected, 5 is the port pin number to which is connected the COMP output of the radar unit (used to help reset “counts” to zero after a vehicle passes), -r2 specifies the reset measurement loop time as 0.2 seconds, and -s2 specifies the the demon sample rate to be 2 microseconds. It should be noted that pin numbers are the Broadcom port numbers.
Results
Below is a portion of the directory containing the sequence of vehicle images. Note the formatting of the file names. Other than the year each date and time field is two digits, resulting in complete chronological ordering in the directory. Overall, the file names can easily be processed with shell commands for analysis, such as listing all speeds on a given day.
-rw-r--r-- 1 root root 2801904 Oct 11 15:15 2019-10-11-15-15-40-31.9725.png
-rw-r--r-- 1 root root 2813913 Oct 11 15:15 2019-10-11-15-15-47-23.7075.png
-rw-r--r-- 1 root root 2818761 Oct 11 15:16 2019-10-11-15-16-41-30.7763.png
-rw-r--r-- 1 root root 2781677 Oct 11 15:16 2019-10-11-15-16-56-25.4475.png
-rw-r--r-- 1 root root 2783022 Oct 11 15:20 2019-10-11-15-20-02-34.365.png
-rw-r--r-- 1 root root 2846547 Oct 11 15:20 2019-10-11-15-20-23-23.5987.png
-rw-r--r-- 1 root root 2858238 Oct 11 15:22 2019-10-11-15-22-01-22.5112.png
-rw-r--r-- 1 root root 2852472 Oct 11 15:22 2019-10-11-15-22-18-27.9487.png
-rw-r--r-- 1 root root 2829923 Oct 11 15:25 2019-10-11-15-25-15-25.665.png
-rw-r--r-- 1 root root 2845125 Oct 11 15:25 2019-10-11-15-25-21-24.4688.png
-rw-r--r-- 1 root root 2865944 Oct 11 15:25 2019-10-11-15-25-26-22.8375.png
Conclusions
The complete code has been presented above in its exact order. It has been in use for over a year now and has worked well. This completes the third part of a four part series describing the photo speed radar project. What remains in the fourth part of the series is a description of set up, operation, and analyzing results.