Image similarity measure











up vote
2
down vote

favorite












I'm attempting to speed up my genetic algorithm that modifies images. After searching, I've found that my fitness function is the bottleneck, taking sometimes up to 11 seconds to complete.



The fitness function accepts two images and returns a float representing the total difference between the images' corresponding pixels, measured as distance in Cartesian RGB space.



To test, I used some large images of the same size (1254 x 834). What can I do to speed this process up?



def fitness(original, new):
fitness = 0

for x in range(0, width):
for y in range(0, height):
r1, g1, b1 = original.getpixel((x, y))
r2, g2, b2 = new.getpixel((x, y))

deltaRed = abs(r1 - r2)
deltaGreen = abs(g1 - g2)
deltaBlue = abs(b1 - b2)

pixelFitness = pixelFitness = math.sqrt(deltaRed ** 2 + deltaGreen ** 2 + deltaBlue ** 2)

fitness += pixelFitness

return fitness









share|improve this question




















  • 1




    getpixel((x, y)) is very likely to be the trouble, as it is very slow operation for every language(and API) I know. I dont know much of python but finding alternative should be easy task for you.
    – wondra
    Jan 29 '15 at 22:12










  • The three calls to abs() are pretty pointless, given that the only thing we do with the results is to square them.
    – Toby Speight
    yesterday










  • I changed the title so that it describes what the code does per site goals: "State what your code does in your title, not your main concerns about it.". Please check that I haven't misrepresented your code, and correct it if I have.
    – Toby Speight
    yesterday















up vote
2
down vote

favorite












I'm attempting to speed up my genetic algorithm that modifies images. After searching, I've found that my fitness function is the bottleneck, taking sometimes up to 11 seconds to complete.



The fitness function accepts two images and returns a float representing the total difference between the images' corresponding pixels, measured as distance in Cartesian RGB space.



To test, I used some large images of the same size (1254 x 834). What can I do to speed this process up?



def fitness(original, new):
fitness = 0

for x in range(0, width):
for y in range(0, height):
r1, g1, b1 = original.getpixel((x, y))
r2, g2, b2 = new.getpixel((x, y))

deltaRed = abs(r1 - r2)
deltaGreen = abs(g1 - g2)
deltaBlue = abs(b1 - b2)

pixelFitness = pixelFitness = math.sqrt(deltaRed ** 2 + deltaGreen ** 2 + deltaBlue ** 2)

fitness += pixelFitness

return fitness









share|improve this question




















  • 1




    getpixel((x, y)) is very likely to be the trouble, as it is very slow operation for every language(and API) I know. I dont know much of python but finding alternative should be easy task for you.
    – wondra
    Jan 29 '15 at 22:12










  • The three calls to abs() are pretty pointless, given that the only thing we do with the results is to square them.
    – Toby Speight
    yesterday










  • I changed the title so that it describes what the code does per site goals: "State what your code does in your title, not your main concerns about it.". Please check that I haven't misrepresented your code, and correct it if I have.
    – Toby Speight
    yesterday













up vote
2
down vote

favorite









up vote
2
down vote

favorite











I'm attempting to speed up my genetic algorithm that modifies images. After searching, I've found that my fitness function is the bottleneck, taking sometimes up to 11 seconds to complete.



The fitness function accepts two images and returns a float representing the total difference between the images' corresponding pixels, measured as distance in Cartesian RGB space.



To test, I used some large images of the same size (1254 x 834). What can I do to speed this process up?



def fitness(original, new):
fitness = 0

for x in range(0, width):
for y in range(0, height):
r1, g1, b1 = original.getpixel((x, y))
r2, g2, b2 = new.getpixel((x, y))

deltaRed = abs(r1 - r2)
deltaGreen = abs(g1 - g2)
deltaBlue = abs(b1 - b2)

pixelFitness = pixelFitness = math.sqrt(deltaRed ** 2 + deltaGreen ** 2 + deltaBlue ** 2)

