One of the staples of image processing is edge detection. Edge detection is generally one of the primary steps for further image processing methods including feature detection. Conceptually, edge detection is relatively simply. Look at the image and compare areas of low intensity to areas of high intensity, simple right?
Actually yeh, it is that simple. When I was doing research for my final year project, I read how people had implemented edge detection in FPGAs and it realistically boils down to a couple of line buffers (actually, m line buffers where m is the vertical size of your convolving kernel) and a few mathematical operations on the values positioned at the bottom of these vectors. This link is similar to what I’m implementing. I have 3x 8bit 640 length line buffers (buffering 3x lines of camera pixels) which act as shift registers i.e. the lowest pixel of row 3 shifts into the highest pixel of row 2, the lowest pixel of row 2 shifts into the highest pixel of row 1 and the lowest pixel of row 1 shifts out completely. This happens on every “pixel clock” cycle (2x camera pixel clocks = 1 pixel clock). For effective edge detection, a black and white image is required. While I could re-code the OV7670 capture module to work with YCbCr values, I decided instead to convert the values on the fly using the YCgCo method. This method was chosen as it could be implemented by grabbing certain bits from a standard RGB565 pixel and summing them:
std_logic_vector(resize(unsigned(pixi(4 downto 2)), 8) + resize(unsigned(pixi(10 downto 6)), 8) + resize(unsigned(pixi(15 downto 13)), 8))
pixi is the input 16bit pixel.
This is done in a module called “imgprekernel” where I preprocess the pixel stream to fit suitably with the image edge generation module. Because there is a 640*3 pixel latency from first pixel to edge pixel output, the imgprekernel module also spits out 640*3 pixels at the end of the camera image stream. This ensures that the image edge generator module always spits out the right amount of pixels. The image edge generator module also contains a counter that is reset at the start of every frame. This counter ensures that edge pixels aren’t emitted until the line buffers are full.
The edge detection module (so inaptly named “imgrcedge” as I was originally only going to implement the Roberts cross operator) actually implements the Sobel operator for edge detection. This requires 13 additions, two subtractions and two modulus functions which equates to a fair bit of combinatorial logic though synthesis is still happy to run at >100MHz (I’m running it at 96MHz)! The logic required for the roberts cross operator is smaller as this is only a 2×2 kernel and requires only two subtractions, two modulus functions and an addition. Resource usage is ~250 LEs for this module.
Along with convolving with the edge detection kernel, imgrcedge also implements a hysteretic thresholding, similar to the Canny detector.
As the output from the hysteretic thresholder is merely a bit to say whether there is or isn’t an edge, I instead packed the bits into the 16bit word size of my SDRAM. This will only work for resolutions where the X is a multiple of 16 – which for 640, 320 and 160, is true. This saves memory space massively meaning an edge detected image only consumes 38.4kB per frame, increasing transfer rates.
All this magic coding results in a variable threshold edge detector which produces images like so:
Edge detection images with decreasing thresholds along with the original image
There are a few artefacts which you can see to the right of the image where certain pixels seems to be repeated so I’ll need to look into this and resolve it for future implementations.
The next few steps are a bit murky. I’d like to have a go at implementing the Hough transform to figure out the lines though I also want to implement DCT compression, blob detection and integrate a microcontroller to control stuff – busy times!
As it turns out, the end of line issue was because of the H/V start, stop and ref values that I was initializing the camera with. Using the initialization values from the Linux driver for this camera solved these issues – Thank you Jonathan Corbet!