Improving Opencv 3 : Mask operations on matrices

Refs: https://docs.opencv.org/4.3.0/d7/d37/tutorial_mat_mask_operations.html

目录

思想

The Basic Method

C++

修改像素

最后填充图像边缘

Python

Java

The filter2D function

C++

Python

Java

完整示例

C++

Python

Java


Mask operations on matrices are quite simple. The idea is that we recalculate each pixel's value in an image according to a mask matrix (also known as kernel). This mask holds values that will adjust how much influence neighboring pixels (and the current pixel) have on the new pixel value. From a mathematical point of view we make a weighted average, with our specified values.

思想

Let's consider the issue of an image contrast enhancement method. Basically we want to apply for every pixel of the image the following formula:

I(i,j) = 5*I(i,j) - [ I(i-1,j) + I(i+1,j) + I(i,j-1) + I(i,j+1)]

\iff I(i,j)*M, \text{where } M = \bordermatrix{ _i\backslash ^j & -1 & 0 & +1 \cr -1 & 0 & -1 & 0 \cr 0 & -1 & 5 & -1 \cr +1 & 0 & -1 & 0 \cr }

The Basic Method

C++

修改像素

void Sharpen(const Mat& myImage,Mat& Result)
{
    CV_Assert(myImage.depth() == CV_8U);  // accept only uchar images
    const int nChannels = myImage.channels();
    Result.create(myImage.size(),myImage.type());
    for(int j = 1 ; j < myImage.rows-1; ++j)
    {
        const uchar* previous = myImage.ptr<uchar>(j - 1);
        const uchar* current  = myImage.ptr<uchar>(j    );
        const uchar* next     = myImage.ptr<uchar>(j + 1);
        uchar* output = Result.ptr<uchar>(j);
        for(int i= nChannels;i < nChannels*(myImage.cols-1); ++i)
        {
            *output++ = saturate_cast<uchar>(5*current[i]
                         -current[i-nChannels] - current[i+nChannels] - previous[i] - next[i]);
        }
    }
    Result.row(0).setTo(Scalar(0));
    Result.row(Result.rows-1).setTo(Scalar(0));
    Result.col(0).setTo(Scalar(0));
    Result.col(Result.cols-1).setTo(Scalar(0));
}

 

*output++ = saturate_cast<uchar>(5*current[i]-current[i-nChannels] - current[i+nChannels] - previous[i] - next[i]);

以彩色三通道的图片为例 BGR

i为B值则 i+/- 3 才同一行上相邻的值, 上下两行的相邻值则是previous和next

数据在内存以BRG的顺序连续存储

*output++ 等同于 *(output++)

0, 1, 2,,,,m表示对各像素点上BRG值

最后填充图像边缘

On the borders of the image the upper notation results inexistent pixel locations (like minus one - minus one). In these points our formula is undefined. A simple solution is to not apply the kernel in these points and, for example, set the pixels on the borders to zeros:

    Result.row(0).setTo(Scalar(0));
    Result.row(Result.rows-1).setTo(Scalar(0));
    Result.col(0).setTo(Scalar(0));
    Result.col(Result.cols-1).setTo(Scalar(0));

Python

At first we make sure that the input images data in unsigned 8 bit format.

my_image = cv.cvtColor(my_image, cv.CV_8U)

We create an output image with the same size and the same type as our input. As you can see in the storing section, depending on the number of channels we may have one or more subcolumns.

height, width, n_channels = my_image.shape
result = np.zeros(my_image.shape, my_image.dtype)

We need to access multiple rows and columns which can be done by adding or subtracting 1 to the current center (i,j). Then we apply the sum and put the new value in the Result matrix.

    for j in range(1, height - 1):
        for i in range(1, width - 1):
            if is_grayscale(my_image):
                sum_value = 5 * my_image[j, i] - my_image[j + 1, i] - my_image[j - 1, i] \
                            - my_image[j, i + 1] - my_image[j, i - 1]
                result[j, i] = saturated(sum_value)
            else:
                for k in range(0, n_channels):
                    sum_value = 5 * my_image[j, i, k] - my_image[j + 1, i, k]  \
                                - my_image[j - 1, i, k] - my_image[j, i + 1, k]\
                                - my_image[j, i - 1, k]
                    result[j, i, k] = saturated(sum_value)

