Make Your Own Camera

1,153

2

18

About: 55+ years in electronics, computers, and teaching ... now retired.

This instructable explains how to make a monochrome camera using an Omnivision OV7670 image sensor, an Arduino microcontroller, a few jumper wires, and Processing 3 software.

Experimental software for obtaining a color image is also presented.

Press the “c” key to capture a 640*480 pixel image ... press the “s” key to save the image to file. Successive images are sequentially numbered should you wish to create a short time-lapse movie.

The camera is not fast (each scan takes 6.4 seconds) and is only suitable for use in fixed lighting.

The cost, excluding your Arduino and PC, is less than a cup of coffee.

Images

The component parts, without jumper wiring, are shown in the opening photo.

The second photo is a screen-shot showing the Arduino camera software and the Processing 3 frame-grabber. The inset shows how the camera is connected.

The video demonstrates the camera in action. When the “c” capture key is pressed there is a brief flash followed by a burst of activity as the image is scanned. The image automatically appears in the display window once the scan is complete. The images are then seen to appear in the Processing folder following each press of the “s” key. The video concludes by cycling rapidly through each of the three saved images.

Step 1: Circuit Diagram

The circuit diagram, for all versions of this camera, is shown in photo 1.

Photos 2, 3 show how the jumpers-wires and components are connected.

Without the aluminium bracket the images are lying on their side.

Warning

Program your Arduino BEFORE attaching any jumper wires to the OV7670 camera chip. This will prevent 5 volt output pins from a previous program from destroying the 3v3 volt OV7670 camera chip.

Step 2: Parts List

The following parts were obtained from https://www.aliexpress.com/

  • 1 only OV7670 300KP VGA Camera Module for arduino DIY KIT
  • 1 only camera bracket complete with nuts and bolts
  • 1 only UNO R3 for arduino MEGA328P 100% original ATMEGA16U2 with USB Cable

The following parts were obtained locally

  • 18 anly Arduino male-female jumper cables
  • 3 only Arduinin female-female jumper cables
  • 1 only mini bread-board
  • 4 only 4K7 ohm 1/2 watt resistors
  • 1 only scrap aluminium stand.

You will also need the following datasheets:

Step 3: Theory

OV7670 camera chip

The default output from the OV7670 camera chip comprises a YUV (4:2:2) video signal and 3 timing waveforms. Other output formats are possible by programming the internal registers via an I2C compatible bus.

The YUV (4:2:2) video signal (photo 1) is a continuous sequence of monochrome (black & white) pixels separated by U (blue color difference) and V (red color difference) color information.

This output format is known as YUV (4:2:2) since each group of 4 bytes contains 2 monochrome bytes and and 2 color bytes.

Monochrome

To obtain a monochrome image we must sample every second data byte.

An Arduino only has 2K of random access memory but each frame comprises 640*2*480 = 307,200 data bytes. Unless we add a frame-grabber to the OV7670 all data must sent to the PC line-by-line for processing.

There are two possibilities:

For each of 480 successive frames, we can capture one line to the Arduino at high speed before sending it to the PC at 1Mbps. Such an approach would see the OV7670 working at full speed but would take a long time (well over a minute).

The approach that I have taken is to slow the PCLK down to 8uS and send each sample as it comes. This approach is significantly faster (6.4 seconds).

Step 4: Design Notes

Compatibility

The OV7670 camera chip is a 3v3 volt device. The data sheet indicates that voltages above 3.5 volts will damage the chip.

To prevent your 5 volt Arduino from destroying the OV7670 camera chip:

  • The external clock (XCLK) signal from the Arduino must be reduced to a safe level by means of a voltage divider.
  • The internal Arduino I2C pull-up resistors to 5 volts must be disabled and replaced with external pull-up resistors to the 3v3 volt supply.
  • Program your Arduino BEFORE attaching any jumper-wires as some of the pins may still be programmed as an output from an earlier project !!! (I learnt this the hard way ... fortunately I bought two as they were so cheap).

External clock

The OV7670 camera chip requires an external clock in the frequency range 10Mhz to 24MHz.

The highest frequency we can generate from a 16MHz Arduino is 8MHz but this seems to work.

Serial link

