1

I am trying to come up with an efficient algorithm to query the top k most dominant colors in a UIImage (jpg or png). By dominant color I mean the color that is present in the most amount of pixels. The use case is mostly geared toward finding the top-1 (single most dominant) color to figure out the images background. Below I document the algorithm I am currently trying to implement and wanted some feedback.

First my algorithm takes an image and draws it into a bitmap context, I do this so I have control over how many bytes per pixel will be present in my image-data for processing/parsing.

Second, after drawing the image, I loop through every pixel in the image buffer. I quickly realized looping through every pixel would not scale, this became a huge performance bottleneck. In order to optimize this, I realized I need not analyze every pixel in the image. I also realized that as I scaled down an image, the dominant colors in the image became more prominent and it resulted in looping through far fewer pixels. So the second step is actually to loop through every pixel in the resized image buffer:

Third, as I loop through each pixel I build up a CountedSet of UIColor objects, this basicly keeps track of a histogram of color counts. Finally, once I have my counted set it is very easy to then loop through it and respond to top-k dominant color queries.

So my algorithm in short is to resize the image (scale it down by some function proportional to the images size), draw it into a bitmap context buffer once and cache it, loop through all data in the buffer and build out a histogram.

My question to the stack overflow community is how efficient is my algorithm, and are there any gains or further optimizations I can make? I just need something that gives me reasonable performance and after doing some performance testing this seemed to work pretty darn well. Furthermore, how accurate will this be? Particularly the rescale operation kinda worries me. Am I trading signficant amount of accuracy for performance here? At the end of the day this will mostly just be used to determine the background color of an image.

Ideas for potential performance improvements: 1) When analyzing a single dominant color do some math to figure out if I have already found the most dominant color based on number of pixels analyzed and exit early. 2) For the top k query, answer it quickly by levaraging a binary-heap data structure (typical top-k query algo)

AyBayBay
  • 1,558
  • 2
  • 17
  • 35
  • If you want us to discuss the efficiency of your algorithm, you're going to have to show it. – sschale May 24 '16 at 00:41
  • Why do I need to show it? My description is pretty clear, I explain exactly what my algorithm is doing and I break down the decisions/motivations behind each optimization along the way. Don't think its entirely necessary to just post my entire algorithm/code too an already lengthy stack overflow question. – AyBayBay May 24 '16 at 00:44
  • Downscaling an image requires looking at each pixel so you can pick a new pixel that is closest to the average of some group of neighbors. Presumably this is done in hardware which is why you perceive a scale-first algorithm to be faster than your approach, but at least to me it seems like you're adding complexity and reducing the accuracy of your overall image since it isn't required that any pixels in the original image maintain the same color in the downscaled image. Try writing your original image-scanner in something that takes advantage of the graphics hardware then time it. – par May 24 '16 at 00:44
  • @par I do not have much experience on iOS working directly with the underlying GPU. Can you provide me some suggestions on where to start? I am using CoreGraphics for the draw/rescale operation, how can I also exploit it to loop through pixels in an image, is there anyway to do that? – AyBayBay May 24 '16 at 00:55
  • 1
    You can try looking at [Metal](https://developer.apple.com/library/ios/documentation/Miscellaneous/Conceptual/MetalProgrammingGuide/Introduction/Introduction.html), but this is a too big a topic for a SO comment (or even a single answer). CoreGraphics rescale is doing a direct-to-hardware operation though which is why you see the speed difference. Get your analysis code into the hardware and you will be able to just scan the raw image faster than anything else. – par May 24 '16 at 01:01
  • @par If you could respond to this question with a comment similar to above I can accept your answer and close this out :) It has pointed me in the right direction and thats all I needed to be honest. I also agree this was a very large question, I did my best to make it concise I just needed to get all the data out there. – AyBayBay May 24 '16 at 02:27
  • @AyBayBay done, and thank you. – par May 24 '16 at 04:15
  • histogram acquisition using CPU only for resolutions around `1024*1024*32bit` should take just few [ms] or up to 100ms for true-color on standard PC. What times you got? On what HW? and what time you want to achieve? – Spektre May 24 '16 at 07:47

