tskMainFunc(), will
execute automatically, starting the DSP/BIOS. This is where
our image processing application begins.
tskMainFunc(), in main.c, opens
the handles to the board for image capture
(VCAP_open()) and to the display
(VCAP_open()) and calls the grayscale function.
Here, several data structures are instantiated that are
defined in the file img_proc.h. The IMAGE
structures will point to the data that is captured by the FPGA
and the data that will be output to the display. The
SCRATCH_PAD structure points to our internal and external
memory buffers used for temporary storage during processing.
LPF_PARAMS is used to store filter coefficients for the low
pass filter.
img_proc() takes us to the file
img_proc.c. First, several variables are
declared and defined. The variable quadrant will denote on
which quadrant of the screen we currently want output;
out_ptr will point to the current output spot in
the output image; and pitch refers to the byte offset between
two lines. This function is the high level control for our
image-processing algorithm. See algorithm flow.
![]() Figure 1:
Algorithm flow.
|
pre_scale_image
function in the file pre_scale_image.c. The
purpose of this function is to take the 640x480 image and
scale it down to a quarter of its size by first downsampling
the input rows by two and then averaging every two pixels
horizontally. The internal and external memory spaces in the
scratch pad are used for this task. The vertical downsampling
will occur when only every other line is read into the
internal memory from the input image. Within internal memory,
we will operate on two lines of data (640 columns/line) at a
time, averaging every two pixels (horizontal neighbors) and
producing two lines of output (320 columns/line) that are
stored in the external memory.
dstr_t are declared. You can view the structure
contents of dstr_t on p. 2-11 of the
IDK
Programmer's Guide. The structure contents are defined
with calls to dstr_open(). This data flow for
the pre-scale is shown in data
flow.
![]() Figure 2:
Data flow of input and output streams.
|
dstr_open():
DSTR_INPUT).
in_image->data, so
only 240 rows. The extra two rows are for buffer.
scratchpad->int_data. This is
where we will be putting the data on each call to
dstr_get().
int_mem from in_image->data.
Because double buffering is used, num_lines is
set to 2.
dstr_get() is called, it will
return a pointer to 2 lines of data, 640 bytes in
length.
dstr_get() is called, but only increment the
stream's internal pointer by 1 (instead of 3) the next time
dstr_get() is called. This is not a parameter
when setting up an output stream.
DSTR_OUTPUT (as done in the next call to
dstr_open()), we would be setting the data to
flow from the Internal Address to the External Address.
pre_scale() (in
pre_scale.c) to operate on one block of data at a
time. This function will perform the horizontal scaling by
averaging every two pixels. This algorithm operates on four
pixels at a time. The entire function is iterated within
pre_scale_image() 120 times, which is the number
of rows in each quadrant. Before
pre_scale_image() exits, the data streams are
closed, and one line is added to the top and bottom of the
image to provide context necessary for the next processing
steps. Now that the input image has been scaled to a quarter
of its initial size, we will proceed with the four image
processing algorithms. In img_proc.c, the
set_ptr() function is called to set the variable
out_ptr to point to the correct quadrant on the
640x480 output image. Then copy_image(),
copy_image.c, is called, performing a direct copy
of the scaled input image into the lower right quadrant of the
output.
out_ptr to point to the
upper right quadrant of the output image and call
conv3x3_image() in conv3x3_image.c.
As with pre_scale_image(), the
_image indicates this is only the wrapper
function for the ImageLIB component, conv3x3().
As before, we must setup our input and output streams. This
time, however, data will be read from the external memory,
into internal memory for processing, and then written to the
output image. Iterating over each row, we compute one line of
data by calling the component function conv3x3()
in conv3x3.c.
conv3x3(), you will see that we perform a 3x3
block convolution, computing one line of data with the low
pass filter mask. Note here that the variables
IN1[i], IN2[i], and
IN3[i] all grab only one pixel at a time. This
is in contrast to the operation of pre_scale()
where the variable in_ptr[i] grabbed 4 pixels at a time. This
is because in_ptr was of type unsigned int, which
implies that it points to four bytes of data at a time.
IN1, IN2, and IN3 are
all of type unsigned char, which implies they point to a
single byte of data. In block convolution, we are computing
the value of one pixel by placing weights on a 3x3 block of
pixels in the input image and computing the sum. What happens
when we are trying to compute the rightmost pixel in a row?
The computation is now bogus. That is why the wrapper
function copies the last good column of data into the two
rightmost columns. You should also note that the component
function ensures output pixels will lie between 0 and 255.
img_proc.c, we can begin the edge
detection algorithm, sobel_image(), for the lower
left quadrant of the output image. This wrapper function,
located in sobel_image.c, performs edge detection
by utilizing the assembly written component function
sobel() in sobel.asm. The wrapper
function is very similar to the others you have seen and
should be straightforward to understand. Understanding the
assembly file is considerably more difficult since you are not
familiar with the assembly language for the c6711 DSP. As
you'll see in the assembly file, the comments are very helpful
since an "equivalent" C program is given there.
img_proc for
this project in your home directory. Enter this new
directory, and then copy the following files as follows
(again, be sure you're in the directory img_proc
when you do this):
copy V:\ece320\idk\c6000\IDK\Examples\NTSC\img_proc
copy V:\ece320\idk\c6000\IDK\Drivers\include
copy V:\ece320\idk\c6000\IDK\Drivers\lib
img_proc.pjt. You
should see a new icon appear at the menu on the left side of
the Code Composer window with the label
img_proc.pjt. Double click on this icon to see a
list of folders. There should be a folder labeled "Source."
Open this folder to see a list of program files.
main.c program calls the
img_proc.c function that displays the output of
four image processing routines in four quadrants on the
monitor. The other files are associated with the four image
processing routines. If you open the "Include" folder, you
will see a list of header files. To inspect the main program,
double click on the main.c icon. A window with
the C code will appear to the right.
tskMainFunc() in the
main.c code. A few lines into this function, you
will see the line
LOG_printf(&trace,"Hello\n");. This line
prints a message to the message log, which can be useful for
debugging. Change the message "Hello\n" to
"Your Name\n" (the "\n" is a
carriage return). Save the file by clicking the little floppy
disk icon at the top left corner of the Code Composer window.
.out" file
has not yet been generated, you need to use the "Rebuild All"
command. The rebuild all command is accomplished by clicking
the button displaying three little red arrows pointing down on
a rectangular box. This will compile every file the main.c
program uses. If you've only changed one file, you only need
to do a "Incremental Build," which is accomplished by clicking
on the button with two little blue arrows pointing into a box
(immediately to the left of the "Rebuild All" button). Click
the "Rebuild All" button to compile all of the code. A window
at the bottom of Code Composer will tell you the status of the
compiling (i.e., whether there were any errors or warnings).
You might notice some warnings after compilation - don't worry
about these.
img_proc.out (the same
procedure as on the other version of Code Composer). Now
select "Debug" -> "Run" to run the program (if you have
problems, you may need to select "Debug" -> "Go Main" before
running). You should see image processing routines running on
the four quadrants of the monitor. The upper left quadrant
(quadrant 0) displays a low pass filtered version of the
input. The low pass filter "passes" the detail in the image,
and attenuates the smooth features, resulting in a "grainy"
image. The operation of the low pass filter code, and how
data is moved to and from the filtering routine, was described
in detail in the previous section. The lower left quadrant
(quadrant 2) displays the output of an edge detection
algorithm. The top right and bottom right quadrants
(quadrants 1 and 3, respectively), show the original input
displayed unprocessed. At this point, you should notice your
name displayed in the message log.
flip_invert.c
to implement an algorithm that horizontally flips and inverts
the input image. The code in flip_invert.c will
operate on one line of the image at a time. The
copyim.c wrapper will call
flip_invert.c once for each row of the prescaled
input image. The flip_invert function call
should appear as follows:
flip_invert(in_data, out_data, cols);
in_data and out_data are
pointers to the input and output buffers in internal memory,
and cols is the length of each column of the
prescaled image.
img_proc.c function should call the
copyim.c wrapper so that the flipped and inverted
image appears in the top right (first) quadrant. The call to
copyim is as follows: copyim(scratch_pad,
out_img, out_ptr, pitch);
im_proc.c code.
The algorithm that copies the image (unprocessed) to the
screen is currently displayed in quadrant 1, so you will need
to comment out its call and replace it with the call to
copyim.
flip_invert.c". You may find the
component code for the low pass filter in
"conv3x3_c.c" helpful in giving you an idea of
how to get started. To compile this code, you must include it
in the "img_proc" project, so that it appears as
an icon in Code Composer. To include your new file, right
click on the "img_proc.pjt" icon in the left
window of Code Composer, and select "Add Files."