fitness += pixelFitness

return fitness









share|improve this question















I'm attempting to speed up my genetic algorithm that modifies images. After searching, I've found that my fitness function is the bottleneck, taking sometimes up to 11 seconds to complete.



The fitness function accepts two images and returns a float representing the total difference between the images' corresponding pixels, measured as distance in Cartesian RGB space.



To test, I used some large images of the same size (1254 x 834). What can I do to speed this process up?



def fitness(original, new):
fitness = 0

for x in range(0, width):
for y in range(0, height):
r1, g1, b1 = original.getpixel((x, y))
r2, g2, b2 = new.getpixel((x, y))

deltaRed = abs(r1 - r2)
deltaGreen = abs(g1 - g2)
deltaBlue = abs(b1 - b2)

pixelFitness = pixelFitness = math.sqrt(deltaRed ** 2 + deltaGreen ** 2 + deltaBlue ** 2)

fitness += pixelFitness

return fitness






python performance image edit-distance genetic-algorithm






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited yesterday









200_success

127k15149412




127k15149412










asked Jan 29 '15 at 22:03









TimCPogue

344




344








  • 1




    getpixel((x, y)) is very likely to be the trouble, as it is very slow operation for every language(and API) I know. I dont know much of python but finding alternative should be easy task for you.
    – wondra
    Jan 29 '15 at 22:12










  • The three calls to abs() are pretty pointless, given that the only thing we do with the results is to square them.
    – Toby Speight
    yesterday










  • I changed the title so that it describes what the code does per site goals: "State what your code does in your title, not your main concerns about it.". Please check that I haven't misrepresented your code, and correct it if I have.
    – Toby Speight
    yesterday














  • 1




    getpixel((x, y)) is very likely to be the trouble, as it is very slow operation for every language(and API) I know. I dont know much of python but finding alternative should be easy task for you.
    – wondra
    Jan 29 '15 at 22:12










  • The three calls to abs() are pretty pointless, given that the only thing we do with the results is to square them.
    – Toby Speight
    yesterday










  • I changed the title so that it describes what the code does per site goals: "State what your code does in your title, not your main concerns about it.". Please check that I haven't misrepresented your code, and correct it if I have.
    – Toby Speight
    yesterday








1




1




getpixel((x, y)) is very likely to be the trouble, as it is very slow operation for every language(and API) I know. I dont know much of python but finding alternative should be easy task for you.
– wondra
Jan 29 '15 at 22:12




getpixel((x, y)) is very likely to be the trouble, as it is very slow operation for every language(and API) I know. I dont know much of python but finding alternative should be easy task for you.
– wondra
Jan 29 '15 at 22:12












The three calls to abs() are pretty pointless, given that the only thing we do with the results is to square them.
– Toby Speight
yesterday




The three calls to abs() are pretty pointless, given that the only thing we do with the results is to square them.
– Toby Speight
yesterday












I changed the title so that it describes what the code does per site goals: "State what your code does in your title, not your main concerns about it.". Please check that I haven't misrepresented your code, and correct it if I have.
– Toby Speight
yesterday




I changed the title so that it describes what the code does per site goals: "State what your code does in your title, not your main concerns about it.". Please check that I haven't misrepresented your code, and correct it if I have.
– Toby Speight
yesterday










2 Answers
2






active

oldest

votes

















up vote
2
down vote



accepted










Thanks to @wondra for the help. I rewrote the function without the getpixel and just loaded the pixels using the image.load() method. This caused the execution time to reduce down to 3.09 seconds.



Rewritten function:



def fitness_new(new):
fitness = 0

new_data = new.load()

for x in range(0, width):
for y in range(0, height):
r1, g1, b1 = optimal_data[x, y]
r2, g2, b2 = new_data[x, y]

deltaRed = abs(r1 - r2)
deltaGreen = abs(g1 - g2)
deltaBlue = abs(b1 - b2)

