38

I have a problem. My company has given me an awfully boring task. We have two databases of dialog boxes. One of these databases contains images of horrific quality, the other very high quality.

Unfortunately, the dialogs of horrific quality contain important mappings to other info.

I have been tasked with, manually, going through all the bad images and matching them to good images.

Would it be possible to automate this process to any degree? Here is an example of two dialog boxes (randomly pulled from Google images) :

Good quality image

Bad Quality image

So I am currently trying to write a program in C# to pull these photos from the database, cycle through them, find the ones with common shapes, and return theird IDs. What are my best options here ?

Mr.Wizard
  • 24,179
  • 5
  • 44
  • 125
Simon Kiely
  • 5,880
  • 28
  • 94
  • 180
  • 4
    Auwtch.. Good luck with that task. I tried programming something like this once as a challenge. Challenge failed :D Those are some pretty hard algorithms. – Matthias Jul 26 '12 at 14:06
  • File format can be jpg, png, gif...whatever you like; I can programatically convert before comparison. Natively they are png :) – Simon Kiely Jul 26 '12 at 14:10
  • 1
    If image A is a blurry version of image B does that mean that images A and B have the same dimensions? – Running Wild Jul 26 '12 at 15:20
  • Ideally, I would like to make it so that didn't nessecarily have to be the case, @RunningWild, but I understand this would be significantly more difficult :) – Simon Kiely Jul 26 '12 at 16:08
  • The AForge library has a bunch of really useful functions for performing this sort of operation. I've not used it much myself so I can't be any more specific, sorry. – RichK Jul 26 '12 at 16:16
  • I think this question could yield some really interesting answers - that could be really universally applicable to a lot of engineers and companies. With that in mind, I will be adding the maximum bounty to it tomorrow. Everyone get ready! :-) – Simon Kiely Jul 27 '12 at 09:37
  • Do both hi- and low-res images cover the same fragment of a dialog box (as in the example)? Or in other words - do they differ only in terms of quality? – kgadek Jul 29 '12 at 17:23
  • 1
    @kgadek They should differ only in quality. If a non language-specific algorithm exists as well though, that would be mightily impressive :) – Simon Kiely Jul 30 '12 at 09:10
  • What do you know about your 2 sets of images? Is the good set fully inside the bad one? The other way around? Only partially intersecting? – zeFrenchy Jul 30 '12 at 09:39
  • @DominiqueJacquel My thinking was that you would have one general method - you are given a dialog box, and wish to compare it to a set of dialog boxes (stored in any form, database, list etc). This will make the solution most general and easy to apply for people across the board, I believe :). You would then be able to call this function repeatedly on your "good set" to find all of its matches in the "bad set". The good set can be assumed to be completely separate from the bad set. – Simon Kiely Jul 30 '12 at 13:14
  • Thinking about this more, pixel by pixel comparison really isnt a great metric with the artefacts and distortion likely present in the low quality images. A better solution would be some kind of general pattern recognition? – Simon Kiely Jul 31 '12 at 10:04
  • define _horrific quality_. Is it linear blur, or can it be other noise effects, or possibly (affine) transformations as well? – moooeeeep Aug 01 '12 at 20:20
  • @moooeeeep Artefacts and general blurring from poor resizing of images :). – Simon Kiely Aug 02 '12 at 15:46
  • you mean downsampling and upscaling? was a specific interpolation method used? nearest-neighbor, bilinear, bicubic, another? – moooeeeep Aug 02 '12 at 15:52
  • @moooeeeep Whatever 'Paint' uses, I am guessing! :) – Simon Kiely Aug 03 '12 at 18:06

18 Answers18

28

I really see no reason to use any external libraries for this, I've done this sort of thing many times and the following algorithm works quite well. I'll assume that if you're comparing two images that they have the same dimensions, but you can just resize one if they don't.

badness := 0.0
For x, y over the entire image:
  r, g, b := color at x,y in image 1
  R, G, B := color at x,y in image 2
  badness += (r-R)*(r-R) + (g-G)*(g-G) + (b-B)*(b-B)
badness /= (image width) * (image height)

Now you've got a normalized badness value between two images, the lower the badness, the more likely that the images match. This is simple and effective, there are a variety of things that make it work better or faster in certain cases but you probably don't need anything like that. You don't even really need to normalize the badness, but this way you can just come up with a single threshold for it if you want to look at several possible matches manually.


