In the previous article, we explained the expansion and corrosion functions in image processing. This article will provide edge gradient computing functions. If you are interested, you can learn more.
Preface
In the previous article, we explained the expansion and corrosion functions in image processing. This article will provide edge gradient computing functions.
Image Edge
How is the image edge represented in mathematics?
On the edge of the image, the adjacent pixel value should be significantly changed. In mathematics, derivative represents a way to change the speed. A big change in the gradient value indicates a significant change in the image content.
Let's use a more vivid image to explain it. Suppose we have a one-dimensional image. The "jump" of the gray value in indicates the existence of the edge:
Using the first-order differential derivation, we can see the existence of the edge "jumping" more clearly (this shows a high peak value ):
From this we can conclude that the edge can be found by locating the phase element with the gradient value greater than the neighboring area.
Approximate Gradient
For example, when the kernel is 3.
First, calculate the approximate derivative of x:
Then, calculate the approximate derivative of the y direction:
Then calculate the gradient:
You can also write it as follows:
Function implementation
The Code is as follows:
Var Sobel = function (_ src, _ xorder, _ yorder, _ size, _ borderType, _ dst ){
(_ Src & (_ xorder ^ _ yorder) | error (arguments. callee, IS_UNDEFINED_OR_NULL/* {line }*/);
If (_ src. type & _ src. type = "CV_GRAY "){
Var kernel1,
Kernel2,
Height = _ src. row,
Width = _ src. col,
Dst = _ dst | new Mat (height, width, CV_16I, 1 ),
DstData = dst. data
Size = _ size | 3;
Switch (size ){
Case 1:
Size = 3;
Case 3:
If (_ xorder ){
Kernel = [-1, 0, 1,
-2, 0, 2,
-1, 0, 1
];
} Else if (_ yorder ){
Kernel = [-1,-2,-1,
, 0, 0,
, 2, 1
];
}
Break;
Case 5:
If (_ xorder ){
Kernel = [-1,-2, 0, 2, 1,
-4,-8, 0, 8, 4,
-6,-12, 0, 12, 6,
-4,-8, 0, 8, 4,
-1,-2, 0, 2, 1
];
} Else if (_ yorder ){
Kernel = [-1,-4,-6,-4,-1,
-2,-8,-12,-8,-2,
, 0, 0, 0, 0,
, 8, 12, 8, 2,
, 4, 6, 4, 1
];
}
Break;
Default:
Error (arguments. callee, UNSPPORT_SIZE/* {line }*/);
}
GRAY216IC1Filter (_ src, size, height, width, kernel, dstData, _ borderType );
} Else {
Error (arguments. callee, UNSPPORT_DATA_TYPE/* {line }*/);
}
Return dst;
};
Here, only Sobel operators with the kernel size of 3 and 5 are provided. The main reason is that kernel computing with the kernel size of 7 or above is slow.
Output a 16-bit signed integer matrix of a single channel.
The Code is as follows:
Function GRAY216IC1Filter (_ src, size, height, width, kernel, dstData, _ borderType ){
Var start = size> 1;
Var withBorderMat = copyMakeBorder (_ src, start, start, 0, 0, _ borderType );
Var mData = withBorderMat. data,
MWidth = withBorderMat. col;
Var I, j, y, x, c;
Var newValue, nowX, offsetY, offsetI;
For (I = height; I --;){
OffsetI = I * width;
For (j = width; j --;){
NewValue = 0;
For (y = size; y --;){
OffsetY = (y + I) * mWidth;
For (x = size; x --;){
NowX = x + j;
NewValue + = (mData [offsetY + nowX] * kernel [y * size + x]);
}
}
DstData [j + offsetI] = newValue;
}
}
}
Then, the kernel and matrix are handed over to the filter for processing.
The reason for separating this filter is that it can be used by other similar computing edge functions, such as Laplacian and Scharr operators.
Convert to an unsigned 8-digit integer
Because the Sobel operator calculates a 16-bit signed integer and cannot be displayed as an image, we need a function to convert it into an unsigned 8-bit integer matrix.
The convertScaleAbs function is to take the absolute value of each element and put it in the Int8Array. because the number of values greater than 255 is automatically converted to 255, and the number smaller than 0 is automatically converted to 0, so we don't need to make a function to take charge of this job.
The Code is as follows:
Function convertScaleAbs (_ src, _ dst ){
_ Src | error (arguments. callee, IS_UNDEFINED_OR_NULL/* {line }*/);
Var height = _ src. row,
Width = _ src. col,
Channel = _ src. channel,
SData = _ src. data;
If (! _ Dst ){
If (channel = 1)
Dst = new Mat (height, width, CV_GRAY );
Else if (channel = 4)
Dst = new Mat (height, width, CV_RGBA );
Else
Dst = new Mat (height, width, CV_8I, channel );
} Else {
Dst = _ dst;
}
Var dData = dst. data;
Var I, j, c;
For (I = height; I --;){
For (j = width * channel; j --;){
DData [I * width * channel + j] = Math. abs (sData [I * width * channel + j]);
}
}
Return dst;
}
Merge values proportionally
We also need a function to overlay the gradient values in the x direction and the gradient values in the y direction.
The Code is as follows:
Var addWeighted = function (_ src1, _ alpha, _ src2, _ beta, _ gamma, _ dst ){
(_ Src1 & _ src2) | error (arguments. callee, IS_UNDEFINED_OR_NULL/* {line }*/);
Var height = _ src1.row,
Width = _ src1.col,
Alpha = _ alpha | 0,
Beta = _ beta | 0,
Channel = _ src1.channel,
Gamma = _ gamma | 0;
If (height! ==__ Src2.row | width! ==__ Src2.col | channel! ==__ Src2.channel ){
Error (arguments. callee, "Src2 must be the same size and channel number as src1! "/* {Line }*/);
Return null;
}
If (! _ Dst ){
If (_ src1.type. match (/CV \ _ \ d + /))
Dst = new Mat (height, width, _ src1.depth (), channel );
Else
Dst = new Mat (height, width, _ src1.depth ());
} Else {
Dst = _ dst;
}
Var dData = dst. data,
S1Data = _ src1.data,
S2Data = _ src2.data;
Var I;
For (I = height * width * channel; I --;)
DData [I] = _ alpha * s1Data [I] + _ beta * s2Data [I] + gamma;
Return dst;
};
This function is very simple. In fact, it only adds the corresponding elements of the two matrices in a fixed proportion.