View Full Version : YUV color difference
StainlessS
21st April 2014, 03:41
Hi guys,
Anybody have any objections to the below code to measure
difference between two frames (YUV, in this case YUY2).
if(vi.IsYUY2()) {
unsigned int Pixels = (vi.width * vi.height);
__int64 accY=0,accC=0;
unsigned long sumY=0,sumC=0;
int y1d,y2d,ud,vd;
for(y=vi.height ; --y>=0; ) {
for (x=rowsize ; (x-=4)>=0 ; ) { // x scans right to left
if((y1d = srcp[x ] - srcp2[x ]) < 0) y1d = -y1d;
if((ud = srcp[x+1] - srcp2[x+1]) < 0) ud = -ud;
if((y2d = srcp[x+2] - srcp2[x+2]) < 0) y2d = -y2d;
if((vd = srcp[x+3] - srcp2[x+3]) < 0) vd = -vd;
sumY += y1d + y2d;
sumC += ud + vd;
}
if(sumY & 0x80000000) {accY += sumY; sumY=0;} // avoid possiblilty of overflow
if(sumC & 0x80000000) {accC += sumC; sumC=0;} // avoid possiblilty of overflow
srcp += pitch;
srcp2 += pitch2;
}
accY += sumY;
accC += sumC;
result = ((double)accY / Pixels);
double resultC = ((double)accC / Pixels); // Pixels * 2 / 2 == Pixels
result += (255.0 - result) / 255.0 * resultC;
}
Above code weights chroma differences dependant upon luma difference. (final result in range 0.0 -> 255.0)
$FF0000, $000000 = 255.0
$7F7F7F, $000000 = 190.749023
$007F7F, $000000 = 127.0
$00FFFF, $000000 = 255.0
$00FF00, $0000FF = 255.0
$0000FF, $000000 = 127.5
EDIT: concerning above:
Perhaps I should point out that in 1st post I used numbers where U & V are based off 0 rather than 128 (a sort of shorthand
to make the numbers easier), as uses absolute difference between like channels, makes no difference if numbers based off 0 or 128.
I often assume that others are on the same page as me :(
NOTE, YUY2 has two luma samples per chroma sample, so Pixels should be Pixels / 2 for chroma, BUT,
we have both U and V samples and so Pixels = Pixels / 2 * 2 == Pixels, so above does work OK.
StainlessS
21st April 2014, 20:05
Here full working plugin source. (EDIT: As supplied, defaults to Avisynth v2.6 plugin)
/*
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
// Uncomment below line for Avisynth v2.58 plugin. Avisynth.h must be correct for Avisynth version plugin.
// #define AVISYNTH_PLUGIN_25
#include <windows.h>
#include "avisynth.h"
AVSValue GetVar(IScriptEnvironment* env, const char* name) {
try {return env->GetVar(name);} catch (IScriptEnvironment::NotFound) {} return AVSValue();}
AVSValue __cdecl ColorDifference(AVSValue args, void* user_data, IScriptEnvironment* env) {
char * myName="ColorDifference: ";
PClip child,child2;
int n,n2;
bool gotcur=false;
if(args[2].IsInt()) {n = args[2].AsInt(); } // Frame n
else {
AVSValue cn = GetVar(env,"current_frame");
if (!cn.IsInt()) env->ThrowError("%s'current_frame' only available in runtime scripts",myName);
n = cn.AsInt(); // current_frame
gotcur=true;
}
if(args[3].IsInt()) {n2 = args[3].AsInt(); } // Frame n2
else {
if(gotcur) {
n2=n;
} else {
AVSValue cn = GetVar(env,"current_frame");
if (!cn.IsInt()) env->ThrowError("%s'current_frame' only available in runtime scripts",myName);
n2 = cn.AsInt(); // current_frame
}
}
if(!args[0].IsClip() || !args[1].IsClip())
env->ThrowError("%sMust have two clips",myName);
// If clip 2 frame is prior clip 1 frame then swap over clips (get earlier frame 1st)
if(n2 < n) {
child = args[1].AsClip(); // Clip = clip2
child2 = args[0].AsClip(); // Clip2= clip
} else {
child = args[0].AsClip(); // Clip
child2 = args[1].AsClip(); // Clip2
}
const VideoInfo &vi = child->GetVideoInfo();
const VideoInfo &vi2 = child2->GetVideoInfo();
if(!vi.IsSameColorspace(vi2))
env->ThrowError("%sClips Dissimilar ColorSpace",myName);
if(vi.width != vi2.width || vi.height != vi2.height)
env->ThrowError("%sClips Dissimilar Dimensions",myName);
if(vi.width==0 || vi.num_frames==0 || vi2.width==0 || vi2.num_frames==0)
env->ThrowError("%sBoth clips must have video",myName);
if(n2 < n) { // Now that we've checked clips, swap frames to match swapped clips.
int swp;
swp=n;n=n2;n2=swp;
}
n = (n<0) ? 0 : (n>= vi.num_frames) ?vi.num_frames -1:n; // Range limit frame n
n2 = (n2<0) ? 0 : (n2>=vi2.num_frames)?vi2.num_frames-1:n2; // Range limit frame n2
const unsigned int Pixels = (vi.width * vi.height);
PVideoFrame src = child->GetFrame(n,env);
PVideoFrame src2 = child2->GetFrame(n2,env);
const int pitch = src->GetPitch(PLANAR_Y); // PLANAR_Y no effect on non-Planar (equates to 0)
const int pitch2 = src2->GetPitch(PLANAR_Y); // PLANAR_Y no effect on non-Planar (equates to 0)
const BYTE *srcp = src->GetReadPtr(PLANAR_Y);
const BYTE *srcp2 = src2->GetReadPtr(PLANAR_Y);
int rowsize=src->GetRowSize(PLANAR_Y);
int x, y;
__int64 acc=0;
unsigned long sum=0;
double result;
if(vi.IsYUY2()) {
__int64 accC=0;
unsigned long sumC=0;
int y1d,y2d,ud,vd;
for(y=vi.height ; --y>=0; ) {
for (x=rowsize ; (x-=4)>=0 ; ) { // x scans right to left
if((y1d = srcp[x ] - srcp2[x ]) < 0) y1d = -y1d;
if((ud = srcp[x+1] - srcp2[x+1]) < 0) ud = -ud;
if((y2d = srcp[x+2] - srcp2[x+2]) < 0) y2d = -y2d;
if((vd = srcp[x+3] - srcp2[x+3]) < 0) vd = -vd;
sum += y1d + y2d;
sumC += ud + vd;
}
if(sum & 0x80000000) {acc += sum; sum=0;} // avoid possiblilty of overflow (BIG FRAME)
if(sumC & 0x80000000) {accC += sumC; sumC=0;}
srcp += pitch;
srcp2 += pitch2;
}
acc += sum;
result = ((double)acc / Pixels);
accC += sumC;
const double resultC = ((double)accC / Pixels); // Pixels / 2 * 2 == Pixels
result += (1.0 - result/255.0) * resultC;
} else if(vi.IsPlanar()) {
int tmp;
for(y=vi.height ; --y>=0 ; ) {
for (x=rowsize ; --x>=0 ; ) { // x scans right to left
if((tmp = srcp[x] - srcp2[x]) < 0) tmp = -tmp;
sum += tmp;
}
if(sum & 0x80000000) {acc += sum; sum=0;} // avoid possiblilty of overflow
srcp += pitch;
srcp2 += pitch2;
}
acc += sum;
result = ((double)acc / Pixels); // Y result
int rowsizeUV=src->GetRowSize(PLANAR_U);
if(rowsizeUV != 0) { // Not Y8
acc=0;
sum=0;
const int huv = src->GetHeight(PLANAR_U);
const int PixelsC = huv * rowsizeUV;
const int pitchUV = src->GetPitch(PLANAR_U);
const int pitchUV2 = src2->GetPitch(PLANAR_U);
srcp = src->GetReadPtr(PLANAR_U);
srcp2 = src2->GetReadPtr(PLANAR_U);
const BYTE *srcpV = src->GetReadPtr(PLANAR_V);
const BYTE *srcpV2 = src2->GetReadPtr(PLANAR_V);
for(y=huv ; --y>=0 ; ) {
for (x=rowsizeUV ; --x>=0 ; ) { // x scans right to left
if((tmp = srcp[x] - srcp2[x]) < 0) tmp = -tmp;
sum += tmp;
if((tmp = srcpV[x] - srcpV2[x]) < 0) tmp = -tmp;
sum += tmp;
}
if(sum & 0x80000000) {acc += sum; sum=0;} // avoid possiblilty of overflow (BIG FRAME)
srcp += pitchUV;
srcpV += pitchUV;
srcp2 += pitchUV2;
srcpV2 += pitchUV2;
}
acc += sum;
const double resultC = ((double)acc / (PixelsC*2)); // PixelsC * 2, ie U+V
result += (1.0 - result/255.0) * resultC;
}
} else if(vi.IsRGB24()) {
int rd,gd,bd;
for(y=vi.height ; --y>=0 ; ) {
for (x=rowsize ; (x-=3)>=0 ; ) { // x scans right to left
if((rd = srcp[x ] - srcp2[x]) < 0) rd = -rd;
if((gd = srcp[x+1] - srcp2[x+1]) < 0) gd = -gd;
if((bd = srcp[x+2] - srcp2[x+2]) < 0) bd = -bd;
sum += rd + gd + bd;
}
if(sum & 0x80000000) {acc += sum; sum=0;} // avoid possiblilty of overflow
srcp += pitch; // bottom to top
srcp2 += pitch2;
}
acc += sum;
result = ((double)acc / (Pixels*3));
} else if(vi.IsRGB32()) {
int rd,gd,bd;
for(y=vi.height ; --y>=0 ; ) {
for (x=rowsize ; (x-=4)>=0 ; ) { // x scans right to left
if((rd = srcp[x ] - srcp2[x]) < 0) rd = -rd;
if((gd = srcp[x+1] - srcp2[x+1]) < 0) gd = -gd;
if((bd = srcp[x+2] - srcp2[x+2]) < 0) bd = -bd;
sum += rd + gd + bd;
}
if(sum & 0x80000000) {acc += sum; sum=0;} // avoid possiblilty of overflow
srcp += pitch; // bottom to top
srcp2 += pitch2;
}
acc += sum;
result = ((double)acc / (Pixels*3));
} else {
env->ThrowError("%sPlanar, YUY2, RGB24 and RGB32 Only",myName);
}
return result; // Implicit conversion to AVSValue type Float
}
#ifdef AVISYNTH_PLUGIN_25
extern "C" __declspec(dllexport) const char* __stdcall AvisynthPluginInit2(IScriptEnvironment* env) {
#else
/* New 2.6 requirement!!! */
// Declare and initialise server pointers static storage.
const AVS_Linkage *AVS_linkage = 0;
/* New 2.6 requirement!!! */
// DLL entry point called from LoadPlugin() to setup a user plugin.
extern "C" __declspec(dllexport) const char* __stdcall
AvisynthPluginInit3(IScriptEnvironment* env, const AVS_Linkage* const vectors) {
/* New 2.6 requirment!!! */
// Save the server pointers.
AVS_linkage = vectors;
#endif
// ------
env->AddFunction("ColorDifference","cc[n]i[n2]i",ColorDifference, 0);
return "`ColorDifference' ColorDifference plugin";
}
Function ColorDifference(clip c, clip c2,int "n"=current_frame,int "n2"=current_frame)
Returns identical results to RGBDifference() for RGB24/32
EDIT: Return identical results for Y8 and GreyScale non Y8 Planar.
EDIT: Precompiled v2.58 and v2.6 plugins with source here (71KB): http://www.mediafire.com/download/n6l85a7rsjiaota/ColorDifference_25%2626_dll_v0.0_20140421.zip
rkuo
21st April 2014, 20:40
What's the point of using chroma at all?
StainlessS
21st April 2014, 20:48
You might want to tell if two frames are the same, or not, ignoring chroma would not help.
Return identical results for Y8 and GreyScale non Y8 Planar.
If you are referring to above line, not all planar is greyscale.
EDIT: Above meaning where Greyscale planar luma plane is same as Y8., ie zero chroma weighting for Y8 or greyscale non Y8.
rkuo
22nd April 2014, 09:00
You might want to tell if two frames are the same, or not, ignoring chroma would not help.
If you are referring to above line, not all planar is greyscale.
EDIT: Above meaning where Greyscale planar luma plane is same as Y8., ie zero chroma weighting for Y8 or greyscale non Y8.
Interesting. In what circumstances would differencing the chroma planes help?
StainlessS
22nd April 2014, 17:37
In the case where you have two frames with the same luma level but one eg blue and the other red, or whatever, they would clearly be different frames.
Perhaps I should point out that in 1st post I used numbers where U & V are based off 0 rather than 128 (a sort of shorthand
to make the numbers easier), as uses absolute difference between like channels, makes no difference if numbers based off 0 or 128.
I often assume that others are on the same page as me :(
EDIT: I shall be adding the plugin to RT_Stats, but probably under the name RT_FrameDifference, perhaps more appropriate.
EDIT: And a test script, using $80 based chroma
C1=$808080 C2=$808080 # 0.0
#C1=$008080 C2=$808080 # 128.0
#C1=$FF8080 C2=$808080 # 127.0
#C1=$408080 C2=$808080 # 64.0
#C1=$208080 C2=$808080 # 96.0
#C1=$804040 C2=$808080 # 64.0
#C1=$802040 C2=$808080 # 80.0
#C1=$804080 C2=$808080 # 32.0
CS="YUY2"
A=Blankclip(width=320,height=240,color_yuv=C1,length=1,pixel_type=CS)
B=Blankclip(width=320,height=240,color_yuv=C2,length=1,pixel_type=CS)
q=ColorDifference(A,B,0,0)
A=A.Subtitle("$"+RightStr("000000"+Hex(C1),6),size=30,align=5)
B=B.Subtitle("$"+RightStr("000000"+Hex(C2),6),size=30,align=5)
StackHorizontal(A,B)
Subtitle(String(q),size=30)
ConvertToRGB
vBulletin® v3.8.11, Copyright ©2000-2026, vBulletin Solutions Inc.