Search code examples
pythonimage-processingmaskscikit-imageglcm

Why do I get different glcms when masking an image and when cropping it


I'm trying to build an image classification model based on features extracted from GLCM. I want to mask some of the images to improve the model, and of course I don't want the GLCM to take those pixels in account. based on the following post I've implemented and conducted a test to make sure that GLCM works correctly for masked images:

1) Take an image and create a cropped version and a masked version(in the same pixels that were cropped).
2
3 4

2) converte images to int32 type and did the following:

#adding 1 to all pixels and setting masked pixels as zero. 
mask_img+=1
crop_img+=1
mask_img[:,:,2][:,int(img.shape[1]/2):int(img.shape[1])] = 0

glcm_crop = greycomatrix(crop_img[:,:,2], 
                levels=257,
                distances=1, 
                angles=0,
                symmetric=True,
                normed=True)

glcm_masked = greycomatrix(mask_img[:,:,2], 
                levels=257,
                distances=1, 
                angles=0,
                symmetric=True,
                normed=True)

#discarding the first row and column that represent zero value pixels
glcm_masked =glcm_masked[1:, 1:, :, :]
glcm_crop = glcm_crop[1:, 1:, :, :]

So in this test, if that GLCM wasn't effected by masked pixels, I would expected that it will be the same matrix for both masked and cropped images. but in reality the matrices were diffrent.

Is my understanding of how GLCM works correct? Does it make sense in theory that those two matrices should be equal?


Solution

  • Let's go through the code slowly. First we import the necessary modules, load an image of type np.int32 and increase the pixel intensities of all pixels of the image by 1:

    import numpy as np
    from skimage import data
    from skimage.feature import greycomatrix
    
    img = data.astronaut().astype(np.int32) + 1
    

    Then we define the shape of the image and the number of intensity levels:

    rows, cols, _ = img.shape
    levels = 256
    

    Now we crop the blue channel of the image, so that we only retain the left half:

    crop_img = img[:, :cols//2, 2]
    

    The right half of the blue channel of the image is masked like this:

    mask_img = img[:, :, 2].copy()
    mask_img[:, cols//2:] = 0
    

    For the sake of this example it is convenient to wrap the GLCM calculation:

    def glcm_wrapper(arr):
        glcm = greycomatrix(arr, levels=levels+1, distances=[1], angles=[0])
        return np.squeeze(glcm)[1:, 1:]
    

    We are ready to check whether the GLCM obtained through both approaches is the same or not:

    glcm_crop = glcm_wrapper(crop_img)
    glcm_mask = glcm_wrapper(mask_img)
    
    print(np.array_equal(glcm_crop, glcm_mask))
    

    If you run all the snippets above, you'll get True.

    It is important to note that if you pass the parameter normed=True to greycomatrix, the resulting GLCMs are different. If you want the matrices to be normalized you have to normalize the GLCMs after removing the first row and the first column. Try this to convince yourself:

    glcm_crop = glcm_crop/glcm_crop.sum()
    glcm_mask = glcm_mask/glcm_mask.sum()
    print(np.allclose(glcm_crop, glcm_mask))