这个比c++的要好理解的多

为什么没有像c++一样填充边缘

Java

We need to access multiple rows and columns which can be done by adding or subtracting 1 to the current center (i,j). Then we apply the sum and put the new value in the Result matrix.

        for (int j = 1; j < myImage.rows() - 1; ++j) {
            for (int i = 1; i < myImage.cols() - 1; ++i) {
                double sum[] = new double[nChannels];
                for (int k = 0; k < nChannels; ++k) {
                    double top = -myImage.get(j - 1, i)[k];
                    double bottom = -myImage.get(j + 1, i)[k];
                    double center = (5 * myImage.get(j, i)[k]);
                    double left = -myImage.get(j, i - 1)[k];
                    double right = -myImage.get(j, i + 1)[k];
                    sum[k] = saturate(top + bottom + center + left + right);
                }
                Result.put(j, i, sum);
            }
        }

On the borders of the image the upper notation results in inexistent pixel locations (like (-1,-1)). In these points our formula is undefined. A simple solution is to not apply the kernel in these points and, for example, set the pixels on the borders to zeros:

        Result.row(0).setTo(new Scalar(0));
        Result.row(Result.rows() - 1).setTo(new Scalar(0));
        Result.col(0).setTo(new Scalar(0));
        Result.col(Result.cols() - 1).setTo(new Scalar(0));

与C++相类似要填充边缘

 

The filter2D function

C++

Applying such filters are so common in image processing that in OpenCV there is a function that will take care of applying the mask (also called a kernel in some places). For this you first need to define an object that holds the mask:

Mat kernel = (Mat_<char>(3,3) <<  0, -1,  0,
                                   -1,  5, -1,
                                    0, -1,  0);
 filter2D( src, dst1, src.depth(), kernel );

The function even has a fifth optional argument to specify the center of the kernel, a sixth for adding an optional value to the filtered pixels before storing them in K and a seventh one for determining what to do in the regions where the operation is undefined (borders).

This function is shorter, less verbose and, because there are some optimizations, it is usually faster than the hand-coded method

Python

to define an object that holds the mask:

 kernel = np.array([[0, -1, 0],
                       [-1, 5, -1],
                       [0, -1, 0]], np.float32)  # kernel should be floating point type

Then call the filter2D() function specifying the input, the output image and the kernel to use:

dst1 = cv.filter2D(src, -1, kernel)
    # ddepth = -1, means destination image has depth same as input image

Java

Mat kern = new Mat(3, 3, CvType.CV_8S);
        int row = 0, col = 0;
        kern.put(row, col, 0, -1, 0, -1, 5, -1, 0, -1, 0);

其它与C++相类似

完整示例

C++

#include <iostream>
#include <opencv.hpp>
#include <ctime>

using namespace cv;
using namespace std;

void Sharpen(const Mat& myImage, Mat& Result);

int main(int argc, char* argv[])
{

	Mat image_grass = imread("Grass.jpg", IMREAD_COLOR);
	//Mat image_grass = imread("Grass.jpg", IMREAD_GRAYSCALE);

	if (!image_grass.data)
	{
		printf("No data\n");
		return -1;
	}

	pyrDown(image_grass, image_grass, Size(image_grass.cols / 2, image_grass.rows / 2));
	Mat src(image_grass);
	Mat dst0, dst1;

	
	namedWindow("Input", WINDOW_AUTOSIZE);
	namedWindow("Output", WINDOW_AUTOSIZE);
	namedWindow("MyOutput", WINDOW_AUTOSIZE);
	imshow("Input", src);

	double t = (double)getTickCount();
	Sharpen(src, dst0);
	t = ((double)getTickCount() - t) / getTickFrequency();
	cout << "Hand written function time passed in seconds: " << t << endl;

	imshow("Output", dst0);
	//waitKey();

	Mat kernel = (Mat_<char>(3, 3) << 0, -1, 0,
		-1, 5, -1,
		0, -1, 0);

	t = (double)getTickCount();
	filter2D(src, dst1, src.depth(), kernel);
	t = ((double)getTickCount() - t) / getTickFrequency();
	cout << "Built-in filter2D time passed in seconds:     " << t << endl;

	imshow("MyOutput", dst1);
	waitKey();

	return EXIT_SUCCESS;
}