Since this question has gotten some more attention I've decided to add a way to speed this up in cases where you are processing many images many times. I used this approach when I had several tens of thousands of images that I needed to compare, and I was sure that a typical pair of images would be wildly different. I also knew that all of my images would be exactly the same dimensions. In a situation in which you are comparing dialog boxes your typical images may be mostly grey-ish, and some of your images may require resizing (although maybe that just indicates a mis-match), in which case this approach may not gain you as much.

The idea is to form a quad-tree where each node represents the average RGB values of the region that node represents. So an 4x4 image would have a root node with RGB values equal to the average RGB value of the image, its children would have RGB values representing the average RGB value of their respective 2x2 regions, and their children would represent individual pixels. (In practice it is a good idea to not go deeper than a region of about 16x16, at that point you should just start comparing individual pixels.)

Before you start comparing images you will also need to decide on a badness threshold. You won't calculate badnesses above this threshold with any reliable accuracy, so this is basically the threshold at which you are willing to label an image as 'not a match'.

Now when you compare image A to image B, first compare the root nodes of their quad-tree representations. Calculate the badness just as you would for a single pixel image, and if the badness exceeds your threshold then return immediately and report the badness at this level. Because you are using normalized badnesses, and since badnesses are calculated using squared differences, the badness at any particular level will be equal to or less than the badness at lower levels, so if it exceeds the threshold at any points you know it will also exceed the threshold at the level of individual pixels.

If the threshold test passes on an nxn image, just drop to the next level down and compare it like it was a 2nx2n image. Once you get low enough just compare the individual pixels. Depending on your corpus of images this may allow you to skip lots of comparisons.

Running Wild
  • 2,951
  • 18
  • 15
  • 1
    Like this for simplicity, it's basically "how close does a pixel from a match pixel from b" except you may need to scale images so they match dimensions – Charleh Jul 29 '12 at 14:22
  • This would be very inefficient. If there are 10,000 images then that is 100million comparisons of entire images. – MikeKulls Jul 31 '12 at 23:38
  • @MikeKulls Did you read the edit I posted? The whole point was that you don't compare entire images. – Running Wild Jul 31 '12 at 23:42
  • @RunningWild Yes sorry I did miss that. – MikeKulls Aug 01 '12 at 01:59
  • He was gonna do it by hand otherwise, so i doubt there are that many images lol – Rusty Rob Aug 01 '12 at 06:05
  • @RunningWild Awesome answer. Thank you :-). Especially thank you for explaining the logic behind this - not enough people do that. It's really helpful :) – Simon Kiely Aug 01 '12 at 10:02
  • @MikeKulls there are many ways to optimize this, you can calculate an Avarage Brightness for all images, and then compare only those with "Close Enough" brightness... can be done with other properties given heuristics. – Tomer W Aug 01 '12 at 10:48
16

I would personally go for an image hashing algorithm.

The goal of image hashing is to transform image content into a feature sequence, in order to obtain a condensed representation. This feature sequence (i.e. a vector of bits) must be short enough for fast matching and preserve distinguishable features for similarity measurement to be feasible.

There are several algorithms that are freely available through open source communities.

A simple example can be found in this article, where Dr. Neal Krawetz shows how the Average Hash algorithm works:

  1. Reduce size. The fastest way to remove high frequencies and detail is to shrink the image. In this case, shrink it to 8x8 so that there are 64 total pixels. Don't bother keeping the aspect ratio, just crush it down to fit an 8x8 square. This way, the hash will match any variation of the image, regardless of scale or aspect ratio.
  2. Reduce color. The tiny 8x8 picture is converted to a grayscale. This changes the hash from 64 pixels (64 red, 64 green, and 64 blue) to 64 total colors.
  3. Average the colors. Compute the mean value of the 64 colors.
  4. Compute the bits. This is the fun part. Each bit is simply set based on whether the color value is above or below the mean.
  5. Construct the hash. Set the 64 bits into a 64-bit integer. The order does not matter, just as long as you are consistent. (I set the bits from left to right, top to bottom using big-endian.)

David Oftedal wrote a C# command-line application which can classify and compare images using the Average Hash algorithm. (I tested his implementation with your sample images and I got a 98.4% similarity).

The main benefit of this solution is that you read each image only once, create the hashes and classify them based upon their similiarity (using, for example, the Hamming distance).

In this way you decouple the feature extraction phase from the classification phase, and you can easily switch to another hashing algorithm if you find it's not enough accurate.


Edit

You can find a simple example here (It includes a test set of 40 images and it gets a 40/40 score).

