Edge detection

What are the edges of an image?

The edge of image is one of the most basic features of image. An edge (or edge) is a collection of pixels with a jump or “roof” change in the gray of the surrounding pixels. Edge is the place where the local intensity changes most obviously, and it mainly exists between target and target, target and background, region and region, so it is an important feature of image segmentation dependence. In essence, the image edge is the response of the discontinuity of the local characteristics of the image (gray scale mutation, color mutation, texture structure mutation, etc.), which marks the end of one region and the beginning of another region.

The detected edge is not the same as the real edge of the actual target. Since the image data is two-dimensional, while the actual object is three-dimensional, the projection from three-dimensional to two-dimensional will inevitably cause the loss of information. In addition, affected by factors such as illumination imbalance and noise in the imaging process, the place with edges may not be detected, and the detected edges may not represent the actual edges.

The edge of an image has two attributes: direction and amplitude. The pixel changes gently along the edge direction, while the pixel changes sharply perpendicular to the edge direction. Such changes in the edges can be detected by differential operators, usually using first or second derivatives to detect the edges. The first derivative considers that the maximum value corresponds to the edge position, while the second derivative corresponds to the edge position by crossing zero.

Since the image is a discrete signal, we can use the two adjacent pixel difference values to represent the derivative of the pixel gray value function, as follows:

This x-direction derivative corresponds to the transpose matrix of the filter of [1, -1] and the transpose matrix of [1, -1] for the y-direction.

However, the calculation result of this derivative method is closest to the gradient in the middle of two pixels, and there is no pixel between two pixels. Therefore, we should select the difference between the pixels before and after the pixels for calculation, as follows:

At this point, the corresponding X-axis filter is [0.5, 0, -0.5], and the corresponding Y-axis filter is its transpose matrix. In this way, we can also get a filter to calculate the gradient of 45 degrees.


0.5 0 0 0 0 0 0 0 0.5 \begin{matrix} 0.5&0&0 \\ 0&0&0 \\ 0&0&-0.5 \end{matrix}

0 0 0.5 0 0 0 0.5 0 0 \begin{matrix} 0&0&0.5 \\ 0&0&0 \\ -0.5&0&0 \end{matrix}

API

Image convolution

public static void filter2D(Mat src, Mat dst, int ddepth, Mat kernel, Point anchor, double delta, int borderType) 
Copy the code
  • Parameter one: SRC, enter the image.
  • Parameter two: DST, the output image, with the same size and number of channels as the input image.
  • Parameter 3: ddepth: data type (depth) of the output image. The value range varies according to the data type of the input image. When a value of -1 is assigned, the data type of the output image is automatically selected.
  • Parameter 4: kernel, convolution kernel, CV_32FC1 type matrix.
  • Parameter 5: Anchor, kernel reference point (anchor point), the default value (-1,-1) indicates that kernel reference point is located in the center of kernel. The reference point is the point in the convolution kernel that overlapped with the pixel point to be processed, and its position must be inside the convolution kernel.
  • Parameter 6: Delta, offset value, add offset value to the calculation result.
  • Parameter 7: borderType, pixel extrapolation select logo. The default parameter is BORDER_DEFAULT, which means that there is no inverse padding of boundary values.

The absolute value

The edge of the image pixel values into may be from high to low pixel values, it is possible that the low pixel values into a high pixel values, the is stated as some numerical value is calculated by convolution need pixels suddenly change from low to high, the negative value pixels from high to low, the two are the edges of the image, so in order to in the image at the same time show the two edge information, You have to take the absolute value of the result.

public static void convertScaleAbs(Mat src, Mat dst, double alpha, double beta) 
Copy the code
  • Parameter one: SRC, enter the image.
  • Parameter two: DST, enter the matrix after calculating the absolute value.
  • Parameter 3: scaling factor. The default parameter is to take the absolute value without scaling.
  • Parameter 4: The offset value added to the original data. The default parameter indicates that the offset value is not added.

This function takes the absolute value of all the data in the matrix. The first two parameters of the function are the input matrix and the output matrix respectively. The two parameters can be the same variable. The third and fourth parameters of the function are the scaling of the absolute value and the offset from the original data. The calculation principle of the function is as follows:


dst ( I ) = saturate_cast<uchar> ( src ( I ) alpha + beta ) \texttt{dst} (I)= \texttt{saturate\_cast<uchar>} (| \texttt{src} (I)* \texttt{alpha} + \texttt{beta} |)

operation

/** ** ** author: yidong * 2020/5/2 */
class EdgeDetectionActivity : AppCompatActivity() {

