Skip to content

Commit 97f2226

Browse files
committed
Update
MSRCP: Add option chroma_protect to attenuate chroma adjustment. MSRCP: Intensity channel is calculated from average of R, G, B value now, instead of weighted average. Delete Specification.h. Update README.md
1 parent a43429f commit 97f2226

File tree

8 files changed

+81
-507
lines changed

8 files changed

+81
-507
lines changed

README.md

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ The Retinex theory and algorithm mainly aims at simulating the color constancy f
1414

1515
The light perceived by visual receptors can be separated into illuminance and reflectance. Retinex estimates the illuminance and derive the reflectance from the light, the filtered result of which is an image represents the reflectance characteristics of the scene, regardless of the illuminance.
1616

17-
Retinex is a very powerful filter in dynamic range compression, contrast enhancement, color constancy, etc.
17+
Retinex is a very powerful filter in dynamic range compression, contrast enhancement, color constancy, de-fog, etc.
1818

1919
## MSRCP
2020

@@ -28,10 +28,12 @@ As MSRCP preserves chromaticity, it is excellent for dynamic range compression a
2828

2929
This function accept 8-16bit integer Gray/YUV/RGB/YCoCg input. Sub-sampled format is not supported. If you want to process YUV420/YUV422 clip, convert it to YUV444 or RGB first.
3030

31+
For processing in YUV444 and RGB, the filtering results are different. MSR is applied to intesity channel, which is Y for YUV444 input and (R+G+B)/3 for RGB input. Since Y is a weighted average of R, G, B, processing in YUV444 may produce imbalanced chromaticity preservation. Also when chroma_protect is larger than 1 (default 1.2), the saturation of YUV444 processing result will be different from that of RGB processing result.
32+
3133
### Usage
3234

3335
```python
34-
retinex.MSRCP(clip input, float[] sigmaS=[25,80,250], float lower_thr=0, float upper_thr=0, bool fulls, bool fulld=fulls)
36+
retinex.MSRCP(clip input, float[] sigmaS=[25,80,250], float lower_thr=0, float upper_thr=0, bool fulls, bool fulld=fulls, float chroma_protect=1.2)
3537
```
3638

3739
- input:<br />
@@ -58,23 +60,35 @@ retinex.MSRCP(clip input, float[] sigmaS=[25,80,250], float lower_thr=0, float u
5860

5961
- fulld: (Default: fulls)<br />
6062
Determine the value range of output clip. True means full range/PC range, and False means limited range/TV range.<br />
61-
Set different value for fulls and fulld will result in range conversion.<br />
62-
When fulls and fulld are the same, it is safe to assign either True or False for any kinds of input clip except YUV. When fulls=False it will automatically determine the Floor and Ceil of input image, which may produce a stronger filter result under some circumstances.
63+
Set different value for fulls and fulld will result in range conversion.
64+
65+
- chroma_protect: (Default: 1.2)<br />
66+
The base of log function to attenuate chroma adjustment. It could avoid extreme chroma amplifying, while the saturation of the result is changed.<br />
67+
Available range is [1, +inf), 1 means no attenuation.<br />
68+
It is only available for YUV/YCoCg input.
6369

6470
### Example
6571

66-
TV range YUV420P8 input, filter in TV range YUV444P16
72+
TV range YUV420P8 input, filtered in TV range YUV444P16 with chroma protect, output TV range YUV444P16
6773

6874
```python
6975
v = core.fmtc.resample(v, csp=vs.YUV444P16)
70-
v = core.retinex.MSRCP(v)
76+
v = core.retinex.MSRCP(v, chroma_protect=1.2)
7177
```
7278

73-
JPEG image(PC range YUV420P8 with MPEG-1 chroma placement) input, filter in PC range RGB48
79+
JPEG image(PC range YUV420P8 with MPEG-1 chroma placement) input, filtered in PC range YUV444P16 without chroma protect, output PC range RGB48
7480

7581
```python
7682
i = core.lsmas.LWLibavSource(r'Image.jpg')
7783
i = core.fmtc.resample(i, csp=vs.YUV444P16, fulls=True, cplace="MPEG1")
84+
i = core.retinex.MSRCP(i, fulls=True, chroma_protect=1)
7885
i = core.fmtc.matrix(i, mat="601", fulls=True, csp=vs.RGB48)
86+
```
87+
88+
PNG image(PC range RGB24) input, filtered in PC range RGB48, output PC range RGB48
89+
90+
```python
91+
i = core.lsmas.LWLibavSource(r'Image.png')
92+
i = core.fmtc.bitdepth(i, bits=16)
7993
i = core.retinex.MSRCP(i)
8094
```

include/MSR.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ class MSRData
3737
std::vector<double> sigma;
3838
double lower_thr = 0;
3939
double upper_thr = 0;
40-
int fulls = 1;
41-
int fulld = fulls;
40+
bool fulls = true;
41+
bool fulld = fulls;
4242

4343
int process[3];
4444

@@ -60,9 +60,9 @@ class MSRData
6060
void fulls_select()
6161
{
6262
if (vi->format->colorFamily == cmGray || vi->format->colorFamily == cmYUV)
63-
fulls = 0;
63+
fulls = false;
6464
else if (vi->format->colorFamily == cmRGB || vi->format->colorFamily == cmYCoCg)
65-
fulls = 1;
65+
fulls = true;
6666
}
6767
};
6868