pixelFitness = pixelFitness = math.sqrt(deltaRed ** 2 + deltaGreen ** 2 + deltaBlue ** 2)

fitness += pixelFitness

return fitness


optimal_data is preloaded as a global variable instead of being passed as an argument, because it is accessed elsewhere. I know this is probably not the most ideal method.






share|improve this answer




























    up vote
    1
    down vote













    To speed this up further, you should use the numpy interface of PIL (if you are not yet using PIL, you should, for this reason):



    from PIL import Image
    import numpy as np

    # `int` important because otherwise it might wrap around when subtracting
    optimal_data = np.asarray(Image.open("optimal.png"), dtype=int)
    new = np.random.randint(0, 256, optimal_data.shape)

    def fitness(optimal_data, new):
    return np.sqrt(((optimal_data - new)**2).sum(axis=-1)).sum()


    This takes only 258 ms ± 2.21 ms for a 2424 x 2424 pixel image on my machine, while the function by @TimCPogue takes 9.93 s ± 465 ms with the same images.



    Note that the array has the shape (width, height, channels), where channels is usually 4 (red, green, blue, alpha), not 3 like your code assumes. If you want to disregard differences in alpha, either set the alpha channel of the new image to the one of the optimal data (new[:,:,-1] = optimal_data[:,:,-1]), or slice in the fitness (optimal_data[...,:-1] - new[...,:-1]).



    For some more readability and the possibility to use a different norm in the future (albeit at the cost of about 30% speed), you could make the norm to use a parameter and use np.linalg.norm, as suggested in the comments by @GarethReese:



    def fitness(optimal_data, new, norm=np.linalg.norm):
    return norm(optimal_data - new, axis=-1).sum()





    share|improve this answer























    • Consider using numpy.linalg.norm instead of writing your own 2-norm.
      – Gareth Rees
      yesterday










    • @GarethRees: While it does make it more readable, it is about 30% slower. Even after making norm = np.linalg.norm a local variable.
      – Graipher
      yesterday











    Your Answer





    StackExchange.ifUsing("editor", function () {
    return StackExchange.using("mathjaxEditing", function () {
    StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
    StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
    });
    });
    }, "mathjax-editing");

    StackExchange.ifUsing("editor", function () {
    StackExchange.using("externalEditor", function () {
    StackExchange.using("snippets", function () {
    StackExchange.snippets.init();
    });
    });
    }, "code-snippets");

    StackExchange.ready(function() {
    var channelOptions = {
    tags: "".split(" "),
    id: "196"
    };
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function() {
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled) {
    StackExchange.using("snippets", function() {
    createEditor();
    });
    }
    else {
    createEditor();
    }
    });

    function createEditor() {
    StackExchange.prepareEditor({
    heartbeatType: 'answer',
    convertImagesToLinks: false,
    noModals: true,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: null,
    bindNavPrevention: true,
    postfix: "",
    imageUploader: {
    brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
    contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
    allowUrls: true
    },
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    });


    }
    });














    draft saved

    draft discarded


















    StackExchange.ready(
    function () {
    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f79023%2fimage-similarity-measure%23new-answer', 'question_page');
    }
    );

    Post as a guest















    Required, but never shown

























    2 Answers
    2






    active

    oldest

    votes








    2 Answers
    2






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes








    up vote
    2
    down vote



    accepted










    Thanks to @wondra for the help. I rewrote the function without the getpixel and just loaded the pixels using the image.load() method. This caused the execution time to reduce down to 3.09 seconds.



    Rewritten function:



    def fitness_new(new):
    fitness = 0

    new_data = new.load()

    for x in range(0, width):
    for y in range(0, height):
    r1, g1, b1 = optimal_data[x, y]
    r2, g2, b2 = new_data[x, y]

    deltaRed = abs(r1 - r2)
    deltaGreen = abs(g1 - g2)
    deltaBlue = abs(b1 - b2)

    pixelFitness = pixelFitness = math.sqrt(deltaRed ** 2 + deltaGreen ** 2 + deltaBlue ** 2)

    fitness += pixelFitness

    return fitness


    optimal_data is preloaded as a global variable instead of being passed as an argument, because it is accessed elsewhere. I know this is probably not the most ideal method.






    share|improve this answer

























      up vote
      2
      down vote



      accepted










      Thanks to @wondra for the help. I rewrote the function without the getpixel and just loaded the pixels using the image.load() method. This caused the execution time to reduce down to 3.09 seconds.



      Rewritten function:



      def fitness_new(new):
      fitness = 0

      new_data = new.load()

      for x in range(0, width):
      for y in range(0, height):
      r1, g1, b1 = optimal_data[x, y]
      r2, g2, b2 = new_data[x, y]

      deltaRed = abs(r1 - r2)
      deltaGreen = abs(g1 - g2)
      deltaBlue = abs(b1 - b2)

      pixelFitness = pixelFitness = math.sqrt(deltaRed ** 2 + deltaGreen ** 2 + deltaBlue ** 2)

      fitness += pixelFitness

      return fitness


      optimal_data is preloaded as a global variable instead of being passed as an argument, because it is accessed elsewhere. I know this is probably not the most ideal method.






      share|improve this answer























        up vote
        2
        down vote



        accepted







        up vote
        2
        down vote



        accepted






        Thanks to @wondra for the help. I rewrote the function without the getpixel and just loaded the pixels using the image.load() method. This caused the execution time to reduce down to 3.09 seconds.



        Rewritten function:



        def fitness_new(new):
        fitness = 0

        new_data = new.load()

        for x in range(0, width):
        for y in range(0, height):
        r1, g1, b1 = optimal_data[x, y]
        r2, g2, b2 = new_data[x, y]

        deltaRed = abs(r1 - r2)
        deltaGreen = abs(g1 - g2)
        deltaBlue = abs(b1 - b2)

        pixelFitness = pixelFitness = math.sqrt(deltaRed ** 2 + deltaGreen ** 2 + deltaBlue ** 2)

        fitness += pixelFitness

        return fitness


        optimal_data is preloaded as a global variable instead of being passed as an argument, because it is accessed elsewhere. I know this is probably not the most ideal method.






        share|improve this answer












        Thanks to @wondra for the help. I rewrote the function without the getpixel and just loaded the pixels using the image.load() method. This caused the execution time to reduce down to 3.09 seconds.



        Rewritten function:



        def fitness_new(new):
        fitness = 0

        new_data = new.load()

        for x in range(0, width):
        for y in range(0, height):
        r1, g1, b1 = optimal_data[x, y]
        r2, g2, b2 = new_data[x, y]

        deltaRed = abs(r1 - r2)
        deltaGreen = abs(g1 - g2)
        deltaBlue = abs(b1 - b2)

        pixelFitness = pixelFitness = math.sqrt(deltaRed ** 2 + deltaGreen ** 2 + deltaBlue ** 2)

        fitness += pixelFitness

        return fitness


        optimal_data is preloaded as a global variable instead of being passed as an argument, because it is accessed elsewhere. I know this is probably not the most ideal method.







        share|improve this answer












        share|improve this answer



        share|improve this answer










        answered Jan 29 '15 at 22:30









        TimCPogue

        344




        344
























            up vote
            1
            down vote













            To speed this up further, you should use the numpy interface of PIL (if you are not yet using PIL, you should, for this reason):



            from PIL import Image
            import numpy as np

            # `int` important because otherwise it might wrap around when subtracting
            optimal_data = np.asarray(Image.open("optimal.png"), dtype=int)
            new = np.random.randint(0, 256, optimal_data.shape)

            def fitness(optimal_data, new):
            return np.sqrt(((optimal_data - new)**2).sum(axis=-1)).sum()


            This takes only 258 ms ± 2.21 ms for a 2424 x 2424 pixel image on my machine, while the function by @TimCPogue takes 9.93 s ± 465 ms with the same images.



            Note that the array has the shape (width, height, channels), where channels is usually 4 (red, green, blue, alpha), not 3 like your code assumes. If you want to disregard differences in alpha, either set the alpha channel of the new image to the one of the optimal data (new[:,:,-1] = optimal_data[:,:,-1]), or slice in the fitness (optimal_data[...,:-1] - new[...,:-1]).



            For some more readability and the possibility to use a different norm in the future (albeit at the cost of about 30% speed), you could make the norm to use a parameter and use np.linalg.norm, as suggested in the comments by @GarethReese:



            def fitness(optimal_data, new, norm=np.linalg.norm):
            return norm(optimal_data - new, axis=-1).sum()





            share|improve this answer























            • Consider using numpy.linalg.norm instead of writing your own 2-norm.
              – Gareth Rees
              yesterday










            • @GarethRees: While it does make it more readable, it is about 30% slower. Even after making norm = np.linalg.norm a local variable.
              – Graipher
              yesterday















            up vote
            1
            down vote













            To speed this up further, you should use the numpy interface of PIL (if you are not yet using PIL, you should, for this reason):



            from PIL import Image
            import numpy as np

            # `int` important because otherwise it might wrap around when subtracting
            optimal_data = np.asarray(Image.open("optimal.png"), dtype=int)
            new = np.random.randint(0, 256, optimal_data.shape)

            def fitness(optimal_data, new):
            return np.sqrt(((optimal_data - new)**2).sum(axis=-1)).sum()


            This takes only 258 ms ± 2.21 ms for a 2424 x 2424 pixel image on my machine, while the function by @TimCPogue takes 9.93 s ± 465 ms with the same images.



            Note that the array has the shape (width, height, channels), where channels is usually 4 (red, green, blue, alpha), not 3 like your code assumes. If you want to disregard differences in alpha, either set the alpha channel of the new image to the one of the optimal data (new[:,:,-1] = optimal_data[:,:,-1]), or slice in the fitness (optimal_data[...,:-1] - new[...,:-1]).



            For some more readability and the possibility to use a different norm in the future (albeit at the cost of about 30% speed), you could make the norm to use a parameter and use np.linalg.norm, as suggested in the comments by @GarethReese:



            def fitness(optimal_data, new, norm=np.linalg.norm):
            return norm(optimal_data - new, axis=-1).sum()





            share|improve this answer























            • Consider using numpy.linalg.norm instead of writing your own 2-norm.
              – Gareth Rees
              yesterday










            • @GarethRees: While it does make it more readable, it is about 30% slower. Even after making norm = np.linalg.norm a local variable.
              – Graipher
              yesterday













            up vote
            1
            down vote










            up vote
            1
            down vote









            To speed this up further, you should use the numpy interface of PIL (if you are not yet using PIL, you should, for this reason):



            from PIL import Image
            import numpy as np

            # `int` important because otherwise it might wrap around when subtracting
            optimal_data = np.asarray(Image.open("optimal.png"), dtype=int)
            new = np.random.randint(0, 256, optimal_data.shape)

            def fitness(optimal_data, new):
            return np.sqrt(((optimal_data - new)**2).sum(axis=-1)).sum()


            This takes only 258 ms ± 2.21 ms for a 2424 x 2424 pixel image on my machine, while the function by @TimCPogue takes 9.93 s ± 465 ms with the same images.



            Note that the array has the shape (width, height, channels), where channels is usually 4 (red, green, blue, alpha), not 3 like your code assumes. If you want to disregard differences in alpha, either set the alpha channel of the new image to the one of the optimal data (new[:,:,-1] = optimal_data[:,:,-1]), or slice in the fitness (optimal_data[...,:-1] - new[...,:-1]).



            For some more readability and the possibility to use a different norm in the future (albeit at the cost of about 30% speed), you could make the norm to use a parameter and use np.linalg.norm, as suggested in the comments by @GarethReese:



            def fitness(optimal_data, new, norm=np.linalg.norm):
            return norm(optimal_data - new, axis=-1).sum()





            share|improve this answer














            To speed this up further, you should use the numpy interface of PIL (if you are not yet using PIL, you should, for this reason):



            from PIL import Image
            import numpy as np

            # `int` important because otherwise it might wrap around when subtracting
            optimal_data = np.asarray(Image.open("optimal.png"), dtype=int)
            new = np.random.randint(0, 256, optimal_data.shape)

            def fitness(optimal_data, new):
            return np.sqrt(((optimal_data - new)**2).sum(axis=-1)).sum()


            This takes only 258 ms ± 2.21 ms for a 2424 x 2424 pixel image on my machine, while the function by @TimCPogue takes 9.93 s ± 465 ms with the same images.



            Note that the array has the shape (width, height, channels), where channels is usually 4 (red, green, blue, alpha), not 3 like your code assumes. If you want to disregard differences in alpha, either set the alpha channel of the new image to the one of the optimal data (new[:,:,-1] = optimal_data[:,:,-1]), or slice in the fitness (optimal_data[...,:-1] - new[...,:-1]).



            For some more readability and the possibility to use a different norm in the future (albeit at the cost of about 30% speed), you could make the norm to use a parameter and use np.linalg.norm, as suggested in the comments by @GarethReese:



            def fitness(optimal_data, new, norm=np.linalg.norm):
            return norm(optimal_data - new, axis=-1).sum()






            share|improve this answer














            share|improve this answer



            share|improve this answer








            edited yesterday

























            answered yesterday









            Graipher

            22.8k53384




            22.8k53384












            • Consider using numpy.linalg.norm instead of writing your own 2-norm.
              – Gareth Rees
              yesterday










            • @GarethRees: While it does make it more readable, it is about 30% slower. Even after making norm = np.linalg.norm a local variable.
              – Graipher
              yesterday


















            • Consider using numpy.linalg.norm instead of writing your own 2-norm.
              – Gareth Rees
              yesterday










            • @GarethRees: While it does make it more readable, it is about 30% slower. Even after making norm = np.linalg.norm a local variable.
              – Graipher
              yesterday
















            Consider using numpy.linalg.norm instead of writing your own 2-norm.
            – Gareth Rees
            yesterday




            Consider using numpy.linalg.norm instead of writing your own 2-norm.
            – Gareth Rees
            yesterday












            @GarethRees: While it does make it more readable, it is about 30% slower. Even after making norm = np.linalg.norm a local variable.
            – Graipher
            yesterday




            @GarethRees: While it does make it more readable, it is about 30% slower. Even after making norm = np.linalg.norm a local variable.
            – Graipher
            yesterday


















            draft saved

            draft discarded




















































            Thanks for contributing an answer to Code Review Stack Exchange!


            • Please be sure to answer the question. Provide details and share your research!

            But avoid



            • Asking for help, clarification, or responding to other answers.

            • Making statements based on opinion; back them up with references or personal experience.


            Use MathJax to format equations. MathJax reference.


            To learn more, see our tips on writing great answers.





            Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


            Please pay close attention to the following guidance:


            • Please be sure to answer the question. Provide details and share your research!

            But avoid



            • Asking for help, clarification, or responding to other answers.

            • Making statements based on opinion; back them up with references or personal experience.


            To learn more, see our tips on writing great answers.




            draft saved


            draft discarded














            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f79023%2fimage-similarity-measure%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown





















































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown

































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown







            Popular posts from this blog

            List directoties down one level, excluding some named directories and files

            list processes belonging to a network namespace

            list systemd RuntimeDirectory mounts