I am converting a 16-bit image to an 8-bit for an ML dataset.
The conversion script considers the min and max values of each band instead of a linear scale from 0-65535 to 0-255. However, the 8-bit image is appearing low contrast.
from osgeo import gdal
def scale_to_8bit(data, min_val, max_val):
scaled_data = ((data - min_val) / (max_val - min_val)) * 255.0
return scaled_data.astype('uint8')
def convert_16bit_to_8bit(input_path, output_path):
# Open the 16-bit GeoTIFF image
dataset = gdal.Open(input_path)
if dataset is None:
raise ValueError("Error: Unable to open the input GeoTIFF file.")
# Read image data and find the minimum and maximum values per channel
band_count = dataset.RasterCount
min_values = [float('inf')] * band_count
max_values = [float('-inf')] * band_count
for band_num in range(1, band_count + 1):
band = dataset.GetRasterBand(band_num)
data = band.ReadAsArray()
min_values[band_num - 1] = data.min()
max_values[band_num - 1] = data.max()
# Create a new 8-bit GeoTIFF
driver = gdal.GetDriverByName("GTiff")
output_dataset = driver.Create(
output_path,
dataset.RasterXSize,
dataset.RasterYSize,
band_count,
gdal.GDT_Byte
)
# Apply scaling and write data to the new GeoTIFF
for band_num in range(1, band_count + 1):
band = dataset.GetRasterBand(band_num)
data = band.ReadAsArray()
scaled_data = scale_to_8bit(data, min_values[band_num - 1], max_values[band_num - 1])
output_band = output_dataset.GetRasterBand(band_num)
output_band.WriteArray(scaled_data)
# Set geotransform and projection information
output_dataset.SetGeoTransform(dataset.GetGeoTransform())
output_dataset.SetProjection(dataset.GetProjection())
# Save changes and close datasets
output_dataset.FlushCache()
output_dataset = None
dataset = None
#if __name__ == "__main__":
input_tiff_path = r'C:\Data\Raster_Images\16bit.tif'
output_tiff_path = r'C:\Data\Raster_Images\8bit.png'
convert_16bit_to_8bit(input_tiff_path, output_tiff_path)
I tried to improve further using the CLAHE algorithm, which marginally increased clarity. However, when I export the map as an Image or as an atlas in QGIS, the output image is 8-bit but does not lose any quality.
import cv2
import numpy as np
import os
def apply_LAB_CLAHE(src_image,out_folder):
img = cv2.imread(src_image, 1)
# converting to LAB color space
lab= cv2.cvtColor(img, cv2.COLOR_BGR2LAB)
l_channel, a, b = cv2.split(lab)
# Applying CLAHE to L-channel
# feel free to try different values for the limit and grid size:
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
cl = clahe.apply(l_channel)
# merge the CLAHE enhanced L-channel with the a and b channel
limg = cv2.merge((cl,a,b))
# Converting image from LAB Color model to BGR color spcae
enhanced_img = cv2.cvtColor(limg, cv2.COLOR_LAB2BGR)
out_file=src_image.replace('PNG_8bit','PNG_8bit_CLAHE')
cv2.imwrite(out_file,enhanced_img)
# Stacking the original image with the enhanced image
#result = np.hstack((img, enhanced_img))
#plt.imshow( result)
Edit#1 Files placed at this link RGB value range for 16-bit image
- R 45-314
- G 167-480
- B 169-362