Code
import cv2
import numpy as np
import os
from pathlib import Path
import matplotlib.pyplot as plt
Janne Mäyrä
February 27, 2023
The simplest approach for extracting information is a simple color extraction which can be used to detect land cover classes distinguished by color such as fields and waters. However, textural areas such as marshes can not be detected this way.
All these extractions are done with white-balance corrected images.
Text(0.5, 1.0, '213406_1984.tif')
For easier color filtering, images are transformed into HSV colorspace
Text(0.5, 1.0, '213408_1965.tif')
HSV-range for fields is between [10, 100, 100]
and [25, 255, 255]
hsv_field_bot = np.array([10,100,100])
hsv_field_top = np.array([25,255,255])
field_mask = cv2.inRange(hsv_ex, hsv_field_bot, hsv_field_top)
field_mask = field_mask.astype(np.int16)
fig, axs = plt.subplots(1,2, figsize=(10,6), dpi=150)
field_mask[field_mask<128] = 0
field_mask[field_mask>128] = 1
axs[0].imshow(field_mask, interpolation='none')
temp = ex_file.copy()
temp[field_mask == 0] = 255
axs[1].imshow(cv2.cvtColor(temp, cv2.COLOR_BGR2RGB))
plt.show()
Water range is between [60,10,10]
aand [130,255,255]
.
hsv_water_bot = np.array([60,10,10])
hsv_water_top = np.array([130,255,255])
water_mask = cv2.inRange(hsv_ex, hsv_water_bot, hsv_water_top)
water_mask = water_mask.astype(np.uint8)
water_mask = water_mask
fig, axs = plt.subplots(1,2, figsize=(10,6), dpi=200)
water_mask[water_mask<128] = 0
water_mask[water_mask>128] = 1
axs[0].imshow(water_mask, interpolation='none')
temp = ex_file.copy()
temp[water_mask == 0] = 255
axs[1].imshow(cv2.cvtColor(temp, cv2.COLOR_BGR2RGB), interpolation='none')
plt.show()
This approach, however, has some difficulties with ditches running through fields as their color is greenish rather than blue.
<matplotlib.image.AxesImage>
Also, overlapping colors are fairly difficult situations.
<matplotlib.image.AxesImage>
Distinguishing between water bodies and waterways can be done fairly easily with MORPH_OPEN
.
water_mask_open = cv2.morphologyEx(water_mask, cv2.MORPH_OPEN, cv2.getStructuringElement(cv2.MORPH_RECT, (15,15)))
fig, axs = plt.subplots(1, 3, dpi=200, figsize=(10,6))
axs[0].imshow(water_mask[500:1500, 2000:3000])
axs[0].set_title('Blue mask')
axs[1].imshow(water_mask_open[500:1500, 2000:3000])
axs[1].set_title('Water bodies')
axs[2].imshow((water_mask - water_mask_open)[500:1500, 2000:3000])
axs[2].set_title('Waterways')
Text(0.5, 1.0, 'Waterways')
After some post-processing the waterway-layer looks fairly good.
from skimage.morphology import skeletonize
fig, axs = plt.subplots(1,3, dpi=200, figsize=(10,4))
for a in axs:
a.set_xticks([])
a.set_yticks([])
waterways = (water_mask - water_mask_open)[500:1500, 2000:3000]
axs[0].imshow(waterways)
axs[0].set_title('Waterways')
opened = cv2.morphologyEx(waterways, cv2.MORPH_OPEN, cv2.getStructuringElement(cv2.MORPH_CROSS, (5,5)))
axs[1].imshow(opened)
axs[1].set_title('Opening with 5x5 kernel')
axs[2].imshow(skeletonize(opened))
axs[2].set_title('Skeletonized waterways')
Text(0.5, 1.0, 'Skeletonized waterways')
As red wraps around 180 in HSV-colorspace, two ranges are required for it.
hsv_red_bot1 = np.array([0,10,10])
hsv_red_top1 = np.array([5,255,255])
hsv_red_bot2 = np.array([175,10,10])
hsv_red_top2 = np.array([180,255,255])
red_mask1 = cv2.inRange(hsv_ex, hsv_red_bot1, hsv_red_top1)
red_mask1 = red_mask1.astype(np.int16)
red_mask2 = cv2.inRange(hsv_ex, hsv_red_bot2, hsv_red_top2)
red_mask2 = red_mask2.astype(np.int16)
fig, axs = plt.subplots(1,2, figsize=(10,6), dpi=150)
red_mask = red_mask1 | red_mask2
red_mask[red_mask<128] = 0
red_mask[red_mask>128] = 1
axs[0].imshow(red_mask, interpolation='none')
temp = ex_file.copy()
temp[red_mask == 0] = 255
axs[1].imshow(cv2.cvtColor(temp, cv2.COLOR_BGR2RGB))
plt.show()
Another approach is to invert the RGB-image, convert it to HSV and look for cyan.
inv_hsv = cv2.cvtColor(~ex_file, cv2.COLOR_BGR2HSV)
hsv_cyan_bot = np.array([85,70,50])
hsv_cyan_top = np.array([95,255,255])
cyan_mask = cv2.inRange(inv_hsv, hsv_cyan_bot, hsv_cyan_top)
cyan_mask = cyan_mask.astype(np.uint8)
fig, axs = plt.subplots(1,2, figsize=(10,6), dpi=150)
cyan_mask[cyan_mask<128] = 0
cyan_mask[cyan_mask>128] = 1
axs[0].imshow(cyan_mask, interpolation='none')
temp = ex_file.copy()
temp[cyan_mask == 0] = 255
axs[1].imshow(cv2.cvtColor(temp, cv2.COLOR_BGR2RGB))
plt.show()
Unfortunately, there are lots of other things than roads that are red in the images. We can get rid of some of them with simple morphological opening but not nearly all of them.