Paolo Moretti
  • 54,162
  • 23
  • 101
  • 92
  • 1
    @Simon Kiely have you tried this? The currently most popular solution would be very resource intensive because it needs to compare every pixel of every image with every other image. For 10,000 images that is 100million comparisons of entire images. Where as this solution here would be very efficient and need to read each image only once. – MikeKulls Jul 31 '12 at 23:37
  • This is a really interesting answer. This is definitely something I will look into...98.4% is incredibly impressive. – Simon Kiely Aug 01 '12 at 12:45
  • @Simon You could create a test set of, say, 100 images and see how it performs with your data. – Paolo Moretti Aug 01 '12 at 13:11
  • 1
    @Simon: 98.4% similarity between posted sample images (two?) does not mean that, in a large corpus, it will actually work that good. Rate of false-positives is also important. – tucuxi Aug 01 '12 at 18:12
  • @tucuxi I have attached an example project with a test set a 40 images – Paolo Moretti Aug 02 '12 at 20:24
  • @MikeKulls: that is actually 49995000 comparison. You don't have to compare image2-image1 if image1-image2 has been compared before. – Vili Aug 24 '12 at 10:36
6

Here's a topic discussing image similarity with algorithms, already implemented in OpenCV library. You should have no problem importing low-level functions in your C# application.

Community
  • 1
  • 1
Dmitriy
  • 1,852
  • 4
  • 15
  • 33
  • You are right, OpenCV is the best choice here, but using it in C# will be non-trivial. If the poster has the option of using C++, I like your answer best. – EkoostikMartin Jul 26 '12 at 14:24
  • @EkoostikMartin you can use EmguCV which is a C~ wrapper for OpenCV – Aliostad Jul 26 '12 at 14:28
  • I would not be adverse to using C++...though I would have to learn it; it sounds interesting :-). Thanks for the responses! – Simon Kiely Jul 26 '12 at 14:29
  • In the worst case poster can create a simple facade dll, written in plain C, thus slightly simplifying calls to OpenCV. But, yeah, @Aliostad mentioned EmguCV, it might be a much better solution. – Dmitriy Jul 26 '12 at 14:30
6

The Commercial TinEye API is a really good option.

I've done image matching programs in the past and Image Processing technology these days is amazing, its advanced so much.

ps here's where those two random pics you pulled from google came from: http://www.tineye.com/search/1ec9ebbf1b5b3b81cb52a7e8dbf42cb63126b4ea/

Jeremy Thompson
  • 61,933
  • 36
  • 195
  • 321
5

Since this is a one-off job, I'd make do with a script (choose your favorite language; I'd probably pick Perl) and ImageMagick. You could use C# to accomplish the same as the script, although with more code. Just call the command line utilities and parse the resulting output.

The script to check a pair for similarity would be about 10 lines as follows:

First retrieve the sizes with identify and check aspect ratios nearly the same. If not, no match. If so, then scale the larger image to the size of the smaller with convert. You should experiment a bit in advance with filter options to find the one that produces the most similarity in known-equivalent images. Nine of them are available.

Then use the compare function to produce a similarity metric. Compare is smart enough to deal with translation and cropping. Experiment to find a similarity threshold that doesn't provide too many false positives.

Gene
  • 46,253
  • 4
  • 58
  • 96
2

I would do something like this :

  • If you already know how the blurred images have been blurred, apply the same function to the high quality images before comparison.

    • Then compare the images using least-squares as suggested above.
    • The lowest value should give you a match. Ideally, you would get 0 if both images are identical
    • to speed things up, you could perform most comparison on downsampled images then refine on a selected subsample of the images
  • If you don't know, try various probable functions (JPEG compression, downsampling, ...) and repeat

Nicolas Barbey
  • 6,639
  • 4
  • 28
  • 34
2

You could try Content-Based Image Retrieval (CBIR).

To put it bluntly:

  1. For every image in the database, generate a fingerprint using a Fourier Transform
  2. Load the source image, make a fingerprint of the image
  3. Calculate the Euclidean Distance between the source and all the images in the database
  4. Sort the results
GalacticJello
  • 11,235
  • 2
  • 25
  • 35
2

I think a hybrid approach to this would be best to solve your particular batch matching problem

  1. Apply the Image Hashing algorithm suggested by @Paolo Morreti, to all images
  2. For each image in one set, find the subset of images with a hash closer that a set distance
  3. For this reduced search space you can now apply expensive matching methods as suggested by @Running Wild or @Raskolnikov ... the best one wins.
