November 02, 2025, 02:24:49 PM

News:

IonicWind Snippit Manager 2.xx Released!  Install it on a memory stick and take it with you!  With or without IWBasic!


Program stuttering

Started by Boris, July 24, 2008, 02:30:04 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Boris

July 24, 2008, 02:30:04 PM Last Edit: July 24, 2008, 02:46:50 PM by Boris
Ok, this is the plan. A program that displays my live webcam image and uses serial COM1 interfacing to an external microcontroller that in turn drives pan and tilt servos. Up/Down/Left/Right buttons beside the veiwport control the panning. Using the Commexample programs packaged with Ebasic (Thank you Paul), and the webcam program posted in another thread (Thank you Pistol350 and Sapero), I have managed to get a working program. But there is a problem.

I use SETTIMER to transmit the serial X and Y data to the microcontroller at regular intervals, however, when the cameras field of view gets dark, the automatic gain booster brightens the image. This seems to slow everything down considerably. The framerate drops, the timer intervals get longer and the servos get jerky. I’m assuming this is caused by the camera drivers using more processing power to artificially brighten the image.

While trying to solve this problem, I experimented with removing all the serial code from the webcam program and used a completely separate program to do the serial/servo controlling. This works. Separating the programs and running them both in parallel has hugely improved the rate at which I can update the servos and there is no variation in update intervals as the camera pans across light and dark views.

So now to the question:

Can I somehow use just one program, but split the tasks (multithreading?) so that one does not interfere with the timing of the other?

Here is the complete program that works but stutters:

$include "windows.inc"
$include "vfw.inc"
$include "shlwapi.inc"

SETID "OPENEXISTING",3
SETID "GENERICREAD",0x80000000
SETID "GENERICWRITE",0x40000000
SETID "NOPARITY",0
SETID "ODDPARITY",1
SETID "EVENPARITY",2
SETID "MARKPARITY",3
SETID "SPACEPARITY",4
SETID "ONESTOPBIT",0
SETID "TWOSTOPBITS",2

type COMMTIMEOUT
    DEF ReadIntervalTimeout:INT 
    DEF ReadTotalTimeoutMultiplier:INT
    DEF ReadTotalTimeoutConstant:INT
    DEF WriteTotalTimeoutMultiplier:INT
    DEF WriteTotalTimeoutConstant:INT
endtype

type DCBbb
    DEF DCBlength:int
    DEF BaudRate:int
DEF flags:int
    DEF wReserved:word
    DEF XonLim:word
    DEF XoffLim:word
    DEF ByteSize:char
    DEF Parity:char
    DEF StopBits:char
    DEF XonChar:char
    DEF XoffChar:char
    DEF ErrorChar:char
    DEF EofChar:char
    DEF EvtChar:char
    DEF wReserved1:word
ENDTYPE

def dcb1:DCBbb
def zhcom,success,written,bytesread:int
def data1:string
dcb1.DCBlength = 26

enum control_ids
ID_viewport
ID_connect
ID_disconnect
ID_settings
ID_capture
ID_pan
ID_up
ID_right
ID_down
ID_left
ID_cent
ID_status
endenum

UINT hWndCap1
DIALOG d1
CREATEDIALOG d1,0,0,820,510,0x80C80080|@MINBOX,0,"Caption",&d1_handler
CONTROL d1,@STATIC,"",13,14,640,480,0x50800000,ID_viewport
CONTROL d1,@BUTTON,"Connect",663,14,147,24,0x50000000,ID_connect
CONTROL d1,@BUTTON,"Disconnect",663,46,147,24,0x50000000,ID_disconnect
CONTROL d1,@BUTTON,"Settings",663,78,147,24,0x50000000,ID_settings
CONTROL d1,@BUTTON,"Capture",663,32+78,147,24,0x50000000,ID_capture
CONTROL d1,@BUTTON,"UP",611+104,32+65+53,41,26,0x50000000,ID_up
CONTROL d1,@BUTTON,"RIGHT",611+149,4+32+65+79,41,26,0x50000000,ID_right
CONTROL d1,@BUTTON,"DOWN",611+104,8+32+65+108,41,26,0x50000000,ID_down
CONTROL d1,@BUTTON,"LEFT",611+61,4+32+65+79,41,26,0x50000000,ID_left
CONTROL d1,@BUTTON,"STOP",611+104,4+32+65+79,41,26,0x50000000,ID_cent
CONTROL d1,@GROUPBOX,"Pan",663,32+215,147,147,0x50000007,ID_pan
control d1,@static,"*",663,400,147,24,0,ID_status
posx=150:posy=150
xx=0:yy=0
showdialog d1
waituntil d1=0