It takes at least 10 uS (microseconds) to send 1 data byte across a 1Mbps (million bits per second) serial link . This time is made up as follows:

  • 8 data bits (8us)
  • 1 start-bit (1uS)
  • 1 stop-bit (1uS)

Internal clock

The internal pixel clock (PCLK) frequency within the OV7670 is set by bits[5:0] within register CLKRC (see photo 1). [1]

If we set bits[5:0] = B111111 = 63 and apply it to the above formula then:

  • F(internal clock) = F (input clock)/(Bit[5:0}+1)
  • = 8000000/(63+1)
  • = 125000 Hz or
  • = 8uS

Since we are only sampling every second data byte, a PCLK interval of 8uS results in a 16uS sample which is sufficient time to transmit 1 data byte (10uS) leaving 6uS for processing.

Frame rate

Each VGA video frame comprises 784*510 pixels (picture elements) of which 640*480 pixels are displayed. Since the YUV (4:2:2) output format has an average of 2 data bytes per pixel, each frame will take 784*2*510*8 uS = 6.4 seconds.

This camera is NOT fast !!!

Horizontal positioning

The image may be moved horizontally if we change the HSTART and HSTOP values while maintaining a 640 pixel difference.

When moving your image left, it is possible for your HSTOP value to be less than the HSTART value!

Don’t be alarmed ... it is all to do with counter overflows as explained in photo 2.

Registers

The OV7670 has 201 eight-bit registers for controlling things such as gain, white balance, and exposure.

One data byte only allows for 256 values in the range [0] to [255]. If we require more control then we must cascade several registers. Two bytes gives us 65536 possibilities ... three bytes give us 16,777,216.

The 16 bit AEC (Automatic Exposure Control) register shown in photo 3 is such an example and is created by combining portions of the following three registers.

  • AECHH[5:0] = AEC[15:10]
  • AECH[7:2 ] = AEC[9:2]
  • COM1[1:0] = AEC[1:0]

Be warned ... the register addresses are not grouped together !

Side effects

A slow frame rate introduces a number of unwanted side effects:

For correct exposure, the OV7670 expects to work at a frame rate of 30 fps (frames per second). Since each frame is taking 6.4 seconds the electronic shutter is open 180 times longer than normal which means all images will be over-exposed unless we alter some register values.

To prevent over-exposure I have set all of the AEC (auto exposure control) register bits to zero. Even so a neutral density filter is needed in front of the lens when the lighting is bright.

A long exposure also appears to affect the UV data. As I have yet to find register combinations that produce correct colours ... consider this to be work in progress.

Note

[1]

The formula shown in the data sheet (photo 1) is correct but the range only shows bits[4:0] ?

Step 5: Timing Waveforms

The note in the bottom left corner of the “VGA Frame Timing” diagram (photo 1) reads:

For YUV/RGB, tp = 2 x TPCLK

Figures 1, 2, & 3 verify the data sheet(s) and confirm that Omnivision treats every 2 data bytes as being the equivalent of 1 pixel.

The oscilloscope waveforms also verify that HREF remains LOW during the blanking intervals.

Fig.4 confirms that the XCLK output from the Arduino is 8MHz. The reason we see a sinewave, rather than a squarewave, is that all of the odd harmonics are invisible to my 20MHz sampling oscilloscope.

Step 6: Frame Grabber

The image sensor within an OV7670 camera chip comprises an array of 656*486 pixels of which a grid of 640*480 pixels are used for the photo.

The HSTART, HSTOP, HREF, and VSTRT, VSTOP, VREF register values are used to position the image over the sensor. If the image is not positioned correctly over the sensor you will see a black band over one or more edges as explained in the “Design Notes” section.

The OV7670 scans each line of the picture one pixel at a time starting from the top left corner until it reaches the bottom right pixel. The Arduino simply passes these pixels to the PC via the serial link as shown in photo 1.

The frame-grabbers’ task is to capture each of these 640*480=307200 pixels and display the contents in an “image” window

Processing 3 achieves this using the following four lines of code !!

Code line 1:

  • byte[] byteBuffer = new byte[maxBytes+1]; // where maxBytes=307200

The underlying code in this statement creates:

  • a 307201 byte array called “byteBuffer[307201]”
  • The extra byte is for a termination (linefeed) character.

Code line 2:

  • size(640,480);

