Friday 6 July 2012

Fixes in OpenCV and RTSP

We've finally got it going!

I'll get onto what was wrong in a second but first I want to share this line with you:

    CvCapture *camera = cvCreateFileCapture("rtsp://admin:admin@192.168.1.128/ch1-s1?tcp"); 

As you can see it is the same as my camera connection url in the previous post except I've added ?tcp to the end. I've found that this forces the rtsp connection to run using the tcp protocol instead of the udp protocol which is useful if you can't afford to have any packet loss. This gives us a decent picture but still performed very poorly, freezing often and dropping the frame rate down to something terrible.



I also found with this that occasionally the connection would be dropped and the code I had before would have no way of recovering so would just exit. Hence I present to you the revised code which upon receiving NULL when trying to get an image takes this as the connection being down and re-establishes it.

/* 
 * File:   main.cpp
 * Author: Ryan
 *
 * Created on October 27, 2011, 2:23 PM
 */

#include <cstdlib>
#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/contrib/contrib.hpp"
#include "opencv2/features2d/features2d.hpp"
#include "opencv2/legacy/legacy.hpp"
#include "opencv2/video/tracking.hpp"
#include <stdio.h> 

using namespace std;
using namespace cv;

int main() {

    //        CvCapture *camera = cvCreateFileCapture("/home/ryan/NetBeansProjects/OpenRTSP/video-H264-1");
    //        CvCapture *camera = cvCreateFileCapture("stream_fifo");    
    //        CvCapture *camera = cvCreateFileCapture("Wildlife.wmv");
    CvCapture *camera = cvCreateFileCapture("rtsp://admin:admin@192.168.1.128/ch1-s1?tcp"); //purple cam /?tcp
    //        CvCapture *camera=cvCreateFileCapture("http://192.168.1.5/image.jpg"); //black cam
    //    CvCapture *camera = cvCreateCameraCapture(0); //built in cam
    //    cvWaitKey(10000);
    if (camera == NULL) {
        printf("camera is null, aborting...");
        return -1;
    }
    printf("camera is not null\n");
    fflush(stdout);
    cvNamedWindow("img");
    int i = 0;
    while (cvWaitKey(10) != 27) {
        //         CvCapture *camera=cvCreateFileCapture("http://192.168.1.5/image.jpg");
        IplImage *img = cvQueryFrame(camera);
        if (img == NULL) {
            printf("img == null ");
            fflush(stdout);
            camera = cvCreateFileCapture("rtsp://admin:admin@192.168.1.128/ch1-s1?tcp");
        }
        cvShowImage("img", img);
        //        cvReleaseCapture(&camera); 
        //        printf("Image: %i\n", ++i);
    }
    cvReleaseCapture(&camera);
    return 0;
}

This code also has the new forced tcp urls in it although they are no longer needed since we have got to the root of the problem.

It turns out that our camera has a small buffer inside it which it uses to store the images it has captured for retrieval. If you don't retrieve the images at a fast enough rate then it is possible to try and retrieve one that is partially over-written or in the process of being written to by the camera's firmware. At least this is the theory my colleague came up with and I am inclined to agree (damn him for figuring it out before me!).

So the fix is pretty simple in this project and is already in the code above; we simply reduce the length of time between image retrieval from the camera by reducing the length of time the program waits for a key press in the loop. If you can see it the line of code is:

    while (cvWaitKey(10) != 27) {

which has been changed from:

    while (cvWaitKey(100) != 27) {

Simple eh! Well at least it is for this small test program. Our main program is considerably bigger with some pretty demanding routines so insuring fast image retrieval may not be such an easy task.

For now however, this is definitely being marked a success!

8 comments:

  1. It seems in my version adding ?tcp appears to be completely ignored :(
    If I do ffplay 'rtsp://192.168.0.151?tcp' - the ?tcp at the end is the magic that makes the picture perfect, but doing cvCreateFileCapture('rtsp://192.168.0.151?tcp'); vs removing the ?tcp has no effect :( - any ideas?

    ReplyDelete
    Replies
    1. Try typing the IP address into your web browser to be taken to the camera's web interface and see if it adds anything onto the end like mine has the /ch1-s1 then use that address instead.

      Delete
    2. No, it does not, once again, if I do ffplay 'rtsp://192.168.0.151?tcp' it plays with TCP and the quality is great. If I do cvCreateFileCapture('rtsp://192.168.0.151?tcp'); the image breaks and the bottom part is blurry as you show in your previous OpenCV post.

      Removing or adding ?tcp in OpenCV makes no difference. It looks like later version of FFmpeg deprecated specifying a transport via the URL in favour or specifying a:

      -rtsp_transport tcp

      Unfortunately there is no option to specify this in OpenCV that I can find. I can find at least one other person complaining about this: http://tech.groups.yahoo.com/group/OpenCV/message/87925

      Delete
  2. Yeah that other person you see is asking about it in the link you posted is me and I'm going to go out on a limb and guess you've posted on a YouTube video about this problem recently as well eh?

    Are you using the code above to test the camera? What if you reduce the frame rate of the camera, it might help. Also, if your camera has the option, you can try reducing the image quality to see if it helps.

    Really I'd recommend trying to find a way to use a http link to the camera if you can cause I'm still not that convinced by the RTSP protocol at all.

    ReplyDelete
    Replies
    1. Ah :) - I also filed a bug about this: http://code.opencv.org/issues/2235

      The problem is most (all?) IP cameras I can find use RTSP for audio+video, some cameras also support HTTP, but not RTSP over HTTP and no Audio over HTTP. Quicktime seems to play the stream without any issues, so I guess it can be made to work somehow: for instance, this yields good results:

      ffplay -rtsp_transport tcp 'rtsp://192.168.0.151'

      If only there was a way to specify this in OpenCV :/

      I'm using slightly different code, but using the code above yields the same results.

      Also, I tried to use live555's ProxyServer to create a proxy of the RTSP source and forced live555 to use TCP, when trying to read the stream with OpenCV with ?tcp at the end, it does not use tcp at all, but rather passes the URL to live555's ProxyServer and the server says no such path exists.

      It seems the only reason this parameter works for you is your IP camera is programmed to respond to ?tcp in the URL and change the way the stream is served, my Axis camera does this, but this Sony camera does not :( - So I'm still stuck in trying to find a way to tell OpenCV to use TCP.

      Delete
    2. I see you're possible work around at http://code.opencv.org/issues/2235, sadly it doesn't look like it is working for me.

      Interestingly the ?tcp doesn't appear to be working on Linux either.

      Delete
    3. You tried editing ./modules/highgui/src/cap_ffmpeg_impl.hpp and adding:

      AVDictionary *d = NULL;·
      av_dict_set(&d, "rtsp_transport", "tcp", 0);

      Above:

      int err = avformat_open_input(&ic, _filename, NULL, NULL);

      If you have, you need to remove all existing versions of opencv from your system and recompile opencv from source. I tested this on mac and linux and it forces a TCP connection :)

      Delete
    4. Cheers, I did try the orignal work around but not with the new part that has been added. Will try again at some point and let you know.

      Delete