SUB d1_handler
INT L,T,W,H
SELECT @MESSAGE
CASE @IDINITDIALOG
CENTERWINDOW d1
getsize(d1,L,T,W,H,ID_viewport)
hWndCap1 = capCreateCaptureWindow("capture 1", 0x50000000, 0,0,W,H, getcontrolhandle(d1, ID_viewport), 20)
enablecontrol d1,ID_disconnect,0
CASE @IDCLOSEWINDOW
SendMessage hWndCap1, WM_CAP_DRIVER_DISCONNECT, 0, 0
CLOSEDIALOG d1,@IDOK
case @idtimer
posx=posx+xx:posy=posy+yy
WriteFile(zhcom,"X"+chr$(posx),2,written,0):pause (20)
WriteFile(zhcom,"Y"+chr$(posy),2,written,0)
setcontroltext d1,ID_status,str$(posx)+" "+str$(posy)+" "+str$(written)
CASE @IDCONTROL
SELECT @CONTROLID
CASE ID_connect
zhcom = CreateFileA("COM1",@GENERICREAD | @GENERICWRITE,0,0,@OPENEXISTING,0,0)
if zhcom=-1
messagebox d1,"Cannot open port COM1","Error",@MB_OK
SendMessage hWndCap1, WM_CAP_DRIVER_DISCONNECT, 0, 0
success = CloseHandle(zhcom)
CLOSEDIALOG d1,@IDOK
endif
IF GetCommState(zhcom,dcb1)
dcb1.BaudRate = 4800
dcb1.ByteSize = 8
dcb1.StopBits = @ONESTOPBIT
dcb1.Parity = @NOPARITY
IF SetCommState(zhcom,dcb1) = 0
messagebox d1,"Error setting com parameters","Error",@MB_OK
SendMessage hWndCap1, WM_CAP_DRIVER_DISCONNECT, 0, 0
success = CloseHandle(zhcom)
CLOSEDIALOG d1,@IDOK
ELSE
ReadFile(zhcom,data1,128,bytesread,0)
ENDIF
ENDIF
capstart()
starttimer d1,50
enablecontrol d1,ID_connect,0
enablecontrol d1,ID_disconnect,1
CASE ID_disconnect
SendMessage hWndCap1, WM_CAP_DRIVER_DISCONNECT, 0, 0
success = CloseHandle(zhcom)
stoptimer d1
enablecontrol d1,ID_connect,1
enablecontrol d1,ID_disconnect,0
CASE ID_settings
/*button clicked*/
CASE ID_capture
capSaveFrame()
CASE ID_up
yy=1
CASE ID_right
xx=1
CASE ID_down
yy=-1
CASE ID_left
xx=-1
CASE ID_cent
xx=0:yy=0
ENDSELECT
ENDSELECT
RETURN
ENDSUB

sub pause(interval as int)
def p as uint
p=millisecs():do:until millisecs()-p>interval
return
endsub

sub capStart(opt index=1:int)
if hWndCap1=0 then return
SendMessage hWndCap1, WM_CAP_DRIVER_CONNECT, 0, 0
SendMessage hWndCap1, WM_CAP_SET_PREVIEWRATE, 50, 0
SendMessage hWndCap1, WM_CAP_SET_OVERLAY, TRUE, 0
SendMessage hWndCap1, WM_CAP_SET_PREVIEW, true, 0
return
endsub

sub capSaveFrame
ISTRING path[300]
INT cnt
POINTER path_end
IF hWndCap1=0 then return
path = GetStartPath
path_end = &path + len(path)
for cnt = 1 to 0x7FFFFFFF
wsprintf(path_end, "%.5d.bmp", cnt)
if PathFileExists(path) = FALSE
if SendMessage(hWndCap1, WM_CAP_FILE_SAVEDIB, 0, &path)
SetCaption d1, "saved as " + *<STRING>path_end
else
SetCaption d1, "failed to save as " + *<STRING>path_end
DeleteFile path
endif
BreakFor
endif
next cnt
return
endsub

Thank you for not breeding

Boris

I tried everything I could think of to no avail. Using a second window and handler for the timer driven serial comms has no noticeable benefit. So I have for the moment resigned myself to using two separate programs running at the same time. Here is the source I have so far.

