Hi! I've got an OrangeMicro iBot2 camera and I want to use it in my robotic project. You helped me to get it running somehow with the ovfx2 driver, but it still was not satisfying.
As the project starts to get completed, I really really needed to get it running, and so I sat for a day with an ov7620 and fx2 datasheets, some papers about Bayer demosaicking, and I fixed the driver. The attached patch: * Replaces the BGBR decoding with Bayer decoding, which is correct for this type of camera. It uses a mix of bilinear and nearest-neighbor approaches, which is fast and simple. * Fixes the fact that in raw RGB progressive mode the ov7620 chip sends every pixel twice at two different positions in the data stream. This is fixed by switching into interlaced mode. * Uses the QVGA60 bit to turn the interlaced mode into a progressive mode with half the data (the correct amount - 640x480 bytes, which is the number of pixels the camera has), and twice the framerate. * Suggests using "clockdiv=0" to achieve 60 Hz framerate. * Disables all kinds of image enhancement options which are OK for YUV mode, but disturb raw RGB mode. * Enables RGB matrix reweighing, since the colors "seep" into each other and this undoes that effect. * Removes the hardware image flip, since I was too lazy to recompute all the Bayer decoding for a mirrored image. All in all, it gives you a very nice smooth image with a very good (30 Hz or 60 Hz) framerate. Please test it, and if you're inclined to, merge into your ov511 driver suite. By the way, I'd like to see the ovfx2 merged into the 2.6 kernel, do you think it's a good idea now that it works rather well? -- Vojtech Pavlik SuSE Labs, SuSE CR
This patch implements proper bayer decoding for ov7620 rawmode. It also switches the OVFX2 camera into raw-RGB interlaced QVGA-60 mode, in which the camers sends all the raw data for its 640x480 bayer-coloured pixels in each field, while asserting the VREF signal on each field, and doesn't change the order of pixels in odd and even fields. This allows 60 Hz framerate at full speed, which can be achieved using the "clockdiv=0" parameter. Signed-off-by: Vojtech Pavlik <[EMAIL PROTECTED]> Makefile | 2 ov511.txt | 7 ++ ovfx2.c | 177 ++++++++++++++++++++++++++++++++++++-------------------------- ovfx2.h | 4 - 4 files changed, 114 insertions(+), 76 deletions(-) ================================================================================= diff -urN ov511-2.28/Makefile ov511-2.28-vp2/Makefile --- ov511-2.28/Makefile 2003-11-30 11:50:56.000000000 +0100 +++ ov511-2.28-vp2/Makefile 2004-11-14 19:57:46.000000000 +0100 @@ -127,4 +127,4 @@ ######################### Version independent targets ########################## clean: - rm -f *.o *.ko *.mod.* .[a-z]* core *.i + rm -rf *.o *.ko *.mod.* .[a-z]* core *.i *.orig *.rej diff -urN ov511-2.28/ov511.txt ov511-2.28-vp2/ov511.txt --- ov511-2.28/ov511.txt 2003-03-19 13:48:45.000000000 +0100 +++ ov511-2.28-vp2/ov511.txt 2004-11-14 21:03:15.622582480 +0100 @@ -287,6 +287,13 @@ Note: V4L2 custom controls and most other ioctls are always supported, regardless of this setting. This setting just enables VIDIOC_QUERYCAP. + NAME: clockdiv + TYPE: integer + DEFAULT: 1 (30 Hz framerate) + DESC: This allows tuning the framerate of the camera. Tested only on ovfx2, + setting this to 0 allows 60 Hz framerate, setting to greater values + decreases the framerate proportionately. + WORKING FEATURES: o Color streaming/capture at most widths and heights that are multiples of 8. diff -urN ov511-2.28/ovfx2.c ov511-2.28-vp2/ovfx2.c --- ov511-2.28/ovfx2.c 2004-07-16 01:32:08.000000000 +0200 +++ ov511-2.28-vp2/ovfx2.c 2004-11-14 21:17:01.389046896 +0100 @@ -1,6 +1,7 @@ /* OmniVision/Cypress FX2 Camera-to-USB Bridge Driver * * Copyright (c) 1999-2004 Mark W. McClelland, David Brownell + * Bayer decoding and 30/60 FPS support by Vojtech Pavlik <[EMAIL PROTECTED]> * Many improvements by Bret Wallach <[EMAIL PROTECTED]> * Color fixes by by Orion Sky Lawlor <[EMAIL PROTECTED]> (2/26/2000) * OV7620 fixes by Charl P. Botha <[EMAIL PROTECTED]> @@ -78,7 +79,7 @@ #define MAX_DATA_SIZE(w, h) (MAX_FRAME_SIZE(w, h) + sizeof(struct timeval)) /* Max size * 2 bytes per pixel average + safety margin */ -#define MAX_RAW_DATA_SIZE(w, h) ((w) * (h) * 2) +#define MAX_RAW_DATA_SIZE(w, h) ((w) * (h)) #define FATAL_ERROR(rc) ((rc) < 0 && (rc) != -EPERM) @@ -1209,7 +1210,7 @@ if (clockdiv >= 0) win.clockdiv = clockdiv; else - win.clockdiv = 0x01; /* Always use the max frame rate */ + win.clockdiv = 0x01; /* Use 30 FPS as default. 0x00 = 60 FPS */ rc = sensor_cmd(ov, OVCAMCHIP_CMD_S_MODE, &win); if (rc < 0) @@ -1269,20 +1270,37 @@ return rc; /********* The following settings are specific to this camera ********/ - i2c_w(ov, 0x03, 0x80); /* Hardcoded saturation value */ - i2c_w_mask(ov, 0x12, 0x48, 0x48); /* Enable h-mirror and raw output */ - i2c_w_mask(ov, 0x27, 0x10, 0x10); /* Bypass RGB matrix */ - i2c_w_mask(ov, 0x28, 0x04, 0x04); /* G/BR (YG) mode */ + + i2c_w (ov, 0x03, 0x80); /* Default saturation value */ + i2c_w (ov, 0x06, 0x80); /* Default brightness value */ + + i2c_w (ov, 0x10, 0x7f); /* Interlaced auto exposure */ + i2c_w (ov, 0x24, 0x08); /* Interlaced white balance */ + i2c_w (ov, 0x25, 0x4a); /* Interlaced white balance */ + + i2c_w_mask(ov, 0x12, 0x08, 0x48); /* Enable raw output */ + i2c_w_mask(ov, 0x61, 0x00, 0x80); /* Enable raw output */ + i2c_w_mask(ov, 0x13, 0x20, 0x00); /* Enable 16-bit */ + i2c_w_mask(ov, 0x28, 0x04, 0x24); /* YG mode, interlace */ i2c_w_mask(ov, 0x2a, 0x00, 0x10); /* No UV 2-pixel delay */ - i2c_w_mask(ov, 0x2d, 0x40, 0x40); /* QVGA 60 FPS (redundant?) */ - i2c_w(ov, 0x67, 0x00); /* Disable YUV postprocess */ + i2c_w_mask(ov, 0x2d, 0x40, 0x40); /* 60 FPS */ + + i2c_w (ov, 0x60, 0x27); /* Color mode, disable UV green averaging */ + i2c_w (ov, 0x74, 0x60); /* AGC max gain = 8x */ + i2c_w (ov, 0x14, 0x00); /* Gamma disable, 640x480, + HREF positive, VSYNC after each field */ + + i2c_w_mask(ov, 0x27, 0x00, 0x10); /* Enable RGB matrix */ + i2c_w (ov, 0x67, 0x00); /* Disable YUV postprocess */ + i2c_w_mask(ov, 0x69, 0x00, 0x40); /* Disable vertical edge enhancement */ + i2c_w (ov, 0x6a, 0x00); /* Disable vertical edge enhancement */ + i2c_w (ov, 0x07, 0xf0); /* Disable sharpening */ + i2c_w_mask(ov, 0x26, 0xc0, 0xf0); /* Disable sharpening */ i2c_w_mask(ov, 0x2d, 0x00, 0x0f); /* Disable anti-alias */ - i2c_w(ov, 0x24, 0x20); /* Use QVGA white pixel ratio */ - i2c_w(ov, 0x25, 0x30); /* Use QVGA black pixel ratio */ - i2c_w(ov, 0x60, 0x26); /* Unknown */ - i2c_w(ov, 0x74, 0x20); /* AGC max gain = 4x */ - i2c_w(ov, 0x06, 0x68); /* Hardcoded brightness value */ - i2c_w(ov, 0x14, 0x04); /* */ + i2c_w_mask(ov, 0x68, 0x00, 0x0f); /* Disable anti-alias */ + i2c_w_mask(ov, 0x20, 0x00, 0x30); /* Disable aperture correction */ + i2c_w_mask(ov, 0x64, 0x00, 0x01); /* Disable gamma correction */ + i2c_w_mask(ov, 0x27, 0x00, 0x02); /* Disable range extension (!) */ return 0; } @@ -1382,44 +1400,79 @@ * **********************************************************************/ + +#define B(x,y) pBGR24[0 + 3 * ((x) + w * (y))] +#define G(x,y) pBGR24[1 + 3 * ((x) + w * (y))] +#define R(x,y) pBGR24[2 + 3 * ((x) + w * (y))] + +#define Bayer(x,y,o) pBayer[(o) + 2 * (x) + w * (y)] + static void -gbgr_to_bgr24(struct ovfx2_frame *frame, - unsigned char *pIn, - unsigned char *pOut) +bayer_to_bgr24(struct ovfx2_frame *frame, + unsigned char *pBayer, + unsigned char *pBGR24) { - const unsigned int a = frame->rawwidth * frame->rawheight / 2; - int i; + int i, j; + int w = frame->rawwidth; + int h = frame->rawheight; - for (i = 0; i < a; i++) { - pOut[0] = pIn[1]; - pOut[1] = pIn[0]; - pOut[2] = pIn[3]; - pOut[3] = pIn[1]; - pOut[4] = pIn[2]; - pOut[5] = pIn[3]; + /* Copy pixels */ + + for (i = 0; i < w; i += 2) + for (j = 0; j < h; j += 2) { + + G(i + 0, j + 1) = Bayer(i + 0, j, 0); + G(i + 1, j + 0) = Bayer(i + 1, j, 0); + B(i + 0, j + 0) = Bayer(i + 0, j, 1); + R(i + 1, j + 1) = Bayer(i + 1, j, 1); + } + + /* Bilinear interpolation with nearest neighbor for edges */ + + for (i = 0; i < w; i += 2) + for (j = 0; j < h; j += 2) + if (i == 0 || j == 0 || i == w - 2 || j == h - 2) { + + G(i + 0, j + 0) = G(i + 1, j + 1) = + ((u32)G(i + 1, j + 0) + (u32)G(i + 0, j + 1)) / 2; + B(i + 1, j + 0) = B(i + 0, j + 1) = B(i + 1, j + 1) = B(i + 0, j + 0); + R(i + 0, j + 0) = R(i + 1, j + 0) = R(i + 0, j + 1) = R(i + 1, j + 1); + + } else { + + G(i + 0, j + 0) = ((u32)G(i + 0, j - 1) + (u32)G(i + 0, j + 1) + + (u32)G(i - 1, j + 0) + (u32)G(i + 1, j + 0)) / 4; + G(i + 1, j + 1) = ((u32)G(i + 1, j + 0) + (u32)G(i + 1, j + 2) + + (u32)G(i + 0, j + 1) + (u32)G(i + 2, j + 1)) / 4; + + B(i + 1, j + 1) = ((u32)B(i + 0, j + 0) + (u32)B(i + 2, j + 0) + + (u32)B(i + 0, j + 2) + (u32)B(i + 2, j + 2)) / 4; + B(i + 0, j + 1) = ((u32)B(i + 0, j + 0) + (u32)B(i + 0, j + 2)) / 2; + B(i + 1, j + 0) = ((u32)B(i + 0, j + 0) + (u32)B(i + 2, j + 0)) / 2; + + R(i + 0, j + 0) = ((u32)R(i - 1, j - 1) + (u32)R(i + 1, j - 1) + + (u32)R(i - 1, j + 1) + (u32)R(i + 1, j + 1)) / 4; + R(i + 0, j + 1) = ((u32)R(i - 1, j + 1) + (u32)R(i + 1, j + 1)) / 2; + R(i + 1, j + 0) = ((u32)R(i + 1, j - 1) + (u32)R(i + 1, j + 1)) / 2; + + } - pIn += 4; - pOut += 6; - } } -/* Flip image vertically */ +/* Rotate image upside down */ static void -vert_mirror(unsigned char *pIn, - unsigned char *pOut, - unsigned int width, - unsigned int height, - unsigned int depth) +rotate_180(struct ovfx2_frame *frame, unsigned char *pBGR24) { - const unsigned int linesize = width * depth; - int i; - - pOut += height * linesize; - - for (i = 0; i < height; i++) { - pOut -= linesize; - memcpy(pOut, pIn, linesize); - pIn += linesize; + int i, j; + unsigned char r, g, b; + int w = frame->rawwidth; + int h = frame->rawheight; + + for (i = 0; i < w; i++) + for (j = 0; j < h / 2; j++) { + b = B(i, j); B(i, j) = B(w - (i + 1), h - (j + 1)); B(w - (i + 1), h - (j + 1)) = b; + g = G(i, j); G(i, j) = G(w - (i + 1), h - (j + 1)); G(w - (i + 1), h - (j + 1)) = g; + r = R(i, j); R(i, j) = R(w - (i + 1), h - (j + 1)); R(w - (i + 1), h - (j + 1)) = r; } } @@ -1436,21 +1489,15 @@ memset(frame->data, 0, MAX_DATA_SIZE(ov->maxwidth, ov->maxheight)); PDEBUG(4, "Dumping %d bytes", frame->bytes_recvd); - gbgr_to_bgr24(frame, frame->rawdata, frame->data); + bayer_to_bgr24(frame, frame->rawdata, frame->data); } else if (dumppix == 2) { /* Dump raw data */ memset(frame->data, 0, MAX_DATA_SIZE(ov->maxwidth, ov->maxheight)); PDEBUG(4, "Dumping %d bytes", frame->bytes_recvd); memcpy(frame->data, frame->rawdata, frame->bytes_recvd); } else { - /* FIXME: Remove this once decoding is stable */ - memset(frame->data, 0, - MAX_DATA_SIZE(ov->maxwidth, ov->maxheight)); - memset(frame->tempdata, 0, - MAX_RAW_DATA_SIZE(ov->maxwidth, ov->maxheight)); - vert_mirror(frame->rawdata, frame->tempdata, frame->rawwidth, - frame->rawheight, 2); - gbgr_to_bgr24(frame, frame->tempdata, frame->data); + bayer_to_bgr24(frame, frame->rawdata, frame->data); + rotate_180(frame, frame->data); } } @@ -1477,13 +1524,13 @@ if (n) { int b = n; /* Number of image data bytes */ -#if 1 - /* The first 2560 bytes of data (first 2 lines?) are invalid */ + + /* The first 1280 bytes of data (first 16-bit href) are invalid */ if (frame->bytes_recvd == 0) { - in += 2560; - b -= 2560; + in += 1280; + b -= 1280; } -#endif + frame->bytes_recvd += b; if (frame->bytes_recvd <= max_raw) memcpy(frame->rawdata + frame->bytes_recvd - b, in, b); @@ -1772,11 +1819,6 @@ ov->rawfbuf = NULL; } - if (ov->tempfbuf) { - vfree(ov->tempfbuf); - ov->tempfbuf = NULL; - } - for (i = 0; i < OVFX2_NUMSBUF; i++) { if (ov->sbuf[i].data) { kfree(ov->sbuf[i].data); @@ -1787,7 +1829,6 @@ for (i = 0; i < OVFX2_NUMFRAMES; i++) { ov->frame[i].data = NULL; ov->frame[i].rawdata = NULL; - ov->frame[i].tempdata = NULL; } PDEBUG(4, "buffer memory deallocated"); @@ -1820,12 +1861,6 @@ memset(ov->rawfbuf, 0, raw_bufsize); - ov->tempfbuf = vmalloc(raw_bufsize); - if (!ov->tempfbuf) - goto error; - - memset(ov->tempfbuf, 0, raw_bufsize); - for (i = 0; i < OVFX2_NUMSBUF; i++) { ov->sbuf[i].data = kmalloc(OVFX2_BULK_SIZE, GFP_KERNEL); if (!ov->sbuf[i].data) @@ -1838,8 +1873,6 @@ ov->frame[i].data = ov->fbuf + i * MAX_DATA_SIZE(w, h); ov->frame[i].rawdata = ov->rawfbuf + i * MAX_RAW_DATA_SIZE(w, h); - ov->frame[i].tempdata = ov->tempfbuf - + i * MAX_RAW_DATA_SIZE(w, h); PDEBUG(4, "frame[%d] @ %p", i, ov->frame[i].data); } diff -urN ov511-2.28/ovfx2.h ov511-2.28-vp2/ovfx2.h --- ov511-2.28/ovfx2.h 2004-07-15 14:16:39.000000000 +0200 +++ ov511-2.28-vp2/ovfx2.h 2004-11-14 21:05:44.254986920 +0100 @@ -77,7 +77,7 @@ /* # of BULK scratch buffers */ #define OVFX2_NUMSBUF 8 -#define OVFX2_BULK_SIZE 32768 +#define OVFX2_BULK_SIZE 16384 #define OVFX2_NUMFRAMES 2 #if OV511_NUMFRAMES > VIDEO_MAX_FRAME @@ -136,7 +136,6 @@ struct ovfx2_frame { int framenum; /* Index of this frame */ unsigned char *data; /* Frame buffer */ - unsigned char *tempdata; /* Temp buffer for multi-stage conversions */ unsigned char *rawdata; /* Raw camera data buffer */ int depth; /* Bytes per pixel */ @@ -180,7 +179,6 @@ int grabbing; /* Are we grabbing? */ unsigned char *fbuf; /* Videodev buffer area */ - unsigned char *tempfbuf; /* Temporary (intermediate) buffer area */ unsigned char *rawfbuf; /* Raw camera data buffer area */ int sub_flag; /* Pix Array subcapture on flag */