Add median filter
This commit is contained in:
parent
4d60cfc88a
commit
49de39883b
|
@ -1,12 +1,23 @@
|
||||||
import cv2
|
import cv2
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import Utilities
|
import Utilities
|
||||||
|
import math
|
||||||
|
|
||||||
|
MAX_LUM_VALUES = 256
|
||||||
|
|
||||||
# Task 1
|
# Task 1
|
||||||
# function to stretch an image
|
# function to stretch an image
|
||||||
def stretchHistogram(img):
|
def stretchHistogram(img):
|
||||||
result = img.copy()
|
result = img.copy()
|
||||||
|
|
||||||
|
grayscale = getLuminance(img)
|
||||||
|
hist = calculateHistogram(grayscale, MAX_LUM_VALUES)
|
||||||
|
minPos, maxPos = findMinMaxPos(hist)
|
||||||
|
|
||||||
|
for x in range(0, img.shape[0]):
|
||||||
|
for y in range(0, img.shape[1]):
|
||||||
|
result[x][y] = (grayscale[x][y] - minPos) / (maxPos - minPos) * 255.0
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@ -14,6 +25,25 @@ def stretchHistogram(img):
|
||||||
# function to equalize an image
|
# function to equalize an image
|
||||||
def equalizeHistogram(img):
|
def equalizeHistogram(img):
|
||||||
result = img.copy()
|
result = img.copy()
|
||||||
|
|
||||||
|
grayscale = getLuminance(img)
|
||||||
|
hist = calculateHistogram(grayscale, MAX_LUM_VALUES)
|
||||||
|
minPos, maxPos = findMinMaxPos(hist)
|
||||||
|
|
||||||
|
# Precompute integral of histogram from left to right.
|
||||||
|
sum = 0
|
||||||
|
integral = []
|
||||||
|
for i in range(0, hist.shape[0]):
|
||||||
|
sum += hist[i]
|
||||||
|
integral.append(sum)
|
||||||
|
|
||||||
|
for x in range(0, img.shape[0]):
|
||||||
|
for y in range(0, img.shape[1]):
|
||||||
|
# Compute histogram value of pixel.
|
||||||
|
bin = int(img[x][y][0] / 255.0 * (MAX_LUM_VALUES - 1))
|
||||||
|
# Equalize pixel.
|
||||||
|
result[x][y] = (255.0 - 1.0) / (img.shape[0] * img.shape[1]) * integral[bin]
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@ -21,6 +51,18 @@ def equalizeHistogram(img):
|
||||||
# function to apply a look-up table onto an image
|
# function to apply a look-up table onto an image
|
||||||
def applyLUT(img, LUT):
|
def applyLUT(img, LUT):
|
||||||
result = img.copy()
|
result = img.copy()
|
||||||
|
|
||||||
|
grayscale = getLuminance(img)
|
||||||
|
|
||||||
|
for x in range(0, img.shape[0]):
|
||||||
|
for y in range(0, img.shape[1]):
|
||||||
|
# Compute histogram value of pixel.
|
||||||
|
bin = int(grayscale[x][y][0] / 255.0 * (MAX_LUM_VALUES - 1))
|
||||||
|
|
||||||
|
result[x][y][0] = LUT[bin]
|
||||||
|
result[x][y][1] = LUT[bin]
|
||||||
|
result[x][y][2] = LUT[bin]
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@ -29,6 +71,17 @@ def applyLUT(img, LUT):
|
||||||
def findMinMaxPos(histogram):
|
def findMinMaxPos(histogram):
|
||||||
minPos = 0
|
minPos = 0
|
||||||
maxPos = 255
|
maxPos = 255
|
||||||
|
|
||||||
|
for x in range(0, histogram.shape[0]):
|
||||||
|
if histogram[x] > 0:
|
||||||
|
minPos = x
|
||||||
|
break
|
||||||
|
|
||||||
|
for x in range(histogram.shape[0] - 1, 0):
|
||||||
|
if histogram[x] > 0:
|
||||||
|
maxPos = x
|
||||||
|
break
|
||||||
|
|
||||||
return minPos, maxPos
|
return minPos, maxPos
|
||||||
|
|
||||||
|
|
||||||
|
@ -37,24 +90,117 @@ def findMinMaxPos(histogram):
|
||||||
def calculateHistogram(img, nrBins):
|
def calculateHistogram(img, nrBins):
|
||||||
# create histogram vector
|
# create histogram vector
|
||||||
histogram = np.zeros([nrBins], dtype=int)
|
histogram = np.zeros([nrBins], dtype=int)
|
||||||
|
|
||||||
|
for x in range(0, img.shape[0]):
|
||||||
|
for y in range(0, img.shape[1]):
|
||||||
|
bin = int(img[x][y][0] / 255.0 * (nrBins - 1))
|
||||||
|
histogram[bin] = histogram[bin] + 1
|
||||||
|
|
||||||
return histogram
|
return histogram
|
||||||
|
|
||||||
|
|
||||||
def apply_log(img):
|
def luminanceD65(rgb: np.ndarray) -> np.float64:
|
||||||
|
"""
|
||||||
|
Compute the luminance value of the specified linear RGB values
|
||||||
|
according to the D65 white point.
|
||||||
|
|
||||||
|
@param rgb(np.ndarray): sRGB image
|
||||||
|
|
||||||
|
@returns The luminance value
|
||||||
|
"""
|
||||||
|
return np.float64(rgb @ [0.2126, 0.7152, 0.0722])
|
||||||
|
|
||||||
|
|
||||||
|
def getLuminance(img):
|
||||||
result = img.copy()
|
result = img.copy()
|
||||||
|
|
||||||
|
for x in range(0, img.shape[0]):
|
||||||
|
for y in range(0, img.shape[1]):
|
||||||
|
lum = luminanceD65(img[x][y])
|
||||||
|
result[x][y][0] = lum
|
||||||
|
result[x][y][1] = lum
|
||||||
|
result[x][y][2] = lum
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def apply_log(img):
|
||||||
|
grayscale = getLuminance(img)
|
||||||
|
result = img.copy()
|
||||||
|
|
||||||
|
# Logarithmic scale factor.
|
||||||
|
LOG_SCALE_FACTOR = 2.0
|
||||||
|
|
||||||
|
for x in range(0, img.shape[0]):
|
||||||
|
for y in range(0, img.shape[1]):
|
||||||
|
|
||||||
|
# Compute logarithmically scaled D65 luminace
|
||||||
|
# and clamp result to be smaller 255.
|
||||||
|
lum = min(255,
|
||||||
|
math.log(grayscale[x][y][0]/255.0 + 1)
|
||||||
|
* 255.0 * LOG_SCALE_FACTOR)
|
||||||
|
|
||||||
|
result[x][y][0] = lum
|
||||||
|
result[x][y][1] = lum
|
||||||
|
result[x][y][2] = lum
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def apply_exp(img):
|
def apply_exp(img):
|
||||||
|
grayscale = getLuminance(img)
|
||||||
result = img.copy()
|
result = img.copy()
|
||||||
|
|
||||||
|
# Logarithmic scale factor.
|
||||||
|
EXP_SCALE_FACTOR = 0.5
|
||||||
|
|
||||||
|
for x in range(0, img.shape[0]):
|
||||||
|
for y in range(0, img.shape[1]):
|
||||||
|
|
||||||
|
# Compute logarithmically scaled D65 luminace
|
||||||
|
# and clamp result to be smaller 255.
|
||||||
|
lum = min(255,
|
||||||
|
(math.exp(grayscale[x][y][0]/255.0) - 1)
|
||||||
|
* 255.0 * EXP_SCALE_FACTOR)
|
||||||
|
|
||||||
|
result[x][y][0] = lum
|
||||||
|
result[x][y][1] = lum
|
||||||
|
result[x][y][2] = lum
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def apply_inverse(img):
|
def apply_inverse(img):
|
||||||
|
grayscale = getLuminance(img)
|
||||||
result = img.copy()
|
result = img.copy()
|
||||||
|
|
||||||
|
for x in range(0, img.shape[0]):
|
||||||
|
for y in range(0, img.shape[1]):
|
||||||
|
|
||||||
|
# Compute logarithmically scaled D65 luminace
|
||||||
|
# and clamp result to be smaller 255.
|
||||||
|
lum = 255.0 - grayscale[x][y][0]
|
||||||
|
|
||||||
|
result[x][y][0] = lum
|
||||||
|
result[x][y][1] = lum
|
||||||
|
result[x][y][2] = lum
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def apply_threshold(img, threshold):
|
def apply_threshold(img, threshold):
|
||||||
|
grayscale = getLuminance(img)
|
||||||
result = img.copy()
|
result = img.copy()
|
||||||
|
|
||||||
|
for x in range(0, img.shape[0]):
|
||||||
|
for y in range(0, img.shape[1]):
|
||||||
|
|
||||||
|
lum = 0
|
||||||
|
if grayscale[x][y][0] >= threshold:
|
||||||
|
lum = 255
|
||||||
|
|
||||||
|
result[x][y][0] = lum
|
||||||
|
result[x][y][1] = lum
|
||||||
|
result[x][y][2] = lum
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
|
@ -2,12 +2,43 @@ import numpy as np
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
import datetime as dt
|
import datetime as dt
|
||||||
import cv2
|
import cv2
|
||||||
|
from numpy._core.numeric import ndarray
|
||||||
import Utilities
|
import Utilities
|
||||||
|
import math
|
||||||
|
|
||||||
|
def _getChannelMedian(values: list[list[int]], channel: int) -> int:
|
||||||
|
channelValues = list(map(lambda rgb: rgb[channel], values))
|
||||||
|
channelValues.sort()
|
||||||
|
return channelValues[int(len(channelValues)/2)]
|
||||||
|
|
||||||
# apply median filter
|
# apply median filter
|
||||||
def applyMedianFilter(img, kSize):
|
def applyMedianFilter(img: ndarray, kSize: int):
|
||||||
filtered_img = img.copy()
|
filtered_img = img.copy()
|
||||||
|
|
||||||
|
for x in range(0, img.shape[0]):
|
||||||
|
for y in range(0, img.shape[1]):
|
||||||
|
|
||||||
|
values = []
|
||||||
|
|
||||||
|
for u in range(int(-kSize/2), int(kSize/2)+1):
|
||||||
|
s = x + u
|
||||||
|
if s < 0 or s >= img.shape[0]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for v in range(int(-kSize/2), int(kSize/2)+1):
|
||||||
|
t = y + v
|
||||||
|
if t < 0 or t >= img.shape[1]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
values.append(img[s][t])
|
||||||
|
|
||||||
|
if len(values) > 0:
|
||||||
|
filtered_img[x][y] = [
|
||||||
|
_getChannelMedian(values, 0),
|
||||||
|
_getChannelMedian(values, 1),
|
||||||
|
_getChannelMedian(values, 2)
|
||||||
|
]
|
||||||
|
|
||||||
return filtered_img
|
return filtered_img
|
||||||
|
|
||||||
|
|
||||||
|
@ -25,6 +56,30 @@ def gaussian(x, y, sigmaX, sigmaY, meanX, meanY):
|
||||||
# create a gaussian kernel of arbitrary size
|
# create a gaussian kernel of arbitrary size
|
||||||
def createGaussianKernel(kSize, sigma=None):
|
def createGaussianKernel(kSize, sigma=None):
|
||||||
kernel = np.zeros((kSize, kSize))
|
kernel = np.zeros((kSize, kSize))
|
||||||
|
|
||||||
|
stdev = math.floor(kSize/2)
|
||||||
|
stdev2 = stdev * stdev
|
||||||
|
factor = 1.0/(stdev2*2*math.pi)
|
||||||
|
|
||||||
|
sum = 0.0
|
||||||
|
|
||||||
|
for x in range(kSize):
|
||||||
|
xm = x - kSize/2
|
||||||
|
xsum = xm * xm / stdev2
|
||||||
|
|
||||||
|
for y in range(kSize):
|
||||||
|
ym = y - kSize/2
|
||||||
|
ysum = ym * ym / stdev2
|
||||||
|
|
||||||
|
kernel[x][y] = math.exp((xsum + ysum) * -0.5) * factor
|
||||||
|
sum += kernel[x][y]
|
||||||
|
|
||||||
|
# Normalize gaussian kernel in order not minimize power loss:
|
||||||
|
# https://stackoverflow.com/a/61355383
|
||||||
|
for x in range(kSize):
|
||||||
|
for y in range(kSize):
|
||||||
|
kernel[x][y] /= sum
|
||||||
|
|
||||||
return kernel
|
return kernel
|
||||||
|
|
||||||
|
|
||||||
|
@ -42,6 +97,28 @@ def createSobelYKernel():
|
||||||
|
|
||||||
def applyKernelInSpatialDomain(img, kernel):
|
def applyKernelInSpatialDomain(img, kernel):
|
||||||
filtered_img = img.copy()
|
filtered_img = img.copy()
|
||||||
|
|
||||||
|
width, height = kernel.shape
|
||||||
|
|
||||||
|
for x in range(0, img.shape[0]):
|
||||||
|
for y in range(0, img.shape[1]):
|
||||||
|
|
||||||
|
filtered_img[x][y] = np.zeros([3])
|
||||||
|
|
||||||
|
for u in range(0, width):
|
||||||
|
s = x + u - int(width/2)
|
||||||
|
|
||||||
|
for v in range(0, height):
|
||||||
|
t = y + v - int(height/2)
|
||||||
|
|
||||||
|
color = np.zeros([3])
|
||||||
|
if t >= 0 and t < img.shape[1] and s >= 0 and s < img.shape[0]:
|
||||||
|
color = img[s][t]
|
||||||
|
|
||||||
|
filtered_img[x][y][0] += kernel[u][v] * color[0]
|
||||||
|
filtered_img[x][y][1] += kernel[u][v] * color[1]
|
||||||
|
filtered_img[x][y][2] += kernel[u][v] * color[2]
|
||||||
|
|
||||||
return filtered_img
|
return filtered_img
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -117,32 +117,32 @@ class MainController:
|
||||||
def apply_gaussian_filter(self, kernel_size):
|
def apply_gaussian_filter(self, kernel_size):
|
||||||
kernel = IF.createGaussianKernel(kernel_size)
|
kernel = IF.createGaussianKernel(kernel_size)
|
||||||
img = IF.applyKernelInSpatialDomain(self._model.input_image, kernel)
|
img = IF.applyKernelInSpatialDomain(self._model.input_image, kernel)
|
||||||
self._model.image = Utilities.ensure_three_channel_grayscale_image(img)
|
self._model.image = img
|
||||||
|
|
||||||
def apply_moving_avg_filter(self, kernel_size):
|
def apply_moving_avg_filter(self, kernel_size):
|
||||||
kernel = IF.createMovingAverageKernel(kernel_size)
|
kernel = IF.createMovingAverageKernel(kernel_size)
|
||||||
img = IF.applyKernelInSpatialDomain(self._model.input_image, kernel)
|
img = IF.applyKernelInSpatialDomain(self._model.input_image, kernel)
|
||||||
self._model.image = Utilities.ensure_three_channel_grayscale_image(img)
|
self._model.image = img
|
||||||
|
|
||||||
def apply_moving_avg_filter_integral(self, kernel_size):
|
def apply_moving_avg_filter_integral(self, kernel_size):
|
||||||
img = IF.applyMovingAverageFilterWithIntegralImage(
|
img = IF.applyMovingAverageFilterWithIntegralImage(
|
||||||
self._model.input_image, kernel_size
|
self._model.input_image, kernel_size
|
||||||
)
|
)
|
||||||
self._model.image = Utilities.ensure_three_channel_grayscale_image(img)
|
self._model.image = img
|
||||||
|
|
||||||
def apply_median_filter(self, kernel_size):
|
def apply_median_filter(self, kernel_size):
|
||||||
img = IF.applyMedianFilter(self._model.input_image, kernel_size)
|
img = IF.applyMedianFilter(self._model.input_image, kernel_size)
|
||||||
self._model.image = Utilities.ensure_three_channel_grayscale_image(img)
|
self._model.image = img
|
||||||
|
|
||||||
def apply_filter_sobelX(self):
|
def apply_filter_sobelX(self):
|
||||||
kernel = IF.createSobelXKernel()
|
kernel = IF.createSobelXKernel()
|
||||||
img = IF.applyKernelInSpatialDomain(self._model.input_image, kernel)
|
img = IF.applyKernelInSpatialDomain(self._model.input_image, kernel)
|
||||||
self._model.image = Utilities.ensure_three_channel_grayscale_image(img)
|
self._model.image = img
|
||||||
|
|
||||||
def apply_filter_sobelY(self):
|
def apply_filter_sobelY(self):
|
||||||
kernel = IF.createSobelYKernel()
|
kernel = IF.createSobelYKernel()
|
||||||
img = IF.applyKernelInSpatialDomain(self._model.input_image, kernel)
|
img = IF.applyKernelInSpatialDomain(self._model.input_image, kernel)
|
||||||
self._model.image = Utilities.ensure_three_channel_grayscale_image(img)
|
self._model.image = img
|
||||||
|
|
||||||
def run_runtime_evaluation(self):
|
def run_runtime_evaluation(self):
|
||||||
IF.run_runtime_evaluation(self._model.input_image)
|
IF.run_runtime_evaluation(self._model.input_image)
|
||||||
|
|
|
@ -57,5 +57,4 @@ class ImageModel(QObject):
|
||||||
|
|
||||||
def load_rgb_image(self, path):
|
def load_rgb_image(self, path):
|
||||||
image = cv2.imread(path, 1)
|
image = cv2.imread(path, 1)
|
||||||
# image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
|
|
||||||
self.image = image
|
self.image = image
|
||||||
|
|
Loading…
Reference in New Issue