Webcam viewport program:

'CAMVIEW.EBA (Ebasic 1.63)
'Basic webcam viewport.
'Boris Burke JULY 2008
'Code derived mainly from examples posted on the forum by Sapero and Pistol350 (Thank you)

$include "windows.inc"
$include "vfw.inc"
$include "shlwapi.inc"

enum control_ids
ID_viewport
ID_connect
ID_disconnect
ID_settings
ID_capture
endenum

UINT hWndCap1
DIALOG d1
CREATEDIALOG d1,0,0,820,510,0x80C80080|@MINBOX,0,"Camview",&d1_handler
CONTROL d1,@STATIC,"",13,14,640,480,0x50800000,ID_viewport
CONTROL d1,@BUTTON,"Connect",663,14,147,24,0x50000000,ID_connect
CONTROL d1,@BUTTON,"Disconnect",663,46,147,24,0x50000000,ID_disconnect
CONTROL d1,@BUTTON,"Settings",663,78,147,24,0x50000000,ID_settings
CONTROL d1,@BUTTON,"Capture",663,32+78,147,24,0x50000000,ID_capture
showdialog d1
waituntil d1=0
end

SUB d1_handler
INT L,T,W,H
SELECT @MESSAGE
CASE @IDINITDIALOG
CENTERWINDOW d1
getsize(d1,L,T,W,H,ID_viewport)
hWndCap1 = capCreateCaptureWindow("capture 1", 0x50000000, 0,0,W,H, getcontrolhandle(d1, ID_viewport), 20)
enablecontrol d1,ID_disconnect,0
CASE @IDCLOSEWINDOW
SendMessage hWndCap1, WM_CAP_DRIVER_DISCONNECT, 0, 0
CLOSEDIALOG d1,@IDOK
case @idtimer
stoptimer d1
setcaption d1,"Camview"
CASE @IDCONTROL
SELECT @CONTROLID
CASE ID_connect
capstart()
enablecontrol d1,ID_connect,0
enablecontrol d1,ID_disconnect,1
CASE ID_disconnect
SendMessage hWndCap1, WM_CAP_DRIVER_DISCONNECT, 0, 0
enablecontrol d1,ID_connect,1
enablecontrol d1,ID_disconnect,0
CASE ID_settings
'no settings control yet
CASE ID_capture
capSaveFrame()
ENDSELECT
ENDSELECT
RETURN
ENDSUB

sub capStart(opt index=1:int)
if hWndCap1=0 then return
SendMessage hWndCap1, WM_CAP_DRIVER_CONNECT, 0, 0
SendMessage hWndCap1, WM_CAP_SET_PREVIEWRATE, 50, 0
SendMessage hWndCap1, WM_CAP_SET_OVERLAY, TRUE, 0
SendMessage hWndCap1, WM_CAP_SET_PREVIEW, true, 0
return
endsub

sub capSaveFrame
ISTRING path[300]
INT cnt
POINTER path_end
IF hWndCap1=0 then return
path = GetStartPath
path_end = &path + len(path)
for cnt = 1 to 0x7FFFFFFF
wsprintf(path_end, "%.5d.bmp", cnt)
if PathFileExists(path) = FALSE
if SendMessage(hWndCap1, WM_CAP_FILE_SAVEDIB, 0, &path)
SetCaption d1, "saved as " + *<STRING>path_end
else
SetCaption d1, "failed to save as " + *<STRING>path_end
DeleteFile path
endif
BreakFor
endif
next cnt
starttimer d1,4000
return
endsub


Screenie of above:


Pan and tilt program:

'PANTROLLER.EBA (Ebasic 1.63)
'Pan and tilt controller
'Boris Burke July 2008
'Code derived from Comm examples written by Paul Turley (Thank you)

DECLARE "Kernel32",CreateFileA(name:string,access:int,share:int,security:int,create:int,flags:int,handle:int),int
DECLARE "Kernel32",GetCommState(handle:int,lpDCBbb:DCBbb),int
DECLARE "Kernel32",SetCommState(handle:int,lpDCBbb:DCBbb),int
DECLARE "Kernel32",CloseHandle(handle:int),int
DECLARE "Kernel32",WriteFile(handle:int,buffer:string,count:int,written:pointer,overlapped:int),int
DECLARE "Kernel32",ReadFile(handle:int,buffer:string,count:int,bytesread:pointer,overlapped:int),int
declare import,Beep(dwFreq:INT,dwDuration:INT),INT
declare import,GetSysColor(int nIndex),int

