Basic idea is to find some area on the image that stands out the most from the rest of the image. There are quite a few ways of doing this, and I decided to use only difference of color and cotrast levels, for each block vs entire image.
I use stddev of colors of 3x3 pixel block with the center on the pixel as a pixel's 'contrast' here.
The first version of robocrop is simple and only supports 100x100 blocks, you can't control how block weight is calculated etc.
There are two weights used here to determine what block to use in the end.
First is simply an average contrast level of block's pixels. This promotes blocks with high contrast, such as cat's fur and folds on dog's skin.
Using only this gives passable results for cat/dog, but fails on the ducks, since ground have higher contrast there.
Second is diversity of block's pixels contrast. This weight promotes blocks that has areas of both low and high contrast, making algorithm to more likely capture the 'edges' of objects.
In the case of 'cat' and 'dog', adding diversity to weight will make cropped image to contain some of the blurry background behind the cat/dog, producing better results. It also helps on ducks, since the duck itself has a low contrast, while the ground has high, algorithm will try to fit both duck and the ground into a block, resulting in a block centered on duck's body.
Same algorithm, packed as a new method of Image class, :robocrop ( and :robocrop! ), that allow to override some parameters. Accepts hash of arguments.
:width, :height - dimensions of resulting canvas.
:contrast, :diversity - additional weights that specify how much average contrast and diversity contributes to block weight.
:precision - specifies how many blocks are actually checked. Higher precision means more accurate results, but worse performance. For :precision => X, at most X*X blocks will be tested.
Despite being very slow, this algorithm is simple and usually produce ok results. Interesting is that there is little to no point in comparing block's color versus image color, using only contrast is good enough, if not better.
Also, I like how algorithm captures the edges. Similar result could be accomplished by using some edge detection library ( imagemagick for example ) first, but it would add complexity to the solution.
I use NArray for calclulations, since its fast and does have nice api.
Inefficient, but great results. The code is a bit hard to read. Could you have use a Canvas instead of an NArray? Also, edge detection is not as complicated as you might think it is ;-)