3
\$\begingroup\$

I was curious about image manipulation and I was wondering how it was working, so I made some little functions wich are modifying the pictures passed in parameter.
The graphics results are not too bad, and the execution time is quite correct (even if I think it can clearly be improved).
So (because I'm not an expert) I was wondering if I did terrible things that should be avoided, is there enough/too much comment, are my comments understandable ...
I have also made some unit tests, but the post is quite long so I didn't post them.


Note : each file begins with (I don't put it each time to save space)

#! /usr/bin/env python3
#-*- conding utf-8 -*-

from PIL import Image
import os

Black and white / greyscale :

At the begining I was using the mean function from the module statistics to get the average but was very slow (around 5s more than now). Also I don't get the exact same result for the 2 functions but it's not visible to the human eye, any idea why ?

"""
This module contain the function:

-black_and_white :
    Transform the image passed as parameter in a black and white Image,
    also called greyscale

"""  

def black_and_white(img):
    """
    for each pixel of the img the average of it's RGB component is applied to
    each RGB component wich result in a darker or brighter grey
    """

    px = img.load()
    size_x, size_y = img.size

    for y in range (size_y):
        for x in range (size_x):
            ppx = px[x,y]
            average = int((ppx[0] + ppx[1] + ppx[2]) / 3)
            px[x,y] = (average, average, average)

def GreyScale(img):
    """ Same result but faster and not mine :/ """

    img.paste(img.convert("L"))

if __name__ == "__main__":
    img = Image.open("../image/spidey.jpg")
    print ("Black and White : \n")
    black_and_white(img)
    img.show()
    os.system("pause")

Luminosity modification:

Just a question about why Python does:

>>>50 * 5.1
254.99999999999997  

Well... I didn't know that.

"""
This module contain the functions:

luminosity_variation:
    Change the luminosity of the image by the variation specified,
    the value stick between -255 and 255, or if percentage is true, between
    -100% and 100%

luminosity_percentage:
    Change the percentage luminosity of luminosity of the image :
        50 % = no change
        100 % = 100% luminosity
        0 % = 0% luminosity

Note: the variation are rights would be better if were curves

"""

def luminosity_variation(img, value, percentage=False):
    """
    The function for each pixel attribute the correct luminosity_variation.
    """

    if not percentage:
        mask = img.point(lambda i : i + value)
        img.paste(mask)

    else:
        mask = img.point(lambda i : i + round(value * 2.55))
        img.paste(mask)



def luminosity_percentage(img, percentage):
    """
    The function for each pixel attribute the correct luminosity_variation
    in percent.
    """

    if percentage < 0:
        percentage = 0

    if percentage > 100:
        percentage = 100

    mask = img.point(lambda i : i + round((percentage - 50) * 5.1))
    img.paste(mask)



if __name__ == "__main__":
    img = Image.open("../image/spidey.jpg")
    i = int(input("luminosity variation :"))
    luminosity_variation(img, i, 1)
    img.show()
    img = Image.open("../image/spidey.jpg")
    i = int(input("luminosity percentage :"))
    luminosity_percentage(img, i)
    img.show()
    os.system("pause")

Thresholding

My question here, because I'm using threshold function only inside thresholding function is it better to define threshold inside the thresholding function?

"""
This module contain the function:

-threshold : used to attribute 0 or 255

-thresholding : used to affect a threshold effect


"""

def threshold(value, i):
    """
    dependig to the value of i and value assigne or 0 or 255 can take a single
    value or a tuple of two element
    """

    if (isinstance(value, tuple)):
        if i > value[0] and i <= value [1]:
            return 255
        else:
            return 0

    if value < i:
        return 255
    else:
        return 0

def thresholding(img, value, choosed=""):
    """
    A function that affect a threshold effect on the image, can do it
    on the rgb componant individualy or on all of them is same time
    """

    if choosed: choosed = choosed.capitalize()
    try:
        if choosed in ("R", "G", "B"): R,G,B = img.split()
    except ValueError:
         R,G,B,A = img.split()


    if choosed == "R":
        R = R.point(lambda i : threshold(value, i))
        img.paste(R)

    elif choosed == "G":
        G = G.point(lambda i : threshold(value, i))
        img.paste(G)

    elif choosed == "B":
        B = B.point(lambda i : threshold(value, i))
        img.paste(B)

    else:
        mask = img.point(lambda i : threshold(value, i))
        img.paste(mask)



if __name__ == "__main__":
    img = Image.open("../image/spidey.jpg")
    i = int(input("threshold : "))
    a = input("choose : ")
    thresholding(img, i, a)
    img.show()
    os.system("pause")

Pixelisation :

I used lambda but I'm not sure that it's the best use of this ...

"""
from random import sample

This module contain the function:

-pixelisation :
    pixelise the picture with pixel of size you want


"""

class ImgPixelisation:
    """
    A class containing all the element necessary to do a pixelisation effect
    """

    def __init__(self, img, px_size):

        #actual pos on the pic
        self.x = 0
        self.y = 0
        #size of pic and zone
        self.px_size = px_size
        self.size_x, self.size_y = img.size
        #each pixel value (rgb)
        self.px = img.load()
        self.end = False
        self.avg = tuple

        #lambda to avoid gooing too far on the line/column
        self.max_x = lambda x : self.x + self.px_size if self.x + self.px_size <= self.size_x else self.size_x
        self.max_y = lambda y : self.y + self.px_size if self.y + self.px_size <= self.size_y else self.size_y

    def get_average(self):
        """ get the average of each RGB component of each pixel of the zone """

        sum = [0,0,0]
        nb = self.px_size * self.px_size
        for j in range(self.y, self.max_y(self.y)):
            for i in range(self.x, self.max_x(self.x)):
                sum[0] += self.px[i,j][0]
                sum[1] += self.px[i,j][1]
                sum[2] += self.px[i,j][2]

        self.avg = (round(sum[0] / nb), round(sum[1] / nb), round(sum[2] / nb))

    def fill(self):
        """ fill the zone"""

        for j in range(self.y, self.max_y(self.y)):
            for i in range(self.x, self.max_x(self.x)):
                self.px[i,j] = self.avg

    def next_line(self):
        self.x = 0
        self.y += self.px_size

        if self.x >= self.size_x and self.y >= self.size_y:
            self.end = True

    def next_column(self):
        self.x += self.px_size

        if self.x >= self.size_x and self.y >= self.size_y:
            self.end = True

    def end_line(self):
        return self.x >= self.size_x



def pixelisation(img, px_size):
    """
    The function for each zone of size px_size attribute the average color of
    each pixel of the zone
    """

    pixy = ImgPixelisation(img, px_size)

    while (not pixy.end):
        while(not pixy.end_line()):
            pixy.get_average()
            pixy.fill()
            pixy.next_column()
        pixy.next_line()





if __name__ == "__main__":
    img = Image.open("../image/spidey.jpg")
    i = int(input("pixelisation size : "))
    pixelisation(img, i)
    img.show()
    print ("pixelisation: \n")
    os.system("pause")

Shuffling :

Same way of proceeding as pixelisation.

"""
This module contain the function:

-shuffling : shuffle the picture with zone of size you want


"""

class ImgShuffling:
    """
    A class containing all the element necessary to do a shuffle effect
    """

    def __init__(self, img, crop_size):

        #actual pos on the pic
        self.x = 0
        self.y = 0
        #size of pic and zone
        self.crop_size = crop_size
        self.size_x, self.size_y = img.size
        self.end = False
        self.all_croped = []

        #lambda to avoid gooing too far on the line/column
        self.max_x = lambda x : self.x + self.crop_size if self.x + self.crop_size <= self.size_x else self.size_x
        self.max_y = lambda y : self.y + self.crop_size if self.y + self.crop_size <= self.size_y else self.size_y

    def add_crop(self, img):

        self.all_croped.append(img.crop((self.x, self.y, self.max_x(self.x), self.max_y(self.y))))

    def shuffle(self):

        self.all_croped = sample(self.all_croped, len(self.all_croped))

    def past_it(self, img):

        img.paste(self.all_croped[0], (self.x, self.y))

        del self.all_croped[0]

    def reset(self):
        self.x = 0
        self.y = 0
        self.end = False

    def next_line(self):
        self.x = 0
        self.y += self.crop_size

        if self.x >= self.size_x and self.y >= self.size_y:
            self.end = True

    def next_column(self):
        self.x += self.crop_size

        if self.x >= self.size_x and self.y >= self.size_y:
            self.end = True

    def end_line(self):

        return self.x >= self.size_x



def shuffling(img, crop_size):
    """
    The function for each zone of size crop_size attribute randomly
    another zone of crop_size
    """

    shuffly = ImgShuffling(img, crop_size)

    first_line = True
    nb = 0
    while (not shuffly.end):
        while(not shuffly.end_line()):
            nb = nb + 1 if first_line else nb
            shuffly.add_crop(img)
            shuffly.next_column()
        shuffly.next_line()
        first_line = False

    new_size_x = nb * crop_size
    new_size_y = int(len(shuffly.all_croped) / nb) * crop_size
    shuffled_img = Image.new("RGB", (new_size_x, new_size_y))

    shuffly.shuffle()
    shuffly.reset()
    while (not shuffly.end):
        while(not shuffly.end_line()):
            shuffly.past_it(shuffled_img)
            shuffly.next_column()
        shuffly.next_line()

    img.paste(shuffled_img)



if __name__ == "__main__":
    img = Image.open("../image/spidey.jpg")
    i = int(input("pixelisation size : "))
    shuffling(img, i)
    img.show()
    print ("shuffling: \n")
    os.system("pause")

I have other functions, but I didn't post them because they are less interesting, you can check them here (the full project, with gui and unit tests) : https://github.com/NoobyBoy/Image-Manipulation-Training

I'm also interrested if you have any idea about how to find a picture inside another one, I have tried some things but it was not very effective ^^', personally I was thinking about matrix but, but I'm not very good with them.

\$\endgroup\$
  • 2
    \$\begingroup\$ The reason why 50 × 5.1 is not exactly 255 is that Python's floats are really double-precision binary floating-point numbers, and the closest double-precision binary floating-point number to 5.1 is \$2871044762448691 \over 2^{49}\$, which is just a little bit smaller than 5.1. \$\endgroup\$ – Gareth Rees Oct 31 '18 at 16:29
  • \$\begingroup\$ I see ... is there any way to bypass this default ? \$\endgroup\$ – T. Cav Nov 1 '18 at 13:19
  • 1
    \$\begingroup\$ If you want to represent the fraction \$51\over 10\$ exactly, then use fractions.Fraction(51, 10). \$\endgroup\$ – Gareth Rees Nov 1 '18 at 14:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.