void Sharpen(const Mat& myImage, Mat& Result)
{
	CV_Assert(myImage.depth() == CV_8U);  // accept only uchar images
	const int nChannels = myImage.channels();
	Result.create(myImage.size(), myImage.type());
	for (int j = 1; j < myImage.rows - 1; ++j)
	{
		const uchar* previous = myImage.ptr<uchar>(j - 1);
		const uchar* current = myImage.ptr<uchar>(j);
		const uchar* next = myImage.ptr<uchar>(j + 1);
		uchar* output = Result.ptr<uchar>(j);
		for (int i = nChannels; i < nChannels*(myImage.cols - 1); ++i)
		{
			*output++ = saturate_cast<uchar>(5 * current[i]
				- current[i - nChannels] - current[i + nChannels] - previous[i] - next[i]);
		}
	}
	Result.row(0).setTo(Scalar(0));
	Result.row(Result.rows - 1).setTo(Scalar(0));
	Result.col(0).setTo(Scalar(0));
	Result.col(Result.cols - 1).setTo(Scalar(0));
}

Python

from __future__ import print_function
import sys
import time

import numpy as np
import cv2 as cv

def is_grayscale(my_image):
    return len(my_image.shape) < 3

def saturated(sum_value):
    if sum_value > 255:
        sum_value = 255
    if sum_value < 0:
        sum_value = 0

    return sum_value


def sharpen1(my_image):
    if is_grayscale(my_image):
        height, width = my_image.shape
    else:
        my_image = cv.cvtColor(my_image, cv.CV_8U)
        height, width, n_channels = my_image.shape

    result = np.zeros(my_image.shape, my_image.dtype)

    for j in range(1, height - 1):
        for i in range(1, width - 1):
            if is_grayscale(my_image):
                sum_value = 5 * my_image[j, i] - my_image[j + 1, i] - my_image[j - 1, i] \
                            - my_image[j, i + 1] - my_image[j, i - 1]
                result[j, i] = saturated(sum_value)
            else:
                for k in range(0, n_channels):
                    sum_value = 5 * my_image[j, i, k] - my_image[j + 1, i, k] \
                                - my_image[j - 1, i, k] - my_image[j, i + 1, k] \
                                - my_image[j, i - 1, k]
                    result[j, i, k] = saturated(sum_value)

    return result

def sharpen(my_image):
    if is_grayscale(my_image):
        height, width = my_image.shape
    else:
        my_image = cv.cvtColor(my_image, cv.CV_8U)
        height, width, n_channels = my_image.shape

    result = np.zeros(my_image.shape, my_image.dtype)

    for i in range(1, height - 1):
        for j in range(1, width - 1):
            if is_grayscale(my_image):
                sum_value = 5 * my_image[i, j] - my_image [i-1, j] \
                            -my_image[i+1, j] - my_image[i, j +1] -my_image[i, j-1]
                result[i, j] = saturated(sum_value)
            else:
                for k in range(0, n_channels):
                    sum_value = 5* my_image[i, j, k] - my_image [i-1, j, k] \
                                -my_image[i+1, j, k] - my_image[i, j +1, k] -my_image[i, j-1, k]
                    result[i, j, k] = saturated(sum_value)
    return result


