Alas, I’m sure the more astute readers will recognize this article as this blog’s first post. What an occasion to be merry! According to many reputable sources, the thing to do with these passing thoughts in my cerebrum is scribble them down on paper so others can see them “when the time is right.”
I will begin this harrowing blog by discussing a current project, whipping up some image processing with the intent to track cancer cells.
After taking some really neat machine learning courses from Coursera, I started discussing the algorithms I had found particularly clever with a co-worker. Turns out that with a little tweaking I was able to apply the algorithms I was discussing to a project he had been telling me about some time before. The goal of his project was to identify and track cancer cells in video clips, recording both population of the colony and the bounds of the cells.
When I thought about image processing, my gut reaction was “Oh, okay. We’ll just strum out a little program in Octave.” But these guys are from the hardcore C days, and Octave wasn’t going to entertain them. Everything had to be C or, since this is the 21st century now, C++.
So in C++ I wrote a convolution function to apply a filter to an image. The first filter I considered for my project is an edge-detecting filter. These filters are also known as Laplacian filters, a first derivative of spacial values used to detect features and other boundaries.
In this approximation of a sharpening filter, an image is filtered with a matrix, or kernel, and the resulting values used to populate the final image. One such kernel might look like this:
-1 -1 -1
-1 8 -1
-1 -1 -1
I figure the real elegance in the code comes from my indexing scheme. I found a good problem where storing a matrix of values in a one-dimensional array can clear up 90% of the headache, and have exercised the corresponding indicial math judiciously. Most of my reasoning can be gleaned form this comment in my header file:
|* All double-arrays or matrices are going to be stored in a|
|* single list format in a QVector<int>. This means items|
|* are to be addressed as follows:|
|* (x, y) becomes ==> kernel[width*y + x]|
|* and, conversely|
|* kernel[i] becomes ==> (i%width, i/width)|
|* These can be centered around the matrix's "origin" with the|
|* following scheme:|
|* kernel[i] becomes ==> (abs(i%size – size/2), abs(i/size – size/2))|
This code is an initial prototype, designed neither for speed nor production release. Indeed, my friend hinted that I keep an eye on speed, but if that is so large of an issue I would much rather consider a vectorized library from Octave to create a simple, easy, quick, and flexible solution. In fact, that is probably what I will spend the rest of the day doing- creating an analog to time and otherwise compare.
|int width = img->width();|
|int height = img->height();|
|int len = width * height;|
|QVector<QRgb> imvec = vectorOfImagePixels(img);|
|qDebug() << "Length of image vector:" << len;|
|for(int i = 0; i < len; ++i) // perform the convolution|
|MainWindow::convolveImageAtPixel(QVector<QRgb> img, short img_width,|
|QVector<double> kernel, short kernel_size,|
|short y, x;|
|QRgb sum = 0;|
|QString out = &quot;&quot;;|
|for(int ky = 0; ky < kernel_size; ++ky)|
|y = center/img_width – kernel_size/2 + ky;|
|int kColFromY = kernel_size * ky;|
|int imgColFromY = img_width * y; // only calculate when new|
|for(int kx = 0; kx < kernel_size; ++kx)|
|x = center%img_width – kernel_size/2 + kx;|
|if (x < 0 || y < 0 || x >= img_width)|
|double kval = kernel[kColFromY + kx];|
|sum += img[imgColFromY + x] * kval;|
For me in my developmental stage of software development, this is a great example of clean, simple code. I am rather pleased at how simple and close-to-the-problem-at-hand this program has remained, even this far into its life.
This is also the first project I have written entirely in Emacs. Even though I built this program with the Qt framework, I created the interface and even compiled and ran the program from within Emacs. I must say, it has shaped up to be surprisingly different from other programs that I have produced, and I am pleased with the results.
You can find the entire project (as well as some before/after images) on my GitHub.