SETID "OPENEXISTING",3
SETID "GENERICREAD",0x80000000
SETID "GENERICWRITE",0x40000000
SETID "NOPARITY",0
SETID "ODDPARITY",1
SETID "EVENPARITY",2
SETID "MARKPARITY",3
SETID "SPACEPARITY",4
SETID "ONESTOPBIT",0
SETID "TWOSTOPBITS",2

type COMMTIMEOUT
    DEF ReadIntervalTimeout:INT 
    DEF ReadTotalTimeoutMultiplier:INT
    DEF ReadTotalTimeoutConstant:INT
    DEF WriteTotalTimeoutMultiplier:INT
    DEF WriteTotalTimeoutConstant:INT
endtype

type DCBbb
    DEF DCBlength:int
    DEF BaudRate:int
DEF flags:int
    DEF wReserved:word
    DEF XonLim:word
    DEF XoffLim:word
    DEF ByteSize:char
    DEF Parity:char
    DEF StopBits:char
    DEF XonChar:char
    DEF XoffChar:char
    DEF ErrorChar:char
    DEF EofChar:char
    DEF EvtChar:char
    DEF wReserved1:word
ENDTYPE

def dcb1:DCBbb
def zhcom,success,written,bytesread:int
def data1:string
dcb1.DCBlength = 26

enum control_ids
ID_connect
ID_disconnect
ID_pan
ID_up
ID_right
ID_down
ID_left
ID_cent
endenum

DIALOG d1
CREATEDIALOG d1,0,0,168,332,0x80C80080|@MINBOX,0,"Pantroller",&d1_handler
CONTROL d1,@BUTTON,"Connect",10,14,147,24,0x50000000,ID_connect
CONTROL d1,@BUTTON,"Disconnect",10,46,147,24,0x50000000,ID_disconnect
CONTROL d1,@BUTTON,"UP",63,82,41,26,0x50000000,ID_up
CONTROL d1,@BUTTON,"RIGHT",108,110,41,26,0x50000000,ID_right
CONTROL d1,@BUTTON,"DOWN",63,142,41,26,0x50000000,ID_down
CONTROL d1,@BUTTON,"LEFT",18,110,41,26,0x50000000,ID_left
CONTROL d1,@BUTTON,"CENT",63,110,41,26,0x50000000,ID_cent
CONTROL d1,@GROUPBOX,"Pan",10,174,147,147,0x50000007,ID_pan
posx=150:posy=150
xx=0:yy=0
int bgc
showdialog d1
waituntil d1=0
end

SUB d1_handler
INT L,T,W,H
SELECT @MESSAGE
CASE @IDINITDIALOG
CENTERWINDOW d1
enablecontrol d1,ID_disconnect,0
bgc=getsyscolor(15)
CASE @IDCLOSEWINDOW
CLOSEDIALOG d1,@IDOK
case @idtimer
frontpen d1,bgc:move d1,posx-70,390-posy:print d1,"x"
select 1
case posx<100:posx=100:xx=0
case posx>200:posx=200:xx=0
case posy<100:posy=100:yy=0
case posy>200:posy=200:yy=0
case (mousedown(1)>0)
getsize(d1,l,t,w,h)
mx=mousex()-l:my=mousey()-t
if mx>35 and mx<137 and my>223 and my<325
xx=0:posx=mx+64
yy=0:posy=424-my
endif
endselect
if abs(xx)+abs(yy)>0 then setcontroltext d1,ID_cent,"STOP" else setcontroltext d1,ID_cent,"CENT"
posx=posx+xx:posy=posy+yy
frontpen d1,0:move d1,posx-70,390-posy:print d1,"x"
WriteFile(zhcom,"X"+chr$(posx),2,written,0):pause (3)
WriteFile(zhcom,"Y"+chr$(posy),2,written,0)
CASE @IDCONTROL
SELECT @CONTROLID
CASE ID_connect
zhcom = CreateFileA("COM1",@GENERICREAD | @GENERICWRITE,0,0,@OPENEXISTING,0,0)
if zhcom=-1
messagebox d1,"Cannot open port COM1","Error",@MB_OK
success = CloseHandle(zhcom)
CLOSEDIALOG d1,@IDOK
endif
IF GetCommState(zhcom,dcb1)
dcb1.BaudRate = 4800
dcb1.ByteSize = 8
dcb1.StopBits = @ONESTOPBIT
dcb1.Parity = @NOPARITY
IF SetCommState(zhcom,dcb1) = 0
messagebox d1,"Error setting com parameters","Error",@MB_OK
success = CloseHandle(zhcom)
CLOSEDIALOG d1,@IDOK
ENDIF
ENDIF
starttimer d1,15
enablecontrol d1,ID_connect,0
enablecontrol d1,ID_disconnect,1
CASE ID_disconnect
success = CloseHandle(zhcom)
enablecontrol d1,ID_connect,1
enablecontrol d1,ID_disconnect,0
CASE ID_up:yy++
CASE ID_right:xx++
CASE ID_down:yy--
CASE ID_left:xx--
CASE ID_cent
if abs(xx)+abs(yy)>0
xx=0:yy=0
setcontroltext d1,ID_cent,"CENT"
else
frontpen d1,bgc:move d1,posx-70,390-posy:print d1,"x"
posx=150:posy=150:xx=0:yy=0
frontpen d1,0:move d1,posx-70,390-posy:print d1,"x"
endif
ENDSELECT
ENDSELECT
RETURN
ENDSUB