    private lateinit var mBinding: ActivityEdgeDetectionBinding
    private lateinit var mRgb: Mat

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_edge_detection)
        val bgr = Utils.loadResource(this, R.drawable.lena)
        mRgb = Mat()
        Imgproc.cvtColor(bgr, mRgb, Imgproc.COLOR_BGR2RGB)
        showMat(mBinding.ivLena, mRgb)
    }

    private fun showMat(view: ImageView, source: Mat) {
        val bitmap = Bitmap.createBitmap(source.width(), source.height(), Bitmap.Config.ARGB_8888)
        bitmap.density = 360
        Utils.matToBitmap(source, bitmap)
        view.setImageBitmap(bitmap)
    }

    override fun onCreateOptionsMenu(menu: Menu?).: Boolean {
        menuInflater.inflate(R.menu.menu_edge_detection, menu)
        return true
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        when (item.itemId) {
            R.id.edge_detection_x -> {
                edgeDetectionX()
            }
            R.id.edge_detection_y -> {
                edgeDetectionY()
            }
            R.id.edge_detection_x_y -> {
                edgeDetectionXAndY()
            }
            R.id.edge_detection_xy -> {
                edgeDetectionXY()
            }
            R.id.edge_detection_yx -> {
                edgeDetectionYX()
            }
        }
        return true
    }

    private fun edgeDetectionX(a) {
        title = "X-axis edge detection"
        // X direction edge detection
        val kernelX = Mat(1.3, CvType.CV_16S)
        val arrayX = shortArrayOf(-1.0.1)
        kernelX.put(0.0, arrayX)
        val resultKernelX = Mat()
        Imgproc.filter2D(mRgb, resultKernelX, CvType.CV_16S, kernelX)
        Core.convertScaleAbs(resultKernelX, resultKernelX)
        showMat(mBinding.ivResult, resultKernelX)
        kernelX.release()
        resultKernelX.release()
    }

    private fun edgeDetectionY(a) {
        title = "Y-axis edge detection"
        // edge detection in Y direction
        val kernelY = Mat(3.1, CvType.CV_16S)
        val arrayY = shortArrayOf(-1.0.1)
        kernelY.put(0.0, arrayY)
        val resultKernelY = Mat()
        Imgproc.filter2D(mRgb, resultKernelY, CvType.CV_16S, kernelY)
        Core.convertScaleAbs(resultKernelY, resultKernelY)
        showMat(mBinding.ivResult, resultKernelY)
        kernelY.release()
        resultKernelY.release()
    }

    private fun edgeDetectionXAndY(a) {
        title = "X and Y direction edge detection"
        // X direction edge detection
        val kernelX = Mat(1.3, CvType.CV_16S)
        val arrayX = shortArrayOf(-1.0.1)
        kernelX.put(0.0, arrayX)
        val resultKernelX = Mat()
        Imgproc.filter2D(mRgb, resultKernelX, CvType.CV_16S, kernelX)
        Core.convertScaleAbs(resultKernelX, resultKernelX)
        // edge detection in Y direction
        val kernelY = Mat(3.1, CvType.CV_16S)
        val arrayY = shortArrayOf(-1.0.1)
        kernelY.put(0.0, arrayY)
        val resultKernelY = Mat()
        Imgproc.filter2D(mRgb, resultKernelY, CvType.CV_16S, kernelY)
        Core.convertScaleAbs(resultKernelY, resultKernelY)

        // merge the X and Y directions
        val resultXY = Mat()
        Core.add(resultKernelX, resultKernelY, resultXY)
        showMat(mBinding.ivResult, resultXY)

        kernelX.release()
        resultKernelX.release()
        kernelY.release()
        resultKernelY.release()
        resultXY.release()
    }

    private fun edgeDetectionXY(a) {
        title = "Edge detection from top left to bottom right."
        // Check the edge from top left to bottom right
        val kernelXY = Mat(2.2, CvType.CV_16S)
        val arrayXY = shortArrayOf(1.0.0, -1)
        kernelXY.put(0.0, arrayXY)
        val resultKernelXY = Mat()
        Imgproc.filter2D(mRgb, resultKernelXY, CvType.CV_16S, kernelXY)
        Core.convertScaleAbs(resultKernelXY, resultKernelXY)
        showMat(mBinding.ivResult, resultKernelXY)
        kernelXY.release()
        resultKernelXY.release()
    }

    private fun edgeDetectionYX(a) {
        title = "Edge detection from top right to bottom left."
        // Check the edge from top right to bottom left
        val kernelYX = Mat(2.2, CvType.CV_16S)
        val arrayYX = shortArrayOf(0, -1.1.0)
        kernelYX.put(0.0, arrayYX)
        val resultKernelYX = Mat()
        Imgproc.filter2D(mRgb, resultKernelYX, CvType.CV_16S, kernelYX)
        Core.convertScaleAbs(resultKernelYX, resultKernelYX)
        showMat(mBinding.ivResult, resultKernelYX)
        kernelYX.release()
        resultKernelYX.release()
    }
}
Copy the code

The effect

The source code

Github.com/onlyloveyd/…