include/MSRCP.h

Lines changed: 44 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222

2323

2424
#include "Helper.h"
25-
#include "Specification.h"
2625
#include "MSR.h"
2726

2827

@@ -33,19 +32,13 @@ class MSRCPData
3332
: public MSRData
3433
{
3534
public:
36-
ColorMatrix ColorMatrix_ = ColorMatrix::Unspecified;
35+
double chroma_protect = 1.2;
3736

3837
public:
3938
MSRCPData(const VSAPI *_vsapi = nullptr)
4039
: MSRData(_vsapi) {}
4140

4241
~MSRCPData() {}
43-
44-
void ColorMatrix_select()
45-
{
46-
if (ColorMatrix_ == ColorMatrix::Unspecified)
47-
ColorMatrix_ = ColorMatrix_Default(vi->width, vi->height);
48-
}
4942
};
5043

5144

@@ -71,14 +64,14 @@ void Retinex_MSRCP(VSFrameRef * dst, const VSFrameRef * src, const VSAPI * vsapi
7164
int sNeutral = 128 << (bps - 8);
7265
T sCeil = (1 << bps) - 1;
7366
//T sCeilC = (1 << bps) - 1;
74-
T sRange = sCeil - sFloor;
67+
T sRange = d.fulls ? (1 << bps) - 1 : 219 << (bps - 8);
7568
T sRangeC = d.fulls ? (1 << bps) - 1 : 224 << (bps - 8);
7669
T dFloor = d.fulld ? 0 : 16 << (bps - 8);
7770
//T dFloorC = d.fulld ? 0 : 16 << (bps - 8);
7871
int dNeutral = 128 << (bps - 8);
7972
T dCeil = d.fulld ? (1 << bps) - 1 : 235 << (bps - 8);
8073
//T dCeilC = d.fulld ? (1 << bps) - 1 : 240 << (bps - 8);
81-
T dRange = dCeil - dFloor;
74+
T dRange = d.fulld ? (1 << bps) - 1 : 219 << (bps - 8);
8275
T dRangeC = d.fulld ? (1 << bps) - 1 : 224 << (bps - 8);
8376
FLType sFloorFL = static_cast<FLType>(sFloor);
8477
//FLType sFloorCFL = static_cast<FLType>(sFloorC);
@@ -140,13 +133,11 @@ void Retinex_MSRCP(VSFrameRef * dst, const VSFrameRef * src, const VSAPI * vsapi
140133
{
141134
sFloor = min;
142135
sCeil = max;
143-
sRange = sCeil - sFloor;
144136
sFloorFL = static_cast<FLType>(sFloor);
145137
//sCeilFL = static_cast<FLType>(sCeil);
146-
sRangeFL = static_cast<FLType>(sRange);
147138
}
148139

149-
gain = 1 / sRangeFL;
140+
gain = 1 / static_cast<FLType>(sCeil - sFloor);
150141
for (j = 0; j < height; j++)
151142
{
152143
i = stride * j;
@@ -157,25 +148,12 @@ void Retinex_MSRCP(VSFrameRef * dst, const VSFrameRef * src, const VSAPI * vsapi
157148

158149
Retinex_MSR(odata, idata, d, height, width, stride);
159150

160-
if (d.fulld)
161-
{
162-
offset = FLType(0.5);
163-
for (j = 0; j < height; j++)
164-
{
165-
i = stride * j;
166-
for (upper = i + width; i < upper; i++)
167-
Ydstp[i] = static_cast<T>(odata[i] * dRangeFL + offset);
168-
}
169-
}
170-
else
151+
offset = dFloorFL + FLType(0.5);
152+
for (j = 0; j < height; j++)
171153
{
172-
offset = dFloorFL + FLType(0.5);
173-
for (j = 0; j < height; j++)
174-
{
175-
i = stride * j;
176-
for (upper = i + width; i < upper; i++)
177-
Ydstp[i] = static_cast<T>(odata[i] * dRangeFL + offset);
178-
}
154+
i = stride * j;
155+
for (upper = i + width; i < upper; i++)
156+
Ydstp[i] = static_cast<T>(odata[i] * dRangeFL + offset);
179157
}
180158
}
181159
else if (fi->colorFamily == cmRGB)
@@ -190,17 +168,14 @@ void Retinex_MSRCP(VSFrameRef * dst, const VSFrameRef * src, const VSAPI * vsapi
190168
Bsrcp = reinterpret_cast<const T *>(vsapi->getReadPtr(src, 2));
191169
Bdstp = reinterpret_cast<T *>(vsapi->getWritePtr(dst, 2));
192170

193-
FLType Kr, Kg, Kb;
194-
ColorMatrix_Parameter(d.ColorMatrix_, Kr, Kg, Kb);
195-
196171
if (d.fulls)
197172
{
198-
gain = 1 / sRangeFL;
173+
gain = 1 / (sRangeFL * 3);
199174
for (j = 0; j < height; j++)
200175
{
201176
i = stride * j;
202177
for (upper = i + width; i < upper; i++)
203-
idata[i] = (Kr*Rsrcp[i] + Kg*Gsrcp[i] + Kb*Bsrcp[i]) * gain;
178+
idata[i] = (Rsrcp[i] + Gsrcp[i] + Bsrcp[i]) * gain;
204179
}
205180
}
206181
else
@@ -222,18 +197,17 @@ void Retinex_MSRCP(VSFrameRef * dst, const VSFrameRef * src, const VSAPI * vsapi
222197
{
223198
sFloor = min;
224199
sCeil = max;
225-
sRange = sCeil - sFloor;
226200
sFloorFL = static_cast<FLType>(sFloor);
227201
//sCeilFL = static_cast<FLType>(sCeil);
228-
sRangeFL = static_cast<FLType>(sRange);
229202
}
230203

231-
gain = 1 / sRangeFL;
204+
offset = sFloorFL * -3;
205+
gain = 1 / (static_cast<FLType>(sCeil - sFloor) * 3);
232206
for (j = 0; j < height; j++)
233207
{
234208
i = stride * j;
235209
for (upper = i + width; i < upper; i++)
236-
idata[i] = (Kr*Rsrcp[i] + Kg*Gsrcp[i] + Kb*Bsrcp[i] - sFloorFL) * gain;
210+
idata[i] = (Rsrcp[i] + Gsrcp[i] + Bsrcp[i] + offset) * gain;
237211
}
238212
}
239213

@@ -247,7 +221,8 @@ void Retinex_MSRCP(VSFrameRef * dst, const VSFrameRef * src, const VSAPI * vsapi
247221
i = stride * j;
248222
for (upper = i + width; i < upper; i++)
249223
{
250-
gain = Min(sRangeFL / Max(Rsrcp[i], Max(Gsrcp[i], Bsrcp[i])), idata[i] <= 0 ? 1 : odata[i] / idata[i]);
224+
gain = idata[i] <= 0 ? 1 : odata[i] / idata[i];
225+
gain = Min(sRangeFL / Max(Rsrcp[i], Max(Gsrcp[i], Bsrcp[i])), gain);
251226
Rdstp[i] = static_cast<T>(Rsrcp[i] * gain + offset);
252227
Gdstp[i] = static_cast<T>(Gsrcp[i] * gain + offset);
253228
Bdstp[i] = static_cast<T>(Bsrcp[i] * gain + offset);
@@ -263,7 +238,8 @@ void Retinex_MSRCP(VSFrameRef * dst, const VSFrameRef * src, const VSAPI * vsapi
263238
i = stride * j;
264239
for (upper = i + width; i < upper; i++)
265240
{
266-
gain = Min(sRangeFL / Max(Rsrcp[i], Max(Gsrcp[i], Bsrcp[i])), idata[i] <= 0 ? 1 : odata[i] / idata[i]) * scale;
241+
gain = idata[i] <= 0 ? 1 : odata[i] / idata[i];
242+
gain = Min(sRangeFL / Max(Rsrcp[i], Max(Gsrcp[i], Bsrcp[i])), gain) * scale;
267243
Rdstp[i] = static_cast<T>((Rsrcp[i] - sFloor) * gain + offset);
268244
Gdstp[i] = static_cast<T>((Gsrcp[i] - sFloor) * gain + offset);
269245
Bdstp[i] = static_cast<T>((Bsrcp[i] - sFloor) * gain + offset);
@@ -312,13 +288,11 @@ void Retinex_MSRCP(VSFrameRef * dst, const VSFrameRef * src, const VSAPI * vsapi
312288
{
313289
sFloor = min;
314290
sCeil = max;
315-
sRange = sCeil - sFloor;
316291
sFloorFL = static_cast<FLType>(sFloor);
317292
//sCeilFL = static_cast<FLType>(sCeil);
318-
sRangeFL = static_cast<FLType>(sRange);
319293
}
320294

321-
gain = 1 / sRangeFL;
295+
gain = 1 / static_cast<FLType>(sCeil - sFloor);
322296
for (j = 0; j < height; j++)
323297
{
324298
i = stride * j;
@@ -329,35 +303,34 @@ void Retinex_MSRCP(VSFrameRef * dst, const VSFrameRef * src, const VSAPI * vsapi
329303

330304
Retinex_MSR(odata, idata, d, height, width, stride);
331305

332-
if (dRangeCFL == sRangeCFL)
333-
{
334-
offset = dNeutralFL + FLType(0.5);
335-
for (j = 0; j < height; j++)
336-
{
337-
i = stride * j;
338-
for (upper = i + width; i < upper; i++)
339-
{
340-
gain = Min(sRangeC2FL / Max(Abs(Usrcp[i] - sNeutral), Abs(Vsrcp[i] - sNeutral)), idata[i] <= 0 ? 1 : odata[i] / idata[i]);
341-
Ydstp[i] = static_cast<T>(odata[i] * dRangeFL + dFloorFL + FLType(0.5));
342-
Udstp[i] = static_cast<T>((Usrcp[i] - sNeutral) * gain + offset);
343-
Vdstp[i] = static_cast<T>((Vsrcp[i] - sNeutral) * gain + offset);
344-
}
345-
}
346-
}
306+
FLType chroma_protect_mul1 = static_cast<FLType>(d.chroma_protect - 1);
307+
FLType chroma_protect_mul2 = static_cast<FLType>(1 / log(d.chroma_protect));
308+
309+
int Uval, Vval;
310+
scale = dRangeCFL / sRangeCFL;
311+
if (d.fulld)
312+
offset = dNeutralFL + FLType(0.499999);
347313
else
348-
{
349-
scale = dRangeCFL / sRangeCFL;
350314
offset = dNeutralFL + FLType(0.5);
351-
for (j = 0; j < height; j++)
315+
316+
for (j = 0; j < height; j++)
317+
{
318+
i = stride * j;
319+
for (upper = i + width; i < upper; i++)
352320
{
353-
i = stride * j;
354-
for (upper = i + width; i < upper; i++)
355-
{
356-
gain = Min(sRangeC2FL / Max(Abs(Usrcp[i] - sNeutral), Abs(Vsrcp[i] - sNeutral)), idata[i] <= 0 ? 1 : odata[i] / idata[i]) * scale;
357-
Ydstp[i] = static_cast<T>(odata[i] * dRangeFL + dFloorFL + FLType(0.5));
358-
Udstp[i] = static_cast<T>((Usrcp[i] - sNeutral) * gain + offset);
359-
Vdstp[i] = static_cast<T>((Vsrcp[i] - sNeutral) * gain + offset);
360-
}
321+
if (d.chroma_protect > 1)
322+
gain = idata[i] <= 0 ? 1 : log(odata[i] / idata[i] * chroma_protect_mul1 + 1) * chroma_protect_mul2;
323+
else
324+
gain = idata[i] <= 0 ? 1 : odata[i] / idata[i];
325+
Uval = Usrcp[i] - sNeutral;
326+
Vval = Vsrcp[i] - sNeutral;
327+
if (dRangeCFL == sRangeCFL)
328+
gain = Min(sRangeC2FL / Max(Abs(Uval), Abs(Vval)), gain);
329+
else
330+
gain = Min(sRangeC2FL / Max(Abs(Uval), Abs(Vval)), gain) * scale;
331+
Ydstp[i] = static_cast<T>(odata[i] * dRangeFL + dFloorFL + FLType(0.5));
332+
Udstp[i] = static_cast<T>(Uval * gain + offset);
333+
Vdstp[i] = static_cast<T>(Vval * gain + offset);
361334
}
362335
}
363336
}

0 commit comments

Comments
 (0)