zeFrenchy
  • 6,541
  • 1
  • 27
  • 36
1

IMHO, best solution is to blur both images and later use some similarity measure (correlation/ mutual information etc) to get top K (K=5 may be?) choices.

ElKamina
  • 7,747
  • 28
  • 43
1

If you extract the contours from the image, you can use ShapeContext to get a very good matching of images.

ShapeContext is build for this exact things (comparing images based on mutual shapes)

ShapeContext implementation links: Original publication A goot ppt on the subject CodeProject page about ShapeContext

*You might need to try a few "contour extraction" techniques like thresholds or fourier transform, or take a look at this CodeProject page about contour extraction

Good Luck.

Community
  • 1
  • 1
Neowizard
  • 2,981
  • 1
  • 21
  • 39
1

If you calculate just pixel difference of images, it will work only if images of the same size or you know exactly how to scale it in horizontal and vertical direction, also you will not have any shift or rotation invariance.

So I recomend to use pixel difference metric only if you have simplest form of problem(images are the same in all characteristics but quality is different, and by the way why quality is different? jpeg artifacts or just rescale?), otherwise i recommend to use normalized cross-correlation, it's more stable metric. You can do it with FFTW or with OpenCV.

mrgloom
  • 20,061
  • 36
  • 171
  • 301
1

If bad quality is just result of lower resolution then:

  • rescale high quality image to low quality image resolution (or rescale both to equal low resolution)
  • compare each pixel color to find closest match

So for example rescaling all of images to 32x32 and comparing that set by pixels should give you quite reasonable results and its still easy to do. Although rescaling method can make difference here.

rumburak
  • 1,097
  • 11
  • 19
0

You could try a block-matching algorithm, although I'm not sure its exact effectiveness against your specific problem - http://scien.stanford.edu/pages/labsite/2001/ee368/projects2001/dropbox/project17/block.html - http://www.aforgenet.com/framework/docs/html/05d0ab7d-a1ae-7ea5-9f7b-a966c7824669.htm

Even if this does not work, you should still check out the Aforge.net library. There are several tools there (including block matching from above) that could help you in this process - http://www.aforgenet.com/

EkoostikMartin
  • 6,831
  • 2
  • 33
  • 62
0

I really like Running Wild's algorithm and I think it can be even more effective if you could make the two images more similar, for example by decreasing the quality of the better one.

BlackBear
  • 22,411
  • 10
  • 48
  • 86
0

Running Wild's answer is very close. What you are doing here is calculating the Peak Signal to Noise Ratio for each image, or PSNR. In your case you really only need the Mean Squared Error, but the squaring component of it helps a great deal in calculating difference between images.

PSNR Reference

Your code should look like:

sum = 0.0
for(imageHeight){
  for(imageWidth){
    errorR = firstImage(r,x,y) - secondImage(r,x,y)
    errorG = firstImage(g,x,y) - secondImage(g,x,y)
    errorB = firstImage(b,x,y) - secondImage(b,x,y)
    totalError = square(errorR) + square(errorG) + square(errorB)
  }
  sum += totalError
}
meanSquaredError = (sum / (imageHeight * imageWidth)) / 3
Raskolnikov
  • 286
  • 3
  • 13
0

I asume the images from the two databases show the same dialog and that the images should be close to identical but of different quality? Then matching images will have same (or very close to same) aspect ratio.

If the low quality images were produced from the high quality images (or equivalent image), then you should use the same image processing procedure as a preprocessing step on the high quality image and match with the low quality image database. Then pixel by pixel comparison or histogram matching should work well.

Image matching can use a lot of resources if you have many images. Maybe a multipass approach is a good idea? For example: Pass 1: use simple mesures like aspect ratio to groupe images (width and height fields in db?) (computationally cheap) Pass 2: match or groupe by histogram for 1st-color-channel (or all channels) (relatively computationally cheap)

I will also recommend OpenCV. You can use it with c,c++ and Python (and soon Java).

mfrellum
  • 169
  • 3
0

Just thinking out loud:

If you use two images that should be compared as layers and combine these (subtract one from the other) you get a new image (some drawing programs can be scripted to do batch conversion, or you could use the GPU by writing a tiny DirectX or OpenGL program)

Next you would have to get the brightness of the resulting image; the darker it is, the better the match.

Emond
  • 50,210
  • 11
  • 84
  • 115
0

Have you tried contour/thresholding techniques in combination with a walking average window (for RGB values ) ?

Ramon Fincken
  • 253
  • 1
  • 5
  • 11