Page 1 of 1

DigiCam Example

Posted: Fri Jul 02, 2004 3:56 pm
by dmoc
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

Code: Select all

; CAMSVR specific messages
Enumeration #WM_USER
  #WM_CAMSVR_NEWDATA
  #WM_CAMSVR_NEWCLIENT
  ;#WM_CAMSVR_
  #WM_CAMSVR_CLIQUIT
  #WM_CAMSVR_SVRQUIT
EndEnumeration
camsvr.pb

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