def main():

    image = cv.imread("Grass.jpg", cv.IMREAD_COLOR)

    if image is None:
        print("No data")
        print("水光潋滟晴方好,山色空蒙雨亦奇。")
    else:
        print("欲把西湖比西子,淡妆浓抹总相宜。")

        src = cv.pyrDown(image)

        cv.namedWindow("Input", cv.WINDOW_AUTOSIZE)
        cv.namedWindow("Output", cv.WINDOW_AUTOSIZE)
        cv.namedWindow("myOutput", cv.WINDOW_AUTOSIZE)

        cv.imshow("Input", src)
        # cv.waitKey()

        t = round(time.time())
        dst0 = sharpen(src)
        t = time.time() - t
        print("Hand written function time passed in seconds: %s" % t)

        cv.imshow("Output", dst0)

        t = time.time()
        kernel = np.array([[0, -1, 0],
                           [-1, 5, -1],
                           [0, -1, 0]], np.float )
        # kernel should be floating point type

        dst1 = cv.filter2D(src, -1, kernel)
        # depth = -1, means destination image has depth same as input image

        t = time.time() - t
        print("Built-in filter2D time passed in seconds:     %s" % t)

        cv.imshow("myOutput", dst1)
        cv.waitKey()
        cv.destroyAllWindows()



def readImageTest():
    image = cv.imread("Grass.jpg", cv.IMREAD_COLOR)

    if image is not None:
        pyrImage = cv.pyrDown(image)

        cv.namedWindow("Input", cv.WINDOW_AUTOSIZE)
        cv.imshow("Input", pyrImage)
        cv.waitKey()

    else:
        print("No data")


if __name__ == "__main__":
    # readImageTest()
    main()




Java

package com.company;

import java.util.Scanner;

import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Scalar;
import org.opencv.highgui.HighGui;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;

public class Main {

    static{ System.loadLibrary(Core.NATIVE_LIBRARY_NAME); }

        public static void main(String[] args) {

            System.out.println("Welcome to OpenCV " + Core.VERSION);
//            String filename = "././wolf.jpg";
            String filename = "Grass.jpg";


            int img_codec = Imgcodecs.IMREAD_COLOR;
            Mat src = Imgcodecs.imread(filename, img_codec);
            Imgproc.pyrDown(src, src);

            HighGui.namedWindow("Input", HighGui.WINDOW_AUTOSIZE);
            HighGui.imshow( "Input", src );

            double t = System.currentTimeMillis();
            Mat dst0 = sharpen(src, new Mat());
            t = ((double) System.currentTimeMillis() - t) / 1000;
            System.out.println("Hand written function time passed in seconds: " + t);
            HighGui.imshow( "Output", dst0 );
            HighGui.moveWindow("Output", 400, 400);
           // HighGui.waitKey();
            Mat kern = new Mat(3, 3, CvType.CV_8S);
            int row = 0, col = 0;
            kern.put(row, col, 0, -1, 0, -1, 5, -1, 0, -1, 0);
            t = System.currentTimeMillis();
            Mat dst1 = new Mat();
            Imgproc.filter2D(src, dst1, src.depth(), kern);
            t = ((double) System.currentTimeMillis() - t) / 1000;
            System.out.println("Built-in filter2D time passed in seconds:     " + t);
            HighGui.imshow( "Output", dst1 );
            HighGui.waitKey();
            System.exit(0);
        }

    public static double saturate(double x) {
        return x > 255.0 ? 255.0 : (x < 0.0 ? 0.0 : x);
    }

    public static Mat sharpen(Mat myImage, Mat Result) {
        myImage.convertTo(myImage, CvType.CV_8U);
        int nChannels = myImage.channels();
        Result.create(myImage.size(), myImage.type());
        for (int j = 1; j < myImage.rows() - 1; ++j) {
            for (int i = 1; i < myImage.cols() - 1; ++i) {
                double sum[] = new double[nChannels];
                for (int k = 0; k < nChannels; ++k) {
                    double top = -myImage.get(j - 1, i)[k];
                    double bottom = -myImage.get(j + 1, i)[k];
                    double center = (5 * myImage.get(j, i)[k]);
                    double left = -myImage.get(j, i - 1)[k];
                    double right = -myImage.get(j, i + 1)[k];
                    sum[k] = saturate(top + bottom + center + left + right);
                }
                Result.put(j, i, sum);
            }
        }
        Result.row(0).setTo(new Scalar(0));
        Result.row(Result.rows() - 1).setTo(new Scalar(0));
        Result.col(0).setTo(new Scalar(0));
        Result.col(Result.cols() - 1).setTo(new Scalar(0));
        return Result;
    }
}


 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值