The underlying code in this statement creates:

  • a variable called “width=640;”
  • a variable called “height=480”;
  • a 307200 pixel array called “pixels[307200]”
  • a 640*480 pixel “image” window in which the contents of pixels[] array are displayed. This “image” window is continuously refreshed at a frame rate of 60 fps.

Code line 3:

  • byteCount = myPort.readBytesUntil(lf, byteBuffer);

The underlying code in this statement:

  • buffers the incoming data locally until it sees a “lf” (linefeed) character.
  • after which it dumps the first 307200 bytes of local data into the byteBuffer[] array.
  • It also saves the number of bytes received (307201) into a variable called “byteCount”.

Code line 4:

  • pixels[i] = color(byteBuffer[i]);

When placed in a for-next-loop, the underlying code in this statement:

  • copies the contents of the “byteBuffer[]” array to the “pixels[]” array
  • the contents of which appear in the image window.

Key Strokes:

The frame-grabber recognises the following keystrokes:

  • ‘c’ = capture the image
  • ‘s’ = save the image to file.

Step 7: Software

Download and install each of the following software packages if not already installed:

  • “Arduino” from https://www.arduino.cc/en/main/software
  • “Java 8” from https://java.com/en/download/ [1]
  • "Processing 3” from https://processing.org/download/

Installing the Arduino sketch:

  • Remove all OV7670 jumper wires [2]
  • Connect a USB cable to your Arduino
  • Copy the contents of “OV7670_camera_mono_V2.ino“ (attached) into an Arduino “sketch” and save.
  • Upload the sketch to your Arduino.
  • Unplug the Arduino
  • You can now safely reconnect the OV7670 jumper wires
  • Reconnect the USB cable.

Installing and running the Processing sketch:

  • Copy the contents of “OV7670_camera_mono_V2.pde” (attached) into a Processing “sketch” and save.
  • Click the top-left “run” button ... a black image window will appear
  • Click the “black” image-window
  • Press the “c” key to capture an image. (approx 6.4 seconds).
  • Press the “s” key to save the image in your processing folder
  • Repeat steps 4 & 5
  • Click the “stop” button to exit the program.

Notes

[1]

Processing 3 requires Java 8

[2]

This is a “once only” safety step to avoid damaging your OV7670 camera chip.

Until the sketch “OV7670_camera_mono.ini” has been uploaded to your Arduino the internal pull-up resistors are connected to 5 volts, plus there is the possiblity that some of the Arduino data lines may be 5 volt outputs ... all of which are fatal to the 3v3 volt OV7670 camera chip.

Once the Arduino has been programmed there is no need to repeat this step and the register values may be safely changed.

Step 8: Obtaining a Color Image

The following software is purely experimental and is posted in the hope that some of the techniques will prove useful. The colors appear to be inverted ... I have yet to find the correct register settings. If you find a solution please post your results.

If we are to obtain a color image, all data bytes must be captured and the following formulas applied.

The OV7670 uses the following formulas to convert RGB (red, green, blue) color information into YUV (4:2:2): [1]

  • Y = 0.31*R + 0.59*G + 0.11*B
  • U = B – Y
  • V = R – Y
  • Cb = 0.563*(B-Y)
  • Cr = 0.713*(R-Y)

The following formulas may be used to convert YUV (4:2:2) back to RGB color: [2]

  • R = Y + 1.402* (Cr – 128)
  • G = Y – 0.344136*(Cb -128) – 0.714136*(Cr -128)
  • B = Y + 1.772*(Cb -128)

The attached software is simply an extension of the monochrome software:

  • A “c” capture request is sent to the Arduino
  • The Arduino sends the even numbered (monochrome) bytes to the PC
  • The PC saves these bytes into an array
  • The Arduino next sends the odd numbered (chroma) bytes to the PC.
  • These bytes are saved into a second array ... we now have the entire image.
  • The above formulas are now applied to each group of four UYVY data bytes.
  • The resulting color pixels are then placed in the “pixels[]” array
  • The PC scans the “pixels[]” array and an image appears in the “image” window.

The Processing 3 software briefly displays each scan and the final results:

  • Photo 1 shows the U & V chroma data from scan 1
  • Photo 2 shows the Y1 & Y2 luminance data from scan 2
  • Photo 3 shows the color image ... only one thing is wrong ... the bag should be green !!

I will post new code once I have solved this program ...

