My raspberry pi 'stereo camera rig'. Good old balsa wood and insulating tape. |
Quick Instructions on getting the code
In this post I get to my first version of a working camera api, which can be downloaded here:
http://www.cheerfulprogrammer.com/downloads/pi_eyes_stage2/picam.zip
Note that to use it, you'll need to download and build the raspberry pi userland code from here. Mine is stored in /opt/vc/userland-master.
I'll go into more details once I have something I'm really happy with!
Writing the actual code...
The basic architecture of the camera system is quite simple once you get over the total lack of documentation of the fairly complex mmal layer... We basically:
- Start up mmal
- Create a 'camera component'
- Tell its 'video output port' to call a callback each time it fills in a new buffer
- In the callback we:
- Lock the buffer
- Read it
- Unlock it
- Give it back to be the port to be recycled
- And when all is done, we kill the camera component and bail out
The callback is called from a seperate thread, so once things are moving the main application can carry on as normal. This lends itself well to a simple initial api of just:
- StartCamera(some setup options + callback pointer)
- StopCamera()
The main bit of code I'm going to keep from the raspberry pi userland code is the raspicamcontrol stuff, which wraps up setting parameters on the camera in a simple api.
.... imagine moments of intense programming with blondie on in the background here ....
.... imagine moments of intense programming with blondie on in the background here ....
It's a few hours later and I've finished revision one. I've got a basic camera api that gets initialised, does stuff for a while then shuts down. Here's the first 'application' that uses it:
#include <stdio.h>
#include <unistd.h>
#include "camera.h"
void CameraCallback(CCamera* cam, const void* buffer, int buffer_length)
{
printf("Do stuff with %d bytes of data\n",buffer_length);
}
int main(int argc, const char **argv)
{
printf("PI Cam api tester\n");
StartCamera(1280,720,30,CameraCallback);
sleep(10);
StopCamera();
}
Neat! With that code I run the application and get this print out
PI Cam api tester
Creating video port pool with 3 buffers of size 1382400
mmal: mmal_vc_port_parameter_set: failed to set port parameter 64:0:ENOSYS
mmal: Function not implemented
Sent buffer 0 to video port
Sent buffer 0 to video port
Sent buffer 0 to video port
Camera successfully created
Do stuff with 1382400 bytes of data
Do stuff with 1382400 bytes of data
Do stuff with 1382400 bytes of data
//... repeat every frame for 10 seconds...
Do stuff with 1382400 bytes of data
Do stuff with 1382400 bytes of data
Do stuff with 1382400 bytes of data
Shutting down camera
Most of the magic is inside 2 files:
- CameraControl.h and CameraControl.cpp - basically just a version of the original RaspiCamControl.c without command line parsing in.
- Camera.h and Camera.cpp - my new camera api
I've posted the full files online for download, but will go over the key bits here. The beefy one is the camera initialization - CCamera::Init
Camera Initialisation
Basic setup / creation of camera component
Camera Initialisation
Basic setup / creation of camera component
bool CCamera::Init(int width, int height, int framerate, CameraCBFunction callback)
{
//init broadcom host - QUESTION: can this be called more than once??
bcm_host_init();
//store basic parameters
Width = width;
Height = height;
FrameRate = framerate;
Callback = callback;
// Set up the camera_parameters to default
raspicamcontrol_set_defaults(&CameraParameters);
MMAL_COMPONENT_T *camera = 0;
MMAL_ES_FORMAT_T *format;
MMAL_PORT_T *preview_port = NULL, *video_port = NULL, *still_port = NULL;
MMAL_STATUS_T status;
//create the camera component
status = mmal_component_create(MMAL_COMPONENT_DEFAULT_CAMERA, &camera);
if (status != MMAL_SUCCESS)
{
printf("Failed to create camera component\n");
return false;
}
//check we have output ports
if (!camera->output_num)
{
printf("Camera doesn't have output ports");
mmal_component_destroy(camera);
return false;
}
//get the 3 ports
preview_port = camera->output[MMAL_CAMERA_PREVIEW_PORT];
video_port = camera->output[MMAL_CAMERA_VIDEO_PORT];
still_port = camera->output[MMAL_CAMERA_CAPTURE_PORT];
// Enable the camera, and tell it its control callback function
status = mmal_port_enable(camera->control, CameraControlCallback);
if (status != MMAL_SUCCESS)
{
printf("Unable to enable control port : error %d", status);
mmal_component_destroy(camera);
return false;
}
// set up the camera configuration
{
MMAL_PARAMETER_CAMERA_CONFIG_T cam_config;
cam_config.hdr.id = MMAL_PARAMETER_CAMERA_CONFIG;
cam_config.hdr.size = sizeof(cam_config);
cam_config.max_stills_w = Width;
cam_config.max_stills_h = Height;
cam_config.stills_yuv422 = 0;
cam_config.one_shot_stills = 0;
cam_config.max_preview_video_w = Width;
cam_config.max_preview_video_h = Height;
cam_config.num_preview_video_frames = 3;
cam_config.stills_capture_circular_buffer_height = 0;
cam_config.fast_preview_resume = 0;
cam_config.use_stc_timestamp = MMAL_PARAM_TIMESTAMP_MODE_RESET_STC;
mmal_port_parameter_set(camera->control, &cam_config.hdr);
}
This first section is pretty simple, albiet fairly long. It's just:
- Creating the mmal camera component
- Getting the 3 'output ports'. The main one we're interested in is the video port, but as far as I can tell the others still need setting up for correct operation.
- Enabling the 'control' port and providing a callback. This basically gives the camera a way of providing us with info about changes of state. Not doing anything with this yet though.
- Filling out a camera config structure, then sending it to the camera control port
Now we have a camera component running, the next step is to configure those output ports:
// setup preview port format - QUESTION: Needed if we aren't using preview?
format = preview_port->format;
format->encoding = MMAL_ENCODING_OPAQUE;
format->encoding_variant = MMAL_ENCODING_I420;
format->es->video.width = Width;
format->es->video.height = Height;
format->es->video.crop.x = 0;
format->es->video.crop.y = 0;
format->es->video.crop.width = Width;
format->es->video.crop.height = Height;
format->es->video.frame_rate.num = FrameRate;
format->es->video.frame_rate.den = 1;
status = mmal_port_format_commit(preview_port);
if (status != MMAL_SUCCESS)
{
printf("Couldn't set preview port format : error %d", status);
mmal_component_destroy(camera);
return false;
}
//setup video port format
format = video_port->format;
format->encoding = MMAL_ENCODING_I420; //not opaque, as we want to read it!
format->encoding_variant = MMAL_ENCODING_I420;
format->es->video.width = Width;
format->es->video.height = Height;
format->es->video.crop.x = 0;
format->es->video.crop.y = 0;
format->es->video.crop.width = Width;
format->es->video.crop.height = Height;
format->es->video.frame_rate.num = FrameRate;
format->es->video.frame_rate.den = 1;
status = mmal_port_format_commit(video_port);
if (status != MMAL_SUCCESS)
{
printf("Couldn't set video port format : error %d", status);
mmal_component_destroy(camera);
return false;
}
//setup still port format
format = still_port->format;
format->encoding = MMAL_ENCODING_OPAQUE;
format->encoding_variant = MMAL_ENCODING_I420;
format->es->video.width = Width;
format->es->video.height = Height;
format->es->video.crop.x = 0;
format->es->video.crop.y = 0;
format->es->video.crop.width = Width;
format->es->video.crop.height = Height;
format->es->video.frame_rate.num = 1;
format->es->video.frame_rate.den = 1;
status = mmal_port_format_commit(still_port);
if (status != MMAL_SUCCESS)
{
printf("Couldn't set still port format : error %d", status);
mmal_component_destroy(camera);
return false;
}
This is 3 almost identical bits of code - one for the preview port (which would be used for doing the full screen preview of the feed if we were using it), one for the video port (that's the one we're interested in) and one for the still port (presumably for capturing stills). If you read the code it's pretty much just plugging in the numbers provided to configure the camera. The most important part, highlighted in red is where we set the video port format to I420 encoding (the native format of the camera). By setting it correctly, this tells mmal that we will be providing a callback for the video output later, and it'll be wanting all the data thankyou very much! Otherwise it just passes in the buffer headers but no actual output... Point of note - I tried setting the format to ABGR, but the camera just output I420 data in a dodgy layout, so it's going to need converting.
Create a buffer pool for the video port to write to
//setup video port buffer and a pool to hold them
video_port->buffer_num = 3;
video_port->buffer_size = video_port->buffer_size_recommended;
MMAL_POOL_T* video_buffer_pool;
printf("Creating video port pool with %d buffers of size %d\n", video_port->buffer_num, video_port->buffer_size);
video_buffer_pool = mmal_port_pool_create(video_port, video_port->buffer_num, video_port->buffer_size);
if (!video_buffer_pool)
{
printf("Couldn't create video buffer pool\n");
mmal_component_destroy(camera);
return false;
}
This little chunk is the first properly 'new' bit when compared to the raspivid. It creates a pool of buffers that we'll be providing to the video port to write the frames to. For now we're just creating it, but later we'll pass all the buffers to the video port and then begin capturing! The buffer_num is 3, as that gives you enough time to have the camera writing 1 buffer, while you read another, with an extra one in the middle for safety. The recommended buffer size comes from the format we specified earlier.
Enable stuff
//enable the camera
status = mmal_component_enable(camera);
if (status != MMAL_SUCCESS)
{
printf("Couldn't enable camera\n");
mmal_port_pool_destroy(video_port,video_buffer_pool);
mmal_component_destroy(camera);
return false;
}
//apply all camera parameters
raspicamcontrol_set_all_parameters(camera, &CameraParameters);
//setup the video buffer callback
status = mmal_port_enable(video_port, VideoBufferCallback);
if (status != MMAL_SUCCESS)
{
printf("Failed to set video buffer callback\n");
mmal_port_pool_destroy(video_port,video_buffer_pool);
mmal_component_destroy(camera);
return false;
}
This pretty simple code enables the camera, sends it a list of setup parameters using the cam control code, then enables the video port. Note the port enable call, which tells the video port about our VideoBufferCallback function, which we want calling for each frame received from the camera.
Give the buffers to the video port
//send all the buffers in our pool to the video port ready for use
{
int num = mmal_queue_length(video_buffer_pool->queue);
int q;
for (q=0;q<num;q++)
{
MMAL_BUFFER_HEADER_T *buffer = mmal_queue_get(video_buffer_pool->queue);
if (!buffer)
printf("Unable to get a required buffer %d from pool queue", q);
if (mmal_port_send_buffer(video_port, buffer)!= MMAL_SUCCESS)
printf("Unable to send a buffer to encoder output port (%d)", q);
printf("Sent buffer %d to video port\n");
}
}
OK, so this one looks a bit odd! The basic idea is that we created a pool of 3 buffers earlier, which is basically a queue of pointers to unused blocks of memory. This bit of code removes each buffer from the pool and sends it into the video port. In effect, we're handing the video port the blocks of memory it'll use to store frames in.
Begin capture and return SUCCESS!
//begin capture
if (mmal_port_parameter_set_boolean(video_port, MMAL_PARAMETER_CAPTURE, 1) != MMAL_SUCCESS)
{
printf("Failed to start capture\n");
mmal_port_pool_destroy(video_port,video_buffer_pool);
mmal_component_destroy(camera);
return false;
}
//store created info
CameraComponent = camera;
BufferPool = video_buffer_pool;
//return success
printf("Camera successfully created\n");
return true;
As our final trick, we set the 'capturing' setting to 1, and if all goes well that VideoBufferCallback function should start getting called.
The video callback
What also deserves a mention is the video callback:
void CCamera::OnVideoBufferCallback(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer)
{
//check if buffer has data in
if(buffer->length)
{
//got data so lock the buffer, call the callback so the application can use it, then unlock
mmal_buffer_header_mem_lock(buffer);
Callback(this,buffer->data,buffer->length);
mmal_buffer_header_mem_unlock(buffer);
}
// release buffer back to the pool
mmal_buffer_header_release(buffer);
// and send one back to the port (if still open)
if (port->is_enabled)
{
MMAL_STATUS_T status;
MMAL_BUFFER_HEADER_T *new_buffer;
new_buffer = mmal_queue_get(BufferPool->queue);
if (new_buffer)
status = mmal_port_send_buffer(port, new_buffer);
if (!new_buffer || status != MMAL_SUCCESS)
printf("Unable to return a buffer to the video port\n");
}
}
The first bit should be fairly simple - we check if the buffer has any data in (hopefully it always does!), and if so, lock it, call the users callback (remember they passed it into the Init function) so the application can have it's merry way with the frame data, then unlock it.
The next bit of code is a little more confusing. It's the second part of the buffer management stuff we saw earlier. Once the buffer is used, we first release it. This frees it up and effectively puts it back in the pool from whence it came! However, the video port is now down a buffer, so (if its still open), we pull the buffer back out of the pool and send it back into the video port ready for reuse.
What is interesting here is that we have control over when the buffer is returned to the video port. I can see down the line doing stuff with the gpu, where I extend the life time of a buffer over a frame so compute shaders can do stuff with it!
What's Next?
Well, I can now read data, at 1080p/30hz if I want but the next question is what to do with it! Currently it's in the funky I420 encoding (basically a common form where 1 channel represents the 'brightness' and the other 2 channels represent the colour). To be useful it'll need converting to rgb, and displaying on screen. I know from Pierre's work that opencv isn't ideal for this, so while I'll need it for proper image processing I think I'll have a look at faster ways to get from camera output -> me on the tv!
1080p/30hz sounds promising.
ReplyDeleteCan you get 60hz using less resolution? Have u tried 1024x768, or 320x240 (120fps??) ? I think that's more interesting than getting fullHD. The examples of code for openCV I saw were at 320x240.
Well the camera can do 720p at 60hz or 640p at 90hz so in theory you could read data at that rate. That said, exactly what you could do with it in such a short space of time is another question altogether!
DeleteThe camera chip itself claims those rates, but nothing beyond 30 fps has so far been demonstrated on the Raspberry Pi. JamesH (author of raspistill / raspivid) tried the register settings Omnivision provided, but couldn't get them working.
DeleteYou've got a great setup for a robot. Parallel processing of images for 3d.
ReplyDeleteLet's put those webcams in a rigid frame and start calibrating them for 3d reconstruction with opencv. You'll get something similar to the kinect depth images.
glad to see your progress. Keep updating your research!!
This comment has been removed by the author.
ReplyDeleteThankyou!
ReplyDeletefor conversion to opencv in callback:
ReplyDeletecv::Mat myuv(HEIGHT + HEIGHT/2, WIDTH, CV_8UC1, (unsigned char*)buffer);
cv::Mat mrgb(HEIGHT, WIDTH, CV_8UC4, dest);
cv::cvtColor(myuv, mrgb, CV_YUV2RGBA_NV21);
cv::imshow("video", mrgb);
cv::waitKey(1);
MestiQQ Adalah perusahaan judi online KELAS DUNIA ber-grade A
ReplyDeleteSudah saatnya Pencinta POKER Bergabung bersama kami dengan Pemain - Pemain RATING-A
Hanya dengan MINIMAL DEPOSIT RP. 10.000 anda sudah bisa bermain di semua games.
Kini terdapat 8 permainan yang hanya menggunakan 1 User ID & hanya dalam 1 website.
( POKER, DOMINO99, ADU-Q, BANDAR POKER, BANDARQ, CAPSA SUSUN, SAKONG ONLINE, BANDAR66 )
PROSES DEPOSIT DAN WITHDRAWAL CEPAT Dan AMAN TIDAK LEBIH DARI 2 MENIT.
100% tanpa robot, 100% Player VS Player.
Live Chat Online 24 Jam Dan Dilayani Oleh Customer Service Profesional.
Segera DAFTARKAN diri anda dan Coba keberuntungan anda bersama MestiQQ
** Register/Pendaftaran : WWW-MestiQQ-POKER
Jadilah Milionare Sekarang Juga Hanya di MestiQQ ^^
Untuk Informasi lebih lanjut silahkan Hubungi Customer Service kami :
BBM : 2C2EC3A3
WA: +855966531715
SKYPE : mestiqqcom@gmail.com
ULANG TAHUN BOLAVITA YANG KE 6
ReplyDeleteSehubungan dengan ulang tahun Bolavita yg ke-6
Bolavita sebagai Agen Taruhan Judi Online terbaik dan terpercaya akan menbagikan FREEBET & FREECHIP kepada member setia bola vita.
FREEBET & FREECHIP hingga 2.000.000 (Dua juta rupiah)
syarat dan ketentuan cek = https://bit.ly/2SsDinv
Gabung sekarang dan klaim bonusnya!
Info Lengkap :
WA: 0812-2222-995 !
BBM : BOLAVITA
WeChat : BOLAVITA
Line : cs_bolavita
Info Togel =>> PREDIKSI TOGEL TERPERCAYA
Happy Good Day para member setia AGENS128, oke gengs kali ini kami akan memberikan refrensi untuk kalian semua yang sedang mencari permainan TOTO ONLINE dengan bonus terbesar yang akan kami berikan kepada kalian semua, jadi untuk kalian yang mau mencoba bermain bersama kami dengan bonus yang besar yang akan kami berikan kepada kalian,kalian bisa mendaftarkan diri kalian sekarang juga dan menangkan BONUS YANG BESAR HANYA BERSAMA AGENS128.
ReplyDeleteUntuk keterangan lebih lanjut, segera hubungi kami di:
BBM : D8B84EE1 atau AGENS128
WA : 0852-2255-5128
Ayo tunggu apalagi !!
Any chance that the http://www.cheerfulprogrammer.com/downloads/pi_eyes_stage2/picam.zip becomes active again? Not sure if this is equivalent, https://github.com/HesselM/picam but I had problems building this one.
ReplyDeleteI saved as a favorite it to my bookmark website list and will be checking back soon. Please visit my web site as well and tell me your opinion.
ReplyDelete무료야설
립카페
마사지블루
건전마사지
바카라사이트
kralbet
ReplyDeletebetpark
tipobet
slot siteleri
kibris bahis siteleri
poker siteleri
bonus veren siteler
mobil ödeme bahis
betmatik
DDE
شركة تنظيف سجاد بالجبيل MzEqn1IxWf
ReplyDeleteشركة مكافحة النمل الابيض بالجبيل FJcIVySgaM
ReplyDelete