2 Answers2

2

You can use some performance tweaks to avoid the downscaling altogether. As we do not see your implementation is hard to say where the bottleneck really is. So here some pointers what to look/check for or improve. Take in mind I do not code for your environment so take extreme prejudice:

  1. pixel access

    Most pixel access function I saw are SLOOOW especially functions called putpixel,getpixel,pixels,.... Because in each single pixel access they are doing too many sanity/safety checks and color/space/address conversions. Instead use direct pixel access. Most of the image interfaces I saw have some kind of ScanLine[] access which gives you direct pointer to a single line in an image. So if you fill your own array of pointers with it you obtain direct pixel access without any slowdowns. This usually speeds up the algorithm from 100 to 10000 times on most platforms (depends on the usage).

    To check for this try to read or fill image 1024*1024*32bit and measure the time. On standard PC it should take up to few [ms] or less. If you got slow access it could be even seconds. For more info see Display an array of color in C

  2. Dominant color

    if #1 is still not fast enough you can take advantage of that dominant color has highest probability in the image. So in theory you do not need to sample whole image instead you could:

    sample every n-th pixel (which is downscaling with nearest neighbor filter) or use randomized pixel positions for sampling. Both approaches have their pros and cons but if you combine them you could get much better results with much less pixels to process then the whole image. Of coarse this will lead to wrong results on some occasions (when you miss many of the dominant pixels) which is improbable but possible.

  3. histogram structure

    for low color count like up to 16bit you can use bucket sort/histogram acquisition which is fast and can be done in O(n) where n is the number of pixels. No searching needed. So if you reduce colors from true color to 16 bit you can significantly boost the speed of histogram computation. Because you lower the constant time hugely and also the complexity goes from O(n*m) to O(n) which is for high color count m really big difference. See my C++ histogram example it is in HSV but in RGB is almost the same...

    In case you need true-color you got 16.7M colors which is not practical for bucket sort style. So you need to use binary search and dictionary to speed up the color search in histogram. If you do not have this then this is your slow down.

  4. histogram sort

    How did you sort the histogram? If you got wrong sort implementation it could take much time for big color counts. I usually use bubble-sort in my examples because it is less code to write and usually enough. But I saw here on SO too many times wrongly implemented bubble sort using alway the worse case time T(n^2) which is wrong (and even I sometimes do it). For time sensitive code I use quick-sort. See bubble sort in C++.

Also your task is really resembling Color quantization (or it is just me?) so take a look at: Effective gif/image color quantization?

Community
  • 1
  • 1
Spektre
  • 41,942
  • 8
  • 91
  • 312
1

Downscaling an image requires looking at each pixel so you can pick a new pixel that is closest to the average color of some group of neighbors. The reason this appears to happen so fast compared to your implementation of iterating through all the pixels is that CoreGraphics hands the scaling task off to the GPU hardware, whereas your approach uses the CPU to iterate through each pixel which is much slower.

So the thing you need to do is write some GPU-based code to scan through your original image and look at each pixel, tallying up the color counts as you go. This has the advantage not only of being very fast, but you'll also get an accurate count of colors. Downsampling produces as I mentioned pixels that are color averages, so you won't end up with reliably correct color counts that correlate to your original image (unless you happen to be downscaling solid colors, but in the typical case you'll end up with something other than you started with).

I recommend looking into Apple's Metal framework for an API that lets you write code directly for the GPU. It'll be a challenge to learn, but I think you'll find it interesting and when you're done your code will scan original images extremely fast without having to go through any extra downsampling effort.

par
  • 16,065
  • 4
  • 61
  • 77
  • Thank you @par this has pointed me in the right-direction to look for lower-level GPU based image processing frameworks. CoreImage seems to look pretty good for what I want, it actually has a HistogramFilter you can apply to the image. The last step for me is how I actually analyze this histogram which isnt clear. I posted a follow-up question here just in case you have knowledge on this too, here it is :P http://stackoverflow.com/questions/37470847/how-to-extract-dominant-color-from-ciareahistogram – AyBayBay May 26 '16 at 21:00