References:

[1]

http://www.haoyuelectronics.com/Attachment/OV7670%... (page 33)

[2]

https://en.wikipedia.org/wiki/YCbCr (JPEG conversion)

  Click here   to view my other instructables.

Share

    Recommendations

    • Faux-Real Contest

      Faux-Real Contest
    • Safe and Secure Challenge

      Safe and Secure Challenge
    • Epilog X Contest

      Epilog X Contest

    18 Discussions

    0
    None
    skywalker58

    6 weeks ago

    firstly thank you for answered me.
    i made your says but now, i encountered a new problem. how can i adjust net(clear) image?
    sorry for my bad english. i hope understand me :)

    sadasdsa.pngfotooo22.png
    15 replies
    0
    None
    lingibskywalker58

    Reply 5 weeks ago

    The fact that you have data indicates that your hardware is working :)

    I suspect the problem is the result of too much light. If you pause the video in my Instructable at 0:14 seconds you will see that the room is dimly lit.

    In Step 4 (Design Notes: Side Effects) I mention "To prevent over-exposure I have set all of the AEC (auto exposure control) register bits to zero. Even so a neutral density filter is needed in front of the lens when the lighting is bright."

    The exposure values are set to minimum in the "initialise_OV7670(){}" subroutine should you wish to experiment.

    Try installing "OV7670_camera_mono.ino/pde" as this software is much faster which means that you can quickly see the effect of different light levels. The "color" software can be tried once you have an image using the "mono" software.

    The "color" software requires the same lighting level ... it is essentially the "mono" software run twice then processed.

    Keep in mind the color "software is purely experimental" and that "the colors appear to be inverted".

    0
    None
    skywalker58lingib

    Reply 5 weeks ago

    i don't understand to what will do. what i should change in code?
    I tried low light but take same image.

    0
    None
    lingibskywalker58

    Reply 5 weeks ago

    Check that your data line wiring is correct.

    If you haven't already done so, upload "OV7670_camera_mono.ino" to your Arduino.

    Do not make any changes to the code. If you have access to an oscilloscope you should see signals on each of the data lines.

    Open the Arduino "Serial|Monitor" and set the baud speed to 1000000.

    Remove the lens-cap from your OV7670 in bright light. Removing the lens cap forces the OV7670 data pattern to 255 (white)

    Now send the letter 'c' to your Arduino and note the symbols/characters that appear in the Serial|Monitor.

    Replace the lens-cap on your OV7670. Adding the lens cap forces the data pattern to be 0 (black)

    Send the letter 'c' to your Arduino and note the symbols/characters that appear the Serial|Monitor. These symbols/characters should be different.

    If no visible characters appear on the screen substitute the following (untested) code in "OV7670_camera_mono.ino":
    /* Read second byte */
    while (PINB & B00000001); // Wait until PCLK pin 8 is low
    data = (PIND & B11110000) | (PINC & B00001111); // Read data
    if (data <128)
    {
    Serial.write('A');
    }
    else
    {
    Serial.write('Z');
    }
    while (!(PINB & B00000001)); // Wait until PCLK pin 8 is high

    If the characters change from 'AAAAAA....' to 'ZZZZZZZ.....' when you add/remove the lens cap your OV7670 is okay, in which case upload "OV7670_camera_mono.ino" once more to your Arduino and run the OV7670_camera_mono.pde" sketch on your PC.

    Point your OV7670 to a dimly lit object.

    An image should appear in the PC image window, and the number 307201 should appear in the bottom Processing window (307201 = 640*480 pixels + 1 termination character) whenever you press the 'c' key.

    Now adjust the lighting level to suit.

    The above tests should determine whether your OV7670 is working .

    0
    None
    skywalker58lingib

    Reply 5 weeks ago

    i make your says. i encountered a new problem. i write this code ( if (data <128)
    {
    Serial.write('A');
    }
    else
    {
    Serial.write('Z');
    } )
    . i see this message (the buffer passed to readBytes....) in processing screen. Before, i delete this parantheses => ''{''. after, again write this parantheses.
    the problem has fixed.

    Later, i upload code in arduino and i look processing screen. and i see this image(in photo).




    Ads&#305;z11.pngAds&#305;z222.pnggdsgfds.png
    0
    None
    lingibskywalker58

    Reply 5 weeks ago

    Your camera may well be working ... it may just need more gain.
    Overwrite any code changes that you may have made by reinstalling BOTH "OV7670_camera_mono.pde" and "OV7670_camera_mono.ino".

    -------------------------------------
    In the "OV7670_camera_mono.pde" software
    -------------------------------------
    Change code line 78 to read:
    pixels[i] = color(int(byteBuffer[i]));

    Explanation:
    Changing the data type SIGNIFICANTLY improves the image.

    -------------------------------------
    In the "OV7670_camera_mono.ino"
    -------------------------------------
    Your image brightness may be adjusted as as follows:

    Change the code to read:
    write_register(0x07, B00000000);
    write_register(0x10, B00100000);
    write_register(0x04, B00000000);

    Explanation:
    These three lines control the image brightness.

    You are allowed to write a '1' into any of the positions below that are shown with an 'x'
    write_register(0x07, Bxxxxxxxx);
    write_register(0x10, Bxxxxxxxx);
    write_register(0x04, B000000xx);

    The default position is"
    write_register(0x07, B00000000);
    write_register(0x10, B00100000);
    write_register(0x04, B00000000);

    Please let me know if this works and I will update the Instructable.

    A screenshot showing the changes is attached.

    CodeChanges.jpg
    0
    None
    skywalker58lingib

    Reply 5 weeks ago

    i tried different combinations in arduino code. but i dont take good image.
    Could my camera be defective?

    ddddsdadsadsaas.png
    0
    None
    lingibskywalker58

    Reply 5 weeks ago

    There is a strong possibility that your OV7670 is not working :(

    With a '1' written into every bit position your screen should be white as shown in Gain_high.jpg (attached) .... your screen is gray ???

    With a '0' written into every bit position you should get an image as shown in Gain_zero.jpg (attached).

    With a gain setting of 4 (binary 00000000 00000001 00) the image starts to over-expose as shown in Gain_four.jpg (attached).

    While you are getting valid sync pulses it appears that your OV7670 data lines are producing invalid data. A simple way of verifying this is to fill each bit position with a '1' (as you have done) and send known hexadecimal data values to your PC as shown in the remaining screen shots.

    0x00 produces a black screen as shown in Serial_write_0x00.jpg.
    0x80 produces a mid-gray screen as shown in Serial_write_0x80.jpg.
    0xD0 produces a light-gray screen as shown in Serial_write_0xD0.jpg
    0xFF will produce a completely white screen.

    Serial_write_0xD0.JPGSerial_write_0x80.JPGSerial_write_0x00.JPGGain_zero.jpgGain_high.JPGGain_four.JPG
    0
    None
    skywalker58lingib

    Reply 5 weeks ago

    i make same thing to your make.(in photo).
    while serial.write(0xD0) , gray color in processing screen. but what i do write.register(0x07,0x10 nad 0x04) ?
    if i dont change serial.write(0xD0 OR 0X80 or 0x00), dont change image in processing screen.
    *** there is a important situation***
    i use 10k ohm resistance previously.
    but now i use 4.7k ohm resistance.
    is this a problem?

    3.png4.pngbir.pngiki.png
    0
    None
    skywalker58skywalker58

    Reply 5 weeks ago

    What i should write here? => write_register(0x07,0x10,0x04) ?

    0
    None
    lingibskywalker58

    Reply 5 weeks ago

    The write_register() function only expects two values.
    The first value is the register number
    The second value is the actual value to be written into the register.

    In my previous answer I have provided several screen shots.

    The photos showing light-gray, darker-gray, and black screens have the test code shown in yellow highlight.
    You should see the same if you enter these values ... regardless of the settings in the initialise_OV7670() function.
    These three tests simply confirm that your frame-grabber is working correctly.

    The photo showing a completely white screen shows what you should see when you try sending actual data to the framegrabber with all of the gain bits (shown in yellow high-light) set to '1'.

    The photo showing a perfect picture requires the initialise_OV7670() values shown (all of the gain bits are set to zero).

    Increasing the gain from zero to 4 produces the over-exposed image.

    -----------------------------------------------------------------------------------------------------

    It would appear that you have previously attempted to make a camera as:
    (1) your PCLK, HREF, and VSYNC wiring were originally mapped to different pins
    (2) you were not using 4K7 pull-up resistors.(I haven't tried 10K resistors but they are probably fine).

    In my article I stress, in several places, to upload my code to your Arduino BEFORE connecting any wires.

    My reason for these warnings is that, before uploading my code to my Arduino, I connected the wires to an Arduino chip that had previously been used in a plotter. The previous motor output pins destroyed the OV7670 data outputs ... everything else worked ... I purchased another OV7670.

    In all probability you have done something similar in which case your OV7670 data lines are dead ... you will need access to an oscilloscope to confirm this.

    Should you purchase another OV7670 you should obtain an image if you follow my article EXACTLY.

    Good luck ... I am unable to provide further help as I have provided all the necessary code for debugging this project.

    0
    None
    skywalker58lingib

    Reply 5 weeks ago

    I'm so thankful for your all helps.
    I will buy a new ov7670.
    i think, my first breadboard and ov7670 wire connection is so bad. i connect ov7670 to 5v in arduino, i must connecting to 3.3v.
    Can i ask you something my other projects next time?
    I want learn new informations from you.

    0
    None
    lingibskywalker58

    Reply 5 weeks ago

    With regards to this camera project I have simply exhausted all possibilities, but feel free to ask for help with any of your other projects :)

    Thank you for your questions. In trying to find a solution I found that a minor change to one line of the processing code significantly improved the image quality. No changes were required to the Arduino code.

    Please note that, should you rebuild your camera:
    (1) OV7670_camera_mono_V2.pde and OV7670_camera_color_V2.pde both contain this code update. The Arduino code is unaltered but is renamed to V2 to match.
    (2) No code changes should be needed.
    (3) Upload the arduino code BEFORE attaching any wires to your OV7670 as 5 volts will destroy the chip.

    0
    None
    lingibskywalker58

    Reply 5 weeks ago

    The write_register() function only expects two values.
    The first value is the register number
    The second value is the actual value to be written into the register.

    In my previous answer I have provided several screen shots.

    The photos showing light-gray, darker-gray, and black screens have the test code shown in yellow highlight.
    You should see the same if you enter these values ... regardless of the settings in the initialise_OV7670() function.
    These three tests simply confirm that your frame-grabber is working correctly.

    The photo showing a completely white screen shows what you should see when you try sending actual data to the framegrabber with all of the gain bits (shown in yellow high-light) set to '1'.

    The photo showing a perfect picture requires the initialise_OV7670() shown.

    Increasing the gain from zero to 4 produces the over-exposed image.

    0
    None
    skywalker58

    Question 6 weeks ago

    'Hi. I make this project. but i encountered this problem. What can i do solve this problem ?. thanks

    poto.png
    1 answer
    0
    None
    lingibskywalker58

    Answer 6 weeks ago

    Thank you for the screen-shot of the "OV7670_camera_color" software.

    The light-gray screen indicates that the Processing 3 software is working correctly.

    It would appear that you have changed the wiring for "PCLK", "HREF", and "VSYNC". As a result no data is being sent to the PC.

    For my code to work the wiring should be as follows:
    /* OV7670 */
    #define OV7670 0x21 // OV7670 address
    #define PCLK 8 //pixel clock from OV7670
    #define HREF 9 //horizontal sync from 0V7670 (not used)
    #define VSYNC 10 //vertical sync from OV7670
    #define XCLK 11 //8MHz output to OV7670

    A wiring-diagram is included in my code header.

    I am using pins 8,9,10,11 for the following reasons:
    - I found pins 2,3 unreliable (external interrupt pin loading ???)
    - Grouping pins 8,9,10,11 together makes it easier to tape the wires in place.

    The same pinouts are used for the "OV7670_camera_mono.ino" and "OV7670_camera_color.ino" software.

    You can confirm that the Arduino code is working using the "Serial|Monitor".

    Set the Arduino Serial|Monitor baud speed to 1000000 and type "c" in the command window. The link light on the Arduino should flash then, after a few seconds, a string of symbols/characters should race across the screen.

    If you want to see a continuous string of visible characters then (temporarily) change code lines 233,296 to read: Serial.write('A'); // Send data to PC

    Once you see a string of "AAAAAAAAAAAAAAAAAA...'s" you know the code is working. Change the code back to Serial.write(data); and launch Processing 3 again.