sub pause(interval as int)
def p as uint
p=millisecs():do:until millisecs()-p>interval
return
endsub


Screenie of above:


The pan-tilt mechanism can be controlled from the â€ËÅ"pan’ box. Click and drag the target inside the box and the webcam will follow.

Video of mouse panning in action: http://www.zen86415.zen.co.uk/pantilt.MOV (5.7Mb Quicktime video format)

I would still like to know how to put these two programs into one.

For those interested, the microcontroller driven by this program is a PICAXE 14M (See http://www.rev-ed.co.uk/picaxe/) and here is the code it is running:

'CAM_PAN_TILT_02.BAS
'Boris Burke JULY 2008
â€ËÅ"PICAXE 14M
'Standerd R/C hobby servos on outputs 4 and 5
'LED on output 3 to indicate connection to PC

servo 4,150:servo 5,150
pause 2000
do
setfreq m8
serin 1,n2400,("X"),b0
serin 1,n2400,("Y"),b1
setfreq m4
high 3
servo 4,b1:servo 5,b0
pause 15
low 3
loop

Thank you for not breeding

sapero

July 27, 2008, 07:22:14 AM #2 Last Edit: July 27, 2008, 07:48:00 AM by sapero
Could you put your code into attachment? It is impossible to copy, everything goes into one line.
I've played with motion detection, and had same problem with delays. An additional thread for capture window helped me a lot.
This is my Aurora code:
// CAMERA_DATA is a custom structure
sub CreateCapWindow(CAMERA_DATA cd)
{
cd.hThread = CreateThread(0, 0, &CCamera_Create, &cd, 0, &cd.ThreadId);
}

sub CCamera_Create(CAMERA_DATA cd)
{
cd.hwnd = capCreateCaptureWindow("", WS_CHILD | WS_VISIBLE, cd.left,
cd.top, cd.width, cd.height, cd.hwndParent, 2100);

MSG message;
while (GetMessage(&message,0,0,0))
{
TranslateMessage(&message);
DispatchMessage(&message);
}
ExitThread(0);
}

The capture window is created in separate thread, and does not lag your main window. To terminate the message loop, I've used PostThreadMessage with WM_QUIT.

Boris

Thanks for trying, but Aurora code means very little to me. An additional thread for capture window sounds ideal. How can it be done in Ebasic?

I can copy all the contents of a CODE box by left-click three times in the box, then right-click>copy. It pastes into Ebasic editor (and any text editor) with proper formatting.
Thank you for not breeding

sapero

July 27, 2008, 02:14:55 PM #4 Last Edit: July 27, 2008, 02:17:26 PM by sapero
Attached is your 'two in one'. The capture window is created in separate thread.

Note, the overlay mode is not supported by all drivers, eg. my Logitech QuickCam pro 4000 does not support it, the WM_CAP_SET_OVERLAY is silently ignored.
You can use WM_CAP_DRIVER_GET_CAPS to detect if overlay and other capabilites are supported:

CAPDRIVERCAPS caps
SendMessage hWndCap1, WM_CAP_DRIVER_GET_CAPS, len(caps), &caps
if (caps.fHasOverlay)
   EnableWindow(overlay checkbox)
endif


* The DCB structure from my headers has been fixed, see in my sig.

Boris

Thanks Sapero. This will require some studying.
Thank you for not breeding