DigiCam Example
Posted: Fri Jul 02, 2004 3:56 pm
This is a spin-off from this thread.
I'm playing with a cheap "mini key-cam" (£9.99 TG Huges, UK) on my win98SE machine. USB drivers are fairly stable but if my PB program craps out for any reason I often need to replug the camera or even reboot. Fed up with doing this and having just converted some file-mapping code (see this thread) I thought why not combine some code and make a stable cam server then have a client I can safely experiment on. So here's the results for anyone else who wants it. I think the code is pretty clear but if not read the two threads mentioned above and if you still have q's then post them here.
The server code ("CAMSVR") is split into a short "header" and the main file. The second include in the main file is from another yet another thread (do a search on these forums, I'm in lazy mode atm). It only handles one client but I plan to support multiple clients and network connections. See below for client info and code.
NB: It's all/always work-in-progress!
camsvr-h.pb
camsvr.pb
camsvr-client.pb
Here's the client code, which is much easier to digest and play around with now the actual camera capture code has been extracted. As presented it simply displays a greyscale image of the latest frame when the server says "Hey! New data!". The part you may want to adapt for your own use is the, gasp, newframe() proc.
You'll notice some code that has been commented out. One line simulates how the display will look on the four-colour Cybiko (another of my cheap toys).
The section called "Delta & Threashold" uses the fact that the server caches ten frames, all of which are available via the file-mapping. I'm interested in image recognition so this was/is the beginnings of removing some noise from the picture by subtracting the previous frame (luminance only) and applying a threshold. The "If" statement around the "plot" was intended to map noisy areas by leaving the camera running for a few minutes. The interesting thing (probably obvious to some) is the resulting picture forms a crude first stage edge detection. Try it yourself to see what I mean. You'll need a carefully lighted rigorous scientific testing environment similar to mine... oh ok, I just plonked it on my desk which is in front of a window (it's day time). I pointed the camera across the room so I could easily wave my hand in front of it to check it's working (also positioned to avoid scaring myself with my own ugly mutt). Remember mine's a cheap camera, 5 fps at 352x288 (or there-abouts) so you may get different results, but post a commented here if you do try it. I'm interested because I initially assumed my cam simply produced a noisy picture, which wasn't the case.
Have fun with the code.
I'm playing with a cheap "mini key-cam" (£9.99 TG Huges, UK) on my win98SE machine. USB drivers are fairly stable but if my PB program craps out for any reason I often need to replug the camera or even reboot. Fed up with doing this and having just converted some file-mapping code (see this thread) I thought why not combine some code and make a stable cam server then have a client I can safely experiment on. So here's the results for anyone else who wants it. I think the code is pretty clear but if not read the two threads mentioned above and if you still have q's then post them here.
The server code ("CAMSVR") is split into a short "header" and the main file. The second include in the main file is from another yet another thread (do a search on these forums, I'm in lazy mode atm). It only handles one client but I plan to support multiple clients and network connections. See below for client info and code.
NB: It's all/always work-in-progress!
camsvr-h.pb
Code: Select all
; CAMSVR specific messages
Enumeration #WM_USER
#WM_CAMSVR_NEWDATA
#WM_CAMSVR_NEWCLIENT
;#WM_CAMSVR_
#WM_CAMSVR_CLIQUIT
#WM_CAMSVR_SVRQUIT
EndEnumeration
Code: Select all
IncludeFile "camsvr-h.pb"
IncludeFile "../../pb-includes/avicapincs.pb"
#WM_CAP_START = #WM_USER
#WM_CAP_DRIVER_CONNECT = #WM_CAP_START + 10
#WM_CAP_DRIVER_DISCONNECT = #WM_CAP_START + 11
#WM_CAP_DRIVER_GET_CAPS = #WM_CAP_START + 14
#WM_CAP_SET_PREVIEW = #WM_CAP_START + 50
#WM_CAP_SET_PREVIEWRATE = #WM_CAP_START + 52
#WM_CAP_STOP = #WM_CAP_START + 68
Structure camsvrT
fw.l: fh.l ; Frame Width, Height
ppf.l ; Pixels Per Frame
bpf.l ; Bytes Per Frame
nfb.l ; Number of frame buffers to allocate
bin.l ; Buffer Idx Number
fba.l ; Frame Buffer Address
ndf.l ; New Data Flag
nda.l ; New Data Address
dct.l ; Data Conversion Type
hWndS.l ; Win Handle: CAMSVR
hWndC.l ; Win Handle: client
hFMO.l ; Handle: File Mapping Object
quit.l ; Flag to tell server to quit (from client/s)
EndStructure
Global cs.camsvrT
;Global hClient.l ; Handle of client app (only one supported atm)
Procedure WindowCallback(WindowID,Message,wParam,lParam)
Protected r.l
r=#PB_ProcessPureBasicEvents
Select Message
Case #WM_CAMSVR_NEWCLIENT
cs\hWndC = lParam
Case #WM_CAMSVR_CLIQUIT
cs\hWndC = 0
Case #WM_CAMSVR_SVRQUIT
cs\quit=1
EndSelect
ProcedureReturn r
EndProcedure
Procedure.l init(w.l, h.l)
Protected f.l, ccw.l, e.l
cs\fw=w: cs\fh=h ; Frame Width, Height
cs\ppf=w*h ; Pixels Per Frame
cs\bpf=cs\ppf*3 ; Bytes Per Frame
cs\nfb=10 ; Number of frame buffers to allocate
cs\bin=0 ; Buffer Idx Number
cs\ndf=0 ; New Data Flag
cs\nda=0 ; New Data Address
cs\quit=0 ; Svr quit flag
; Set up window
f = #PB_Window_ScreenCentered | #PB_Window_SystemMenu
If OpenWindow(0, 0, 0, w+20, h+20, f, "CAMSVR")=0
Debug "ERROR: Failed to open PB window!": ProcedureReturn 0
EndIf
CreateGadgetList(WindowID())
CreateImage(0,w,h)
ImageGadget(0,10,10,w,h,UseImage(0))
SetWindowCallback(@WindowCallback())
; Start capture
If OpenLibrary(0, "avicap32.dll")=0
Debug "ERROR: Failed to open avicap32.dll!": ProcedureReturn 0
EndIf
ccw = IsFunction(0, "capCreateCaptureWindowA")
f = #WS_CHILD|#WS_VISIBLE
cs\hWndS = CallFunctionFast(ccw, "CAMSVR", f, 0,0, w,h, GadgetID(0), 1)
; Allocate frame buffers
;Dim fdata.b(cs\bpf*cs\nfb)
;HANDLE CreateFileMapping(
; HANDLE hFile, // handle to file to map
; LPSECURITY_ATTRIBUTES lpFileMappingAttributes, // optional security attributes
; DWORD flProtect, // protection for mapping object
; DWORD dwMaximumSizeHigh, // high-order 32 bits of object size
; DWORD dwMaximumSizeLow, // low-order 32 bits of object size
; LPCTSTR lpName // name of file-mapping object
; );
cs\hFMO=CreatefileMapping_($FFFFFFFF, #Null, #PAGE_READWRITE, 0, cs\bpf*cs\nfb, "CAMSVR")
If cs\hFMO=0: Debug "ERROR: Mapping failed!": ProcedureReturn 0: EndIf
;LPVOID MapViewOfFile(
; HANDLE hFileMappingObject, // file-mapping object to map into address space
; DWORD dwDesiredAccess, // access mode
; DWORD dwFileOffsetHigh, // high-order 32 bits of file offset
; DWORD dwFileOffsetLow, // low-order 32 bits of file offset
; DWORD dwNumberOfBytesToMap // number of bytes to map
; );
cs\fba=MapViewOfFile_(cs\hFMO, #FILE_MAP_WRITE, 0, 0, 0)
If cs\fba=0: Debug "ERROR: Mapping View failed!": ProcedureReturn 0: EndIf
ProcedureReturn 1
EndProcedure
Procedure.l cbNewFrame(lwnd.l, *vh.VIDEOHDR)
cs\nda=cs\fba+cs\bin*cs\bpf
RtlMoveMemory_ (cs\nda, *vh\lpData, cs\bpf);*vh\dwBytesUsed)
If cs\hWndC
SendMessage_(cs\hWndC, #WM_CAMSVR_NEWDATA, 0, cs\bin);*cs\bpf)
EndIf
cs\bin+1: If cs\bin=>cs\nfb: cs\bin=0: EndIf
cs\ndf=1
EndProcedure
Procedure main()
Protected e.l
If init(320, 240)=0: Debug "ERROR: Init failed!": ProcedureReturn: EndIf
SendMessage_(cs\hWndS, #WM_CAP_DRIVER_CONNECT, 1, 0)
SendMessage_(cs\hWndS, #WM_CAP_SET_PREVIEW, #True, 0)
SendMessage_(cs\hWndS, #WM_CAP_SET_PREVIEWRATE, 15, 0)
capSetCallbackOnFrame(cs\hWndS, @cbNewFrame()) ; 320x240x3
Repeat
e=WaitWindowEvent()
Until e=#PB_Event_CloseWindow
SendMessage_(cs\hWndS, #WM_CAP_STOP, 0, 0)
SendMessage_(cs\hWndS, #WM_CAP_DRIVER_DISCONNECT, 0, 0)
UnmapViewOfFile_(cs\fba)
CloseHandle_(cs\hFMO)
DestroyWindow_(cs\hWndS)
CloseLibrary(0)
EndProcedure
main()
End
camsvr-client.pb
Here's the client code, which is much easier to digest and play around with now the actual camera capture code has been extracted. As presented it simply displays a greyscale image of the latest frame when the server says "Hey! New data!". The part you may want to adapt for your own use is the, gasp, newframe() proc.
You'll notice some code that has been commented out. One line simulates how the display will look on the four-colour Cybiko (another of my cheap toys).
The section called "Delta & Threashold" uses the fact that the server caches ten frames, all of which are available via the file-mapping. I'm interested in image recognition so this was/is the beginnings of removing some noise from the picture by subtracting the previous frame (luminance only) and applying a threshold. The "If" statement around the "plot" was intended to map noisy areas by leaving the camera running for a few minutes. The interesting thing (probably obvious to some) is the resulting picture forms a crude first stage edge detection. Try it yourself to see what I mean. You'll need a carefully lighted rigorous scientific testing environment similar to mine... oh ok, I just plonked it on my desk which is in front of a window (it's day time). I pointed the camera across the room so I could easily wave my hand in front of it to check it's working (also positioned to avoid scaring myself with my own ugly mutt). Remember mine's a cheap camera, 5 fps at 352x288 (or there-abouts) so you may get different results, but post a commented here if you do try it. I'm interested because I initially assumed my cam simply produced a noisy picture, which wasn't the case.
Have fun with the code.
Code: Select all
IncludeFile "camsvr-h.pb"
Structure cscT
fw.l: fh.l ; Frame Width, Height
ppf.l ; Pixels Per Frame
bpf.l ; Bytes Per Frame
nfb.l ; Number of frame buffers to allocate
fba.l ; Frame Buffer Address
hWndS.l ; Win Handle: CAMSVR
hFMO.l ; Handle: File Mapping Object
quit.l ; Quit Flag
EndStructure
Global csc.cscT
Procedure newframe(bin.l)
Protected r.l, g.l, b.l
Protected pp.l, ps.l, pt.l
Protected v.l, f.l, vp.l
UseImage(0)
StartDrawing(ImageOutput())
ps = csc\fba+bin*csc\bpf
bin-1: If bin<0: bin=9: EndIf
pp = csc\fba+bin*csc\bpf
For y=0 To ImageHeight()-1
For x=0 To ImageWidth()-1
b=PeekB(ps)&$FF: g=PeekB(ps+1)&$FF: r=PeekB(ps+2)&$FF
;v=(r+g+b)/3 ; Luminance
v=r*0.3 + g*0.59 + b*0.11 ; Luminance (alt method from ogl forums)
; Delta & Threashold
;b=PeekB(pp)&$FF: g=PeekB(pp+1)&$FF: r=PeekB(pp+2)&$FF
;vp = (r+g+b)/3: v = Abs(v-vp)
;If v>32: v=255: Else: v=0: EndIf
;v = 3*(v/255.0): v = 255*(v/3.0) ; Simulated Cybiko display
r=v: g=v: b=v ; Greyscale
If v ; Only changes
Plot(x, ImageHeight()-y, RGB(r,g,b))
EndIf
ps+3: pp+3
Next
Next
StopDrawing()
StartDrawing(WindowOutput())
DrawImage(UseImage(0), 10, 10)
StopDrawing()
EndProcedure
Procedure WindowCallback(WindowID,Message,wParam,lParam)
Protected r.l
r=#PB_ProcessPureBasicEvents
Select Message
Case #WM_CAMSVR_NEWDATA
newframe(lParam)
Case #WM_CAMSVR_SVRQUIT
csc\quit=1
EndSelect
ProcedureReturn r
EndProcedure
Procedure.l init(w.l, h.l)
Protected f.l
csc\fw=w: csc\fh=h ; Frame Width, Height
csc\ppf=w*h ; Pixels Per Frame
csc\bpf=csc\ppf*3 ; Bytes Per Frame
csc\nfb=10 ; Number of frame buffers to allocate
; Set up window
f = #PB_Window_ScreenCentered | #PB_Window_SystemMenu
If OpenWindow(0, 0, 0, w+20, h+20, f, "CAMSVR Client")=0
Debug "ERROR: Failed to open PB window!": ProcedureReturn 0
EndIf
CreateGadgetList(WindowID())
CreateImage(0,w,h)
ImageGadget(0,10,10,w,h,UseImage(0))
SetWindowCallback(@WindowCallback())
; Connect to CamSvr
csc\hWndS=FindWindow_(0,"CAMSVR")
If csc\hWndS=0: Debug "ERROR: Failed to find CAMSVR!": ProcedureReturn 0: EndIf
;HANDLE CreateFileMapping(
; HANDLE hFile, // handle to file to map
; LPSECURITY_ATTRIBUTES lpFileMappingAttributes, // optional security attributes
; DWORD flProtect, // protection for mapping object
; DWORD dwMaximumSizeHigh, // high-order 32 bits of object size
; DWORD dwMaximumSizeLow, // low-order 32 bits of object size
; LPCTSTR lpName // name of file-mapping object
; );
csc\hFMO=CreatefileMapping_($FFFFFFFF, #Null, #PAGE_READWRITE, 0, csc\bpf*csc\nfb, "CAMSVR")
If csc\hFMO=0: Debug "ERROR: Mapping failed!": ProcedureReturn 0: EndIf
;LPVOID MapViewOfFile(
; HANDLE hFileMappingObject, // file-mapping object to map into address space
; DWORD dwDesiredAccess, // access mode
; DWORD dwFileOffsetHigh, // high-order 32 bits of file offset
; DWORD dwFileOffsetLow, // low-order 32 bits of file offset
; DWORD dwNumberOfBytesToMap // number of bytes to map
; );
csc\fba=MapViewOfFile_(csc\hFMO, #FILE_MAP_WRITE, 0, 0, 0)
If csc\fba=0: Debug "ERROR: Mapping View failed!": ProcedureReturn 0: EndIf
SendMessage_(csc\hWndS, #WM_CAMSVR_NEWCLIENT, 0, WindowID()) ; Register client
ProcedureReturn 1
EndProcedure
Procedure main()
Protected e.l
If init(320, 240)=0: Debug "ERROR: Init failed!": ProcedureReturn: EndIf
Repeat
e=WaitWindowEvent()
Until e=#PB_Event_CloseWindow
UnmapViewOfFile_(csc\fba)
CloseHandle_(csc\hFMO)
SendMessage_(csc\hWndS, #WM_CAMSVR_CLIQUIT, 0, 0)
EndProcedure
main()
End