added luminance based grayscale conversion

This commit is contained in:
Corey Hanson
2023-08-24 00:07:01 -04:00
parent 2de40a517e
commit e91f331652
2 changed files with 33 additions and 3 deletions
+2
View File
@@ -99,6 +99,7 @@ A node that that will analyze the differences between a modified QR and create a
* `module_size` - The size in pixels of each QR module (the individual blocks that make up a qr code) in the provided images.
* `grayscale_method` - How to convert the RGB color channels to a single grayscale value.
* `mean` - A simple average of the RGB values.
* `luminance` - Converts colors to grayscale using a [more advanced formula](https://en.wikipedia.org/wiki/Grayscale#Colorimetric_(perceptual_luminance-preserving)_conversion_to_grayscale) to better match percieved brightness.
* `aggregate_method` - How to aggregate the many pixels that make up a QR module into a single value.
* `mean` - Within each module a simple average is taken of all constituent pixels.
* `evaluate` - Which part of the QR to examine.
@@ -107,6 +108,7 @@ A node that that will analyze the differences between a modified QR and create a
* `finder_pattern` - Ignores the modules while examining only the 3 finder patterns at the corners of the QR.
* `error_difficulty` - It is possible that while using the same reader a QR code could be easily scannable on one monitor, but not on another. The way the colors are displayed can have a major effect on unsability. By increasing this threshold, you are simulating inability to distinguish midrange pixels for both the `PERCENT_ERROR` and `QR_ERROR_MASK` outputs. It ranges from 0 to 1 where 0 only automatically fails perfect grays while 1 accepts only exactly matched modules that are perfectly black or white.
* `inverted_pattern` - If set to `True` this assumes that the QR code has inverted colors (a black background with white modules.)
* `gamma` - If using the `grayscale` method based on `luminance`, this will influence the formula by appending the gamma expansion and compression formulas before and after the color conversion. A gamma of 2.2 will use the [formula specific to srgb](qr_nodes.py#0.04045), while other gamma values will simply use the gamma amount as an exponent.
#### Outputs
+31 -3
View File
@@ -273,11 +273,12 @@ class QRErrorMasker:
"source_qr": ("IMAGE",),
"modified_qr": ("IMAGE",),
"module_size": ("INT", {"default": 16, "min": 1, "max": 64, "step": 1}),
"grayscale_method": (["mean",], {"default": "mean"}),
"grayscale_method": (["mean", "luminance"], {"default": "luminance"}),
"aggregate_method": (["mean",], {"default": "mean"}),
"evaluate": (["full_qr", "module_pattern", "finder_pattern"], {"default": "module_pattern"}),
"error_difficulty": ("FLOAT", {"default": 0, "min": 0, "max": 1, "step": .01}),
"inverted_pattern": ("BOOLEAN", {"default": False}),
"gamma": ("FLOAT", {"default": 2.2, "min": .1, "max": 2.8, "step": .1}),
},
}
@@ -319,6 +320,28 @@ class QRErrorMasker:
def _squeeze_by_mean(self, tensor):
return torch.mean(tensor, dim=-1)
def _gamma_expansion(self, tensor, gamma):
if gamma == 1:
return tensor
if gamma == 2.2:
return torch.where(tensor <= 0.04045, tensor / 12.92, ((tensor + 0.055) / 1.055) ** 2.4)
return tensor ** gamma
def _gamma_compression(self, tensor, gamma):
if gamma == 1:
return tensor
if gamma == 2.2:
return torch.where(tensor <= .0031308, tensor * 12.92, 1.055 * tensor ** (1/2.4) - 0.055)
return tensor ** (1/gamma)
def _grayscale_by_luminance(self, tensor, gamma):
weights = torch.tensor([0.2125, 0.7154, 0.0721], dtype=torch.float32)
tensor = self._gamma_expansion(tensor, gamma)
tensor = tensor @ weights
if gamma != 1:
tensor = tensor ** gamma
return self._gamma_compression(tensor, gamma)
def _squeeze_to_modules(self, tensor, method):
tensor = self._reshape_tensor_to_modules(tensor)
if method == "mean":
@@ -332,7 +355,8 @@ class QRErrorMasker:
module_size,
grayscale_method,
aggregate_method,
inverted_pattern
inverted_pattern,
gamma
):
if source_qr.shape != modified_qr.shape:
raise ValueError("Source and modified QR must have the same dimensions.")
@@ -348,6 +372,8 @@ class QRErrorMasker:
)
if grayscale_method == "mean":
modified_qr = self._squeeze_by_mean(modified_qr)
elif grayscale_method == "luminance":
modified_qr = self._grayscale_by_luminance(modified_qr, gamma)
else:
raise ValueError("Currently only mean is supported for rgb to grayscale conversion.")
source_qr = self._squeeze_to_modules(source_qr, "mean")
@@ -423,13 +449,15 @@ class QRErrorMasker:
evaluate,
error_difficulty,
inverted_pattern,
gamma,
):
source_qr, modified_qr = self._reduce_to_modules(source_qr,
modified_qr,
module_size,
grayscale_method,
aggregate_method,
inverted_pattern
inverted_pattern,
gamma
)
mask = self._create_qr_mask(source_qr.shape[0], evaluate)
error_mask, percent_error = self._compare_modules(source_qr,