sábado, 20 de enero de 2018

Tratamiento de imágenes con openCV en Python

El post anterior incluye una referencia de la cual adapté el procedimiento para obtener los subarrays a los cuales allí se hacía mención y cuya temática principal era el tratamiento de imágenes.

El artículo referencia tenía como objetivo entender el significado y la aplicación de la convolución de imágenes para difuminarlas, incrementar su contraste o delimitar bordes a través de la aplicación de diferentes filtros que allí se denominaban kernels empleando OpenCV con Python.

Por otra parte, viendo el enorme potencial del módulo me dispuse a modificar ligeramente el script allí contenido para ver como se comportaba con el tratamiento de imágenes GeoTiff georreferenciadas. Como sospechaba que de alguna manera se perdería la referencia espacial de la imagen (o desconozco, por ahora, la manera de obtenerla e incorporarla con este módulo) pensé que el usó simultáneo con GDAL podía ser útil para estas tareas.

El código completo adaptado se incluye a continuación. Sólo considero el realce por contraste (sharpen) y usé una imagen tif con los tres canales RGB.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
from skimage.exposure import rescale_intensity
import numpy as np
import argparse
import cv2

def convolve(image, kernel):
    # grab the spatial dimensions of the image, along with
    # the spatial dimensions of the kernel
    (iH, iW) = image.shape[:2]
    (kH, kW) = kernel.shape[:2]
    
    # allocate memory for the output image, taking care to
    # "pad" the borders of the input image so the spatial
    # size (i.e., width and height) are not reduced
    pad = (kW - 1) / 2
    image = cv2.copyMakeBorder(image, pad, pad, pad, pad,
                            cv2.BORDER_REPLICATE)
    
    output = np.zeros((iH, iW), dtype="float32")
    
    # loop over the input image, "sliding" the kernel across
    # each (x, y)-coordinate from left-to-right and top to
    # bottom
    
    for y in np.arange(pad, iH + pad):
        for x in np.arange(pad, iW + pad):
            # extract the ROI of the image by extracting the
            # *center* region of the current (x, y)-coordinates
            # dimensions
            roi = image[y - pad:y + pad + 1, x - pad:x + pad + 1]
            
            # perform the actual convolution by taking the
            # element-wise multiplicate between the ROI and
            # the kernel, then summing the matrix
            k = (roi * kernel).sum()
            
            # store the convolved value in the output (x,y)-
            # coordinate of the output image
            
            output[y - pad, x - pad] = k
    
    # rescale the output image to be in the range [0, 255]
    output = rescale_intensity(output, in_range=(0, 255))
    output = (output * 255).astype("uint8")
    
    # return the output image
    return output

# construct average blurring kernels used to smooth an image
smallBlur = np.ones((7, 7), dtype="float") * (1.0 / (7 * 7))
largeBlur = np.ones((21, 21), dtype="float") * (1.0 / (21 * 21))

# construct a sharpening filter
sharpen = np.array((
    [0, -1, 0],
    [-1, 5, -1],
    [0, -1, 0]), dtype="int")

# construct the Laplacian kernel used to detect edge-like
# regions of an image
laplacian = np.array((
    [0, 1, 0],
    [1, -4, 1],
    [0, 1, 0]), dtype="int")

# construct the Sobel x-axis kernel
sobelX = np.array((
    [-1, 0, 1],
    [-2, 0, 2],
    [-1, 0, 1]), dtype="int")

# construct the Sobel y-axis kernel
sobelY = np.array((
    [-1, -2, -1],
    [0, 0, 0],
    [1, 2, 1]), dtype="int")

# construct the kernel bank, a list of kernels we're going
# to apply using both our custom `convole` function and
# OpenCV's `filter2D` function

kernelBank = (
    #("small_blur", smallBlur),
    #("large_blur", largeBlur),
    ("sharpen", sharpen),
    #("laplacian", laplacian),
    #("sobel_x", sobelX),
    #("sobel_y", sobelY)
    )

# load the input image and convert it to grayscale
image = cv2.imread("/home/zeito/Desktop/convolutions-opencv/utah_lake.tif")
#image = cv2.imread("/home/zeito/Desktop/convolutions-opencv/palma.tif")

gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# loop over the kernels
for (kernelName, kernel) in kernelBank:
    # apply the kernel to the grayscale image using both
    # our custom `convole` function and OpenCV's `filter2D`
    # function
    print("[INFO] applying {} kernel".format(kernelName))
    convoleOutput = convolve(gray, kernel)
    opencvOutput = cv2.filter2D(gray, -1, kernel) #ddepth=-1, the output image will have the same depth as the source.
    opencvOutput2 = cv2.filter2D(image, -1, kernel) #ddepth=-1, the output image will have the same depth as the source.
    
    # show the output images
    #cv2.imshow("original1", gray)
    cv2.imshow("original2", image)
    #cv2.imshow("{} - convole".format(kernelName), convoleOutput)
    #cv2.imshow("{} - opencv1".format(kernelName), opencvOutput)
    cv2.imshow("{} - opencv2".format(kernelName), opencvOutput2)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

La aplicación del código anterior a la referida imagen produce una considerable nitidez de la misma. No obstante, al grabarla con el método 'imwrite' se pierde la georreferenciación (aunque ésto se puede remediar fácilmente con gdalinfo y gdal_translate en Python).


Otra imagen (esta vez con una resolución submétrica) donde probé el filtro sharpen fue en la de un cultivo de palma aceitera donde interesaba individualizar las plantas para contarlas y estimar la densidad del cultivo.


Como puede observarse, el incremento de la nitidez es apreciable en la imagen inferior y las plantas de palma son claramente identificables visualmente del bosque aledaño. Ahora sólo resta como aplicar los algoritmos para la segregación e identificación de objetos previo al proceso de contaje.

No hay comentarios: