Even though I might not use this package as I don't have any Go project in my pipeline, reading the code was a nice learning experience and just demystified a few things about image processing for me.
Go might not be one of my favorite languages, but its readability is just great (or maybe this codebase isn't idiomatic Go?).
Agreed, that is definitely one of the nice things about Go. Compared to something like Haskel, to pick an extreme example, it is so easy.
Part of that is because there aren't any 'higher level' functions so you are forced to write everything out as explicit loops. Often a bit tedious but it is definitely easier to see what exactly is happening when reading the code.
Side rant on Haskel: I really wanted to get into it, but when you see function signatures like this:
add :: Integer -> Integer -> Integer
You have to wonder what pipe they are smoking. I'm sure someone is going to tell me there is an obscure compsci reason why it couldn't have been this:
I'm sure someone is going to tell me there is an obscure compsci reason why it couldn't have been this: [...]
This "obscure" reason is actually one of the core features of the language and one of the reasons it's called Haskell. A thing that you are going to use all the time writing Haskell. The reason is called currying: https://en.m.wikipedia.org/wiki/Currying
What exactly is your complaint? That function signature wasn't written in the way you wanted? You could always just mentally read a->b->c->d as a,b,c->d and use it accordingly.
Since you're touting the simplicity of Go, I can describe a reason why the function signatures is written like that: a,b,c->d would be a special case of a->b->c->d (multi-arity function without currying, I guess?), so it's not needed.
I'm no expert but isn't the reason so that you know that it's a series of curried functions?
If it was written as
add :: Integer, Integer -> Integer
that would imply that it was a single function taking 2 arguments and that if you called it with only 1 argument it would fail instead of returning you a curried add function (a bit like partial application but not exactly).
Did you benchmark Go vs C/C++ for parallel image processing ? I'm curious to see if there is actually a cost into switching to a higher lever language like Go.
I haven't done any formal benchmarks yet. At the moment the library is not fully optimized, just the low hanging fruit (optimization/bugfixing is the next stage).
But I will definitely post some benchmarking results soon, as they are crucial for the optimization stage.
By the way, any suggestions/contributions to the project are more than welcome!
All of the blur/ functions could see big improvements with some simple changes:
* Use separable kernels whenever possible (this reduces a O(K^2) evaluation to 2K evaluations)
Some filters (like your box filter) can be done efficiently as a linear combination of IID filters (i.e. implicitly computing a pair of running sums, subtracting one from the other as you go).
* The convolve/ package could use some cache blocking, and should definitely have the conditionals on the inner-loop removed.
Unfortunately, these changes will likely make your code a bit more difficult to read.
This is one of the reasons Halide has become so popular for image-processing (if you're interested in high performance image processing without sacrificing maintainability, Halide is definitely worth looking into!)
Thank you for the suggestions! The blur and convolution packages are definitely among the first things to be optimised, lots of other features could benefit from a much needed faster convolve function.
Halide looks very interesting, I found the "Halide Talk" video on their website to be a great primer on their methods.
This looks neat, nice code, but isn't this just the same stuff that xv (1) has been doing since the 90's? It sure looks similar. Maybe this is much faster?
The project was originally for learning purposes, as I wanted to try out Go's concurrency/parallelism for something not web related. It quickly grew into a collection of common image processing functions, so I decided to turn it into a Go package.
The goal of the library is ease of use and development instead of being the fastest implementation (and then loosing too much readability). That being said, it is not slow but there's still room for improvement, which will be addressed during the optimisation and bug fixing stage.
I'm not sure if it's the same as xv, to be honest I haven't worked with it before. But it looks interesting, thanks for the link!
Basically every time that we need to iterate over the Pix []uint8 containing the pixel data, as this can be run in parallel (or concurrently if running in a single logical CPU).
You will usually see it like this:
parallel.Line(height, func(start, end int) {
for y := start; y < end; y++ {
for x := 0; x < w; x++ {
pos:= y*img.Stride + x*4
// ...
}
}
})
parallel.Line is a function that takes a length int and a fn func(start, end int) and it splits the provided length into segments, then it dispatches the provided fn with each segment range to a new goroutine. The number of segments is defined by the number of available logical CPUs.
So basically each dispatched fn is iterating over its assigned range of the Pix []uint8 and the image is split into chunks by height. Notice that only the y-axis loop is assigned a partial range and we iterate over the x-axis first, this is because we want to move sequentially in the slice as much as possible.
During the optimization stage I would like to benchmark against a tiled segmentation instead of a line one, but the current version only implements the latter.
Even though I might not use this package as I don't have any Go project in my pipeline, reading the code was a nice learning experience and just demystified a few things about image processing for me.
Go might not be one of my favorite languages, but its readability is just great (or maybe this codebase isn't idiomatic Go?).