OpenCV之图像处理(三) 矩阵的掩膜操作

掩膜操作:   ┌  0 -1  0 ┐ 例如:掩膜矩阵 3*3 在图像矩阵上移动与图像重合,与每一个重合的像素点做掩膜操作,公式:中心点掩膜后的颜色数据 I(i,j) = 5*I(i,j) - [I(i-1,j)+I(i+1,j)+I(i,j-1)+I(i,j+1)]
            │ -1  5 -1 │          这里是3*3的矩阵,所以图像数据的第一行倒数第一行,第一列倒数第一列不做掩膜操作          i,j表示像素的位置,第i行,第j列, I(i,j) 表示每个通道颜色数据
            └  0 -1  0 ┘          掩膜操作不是矩阵乘法,由公式可以看出

掩膜操作可以提高图像对比度,对比度提高可以增加图像感官度、锐化,让看起来有点模糊的图像更清晰

vs代码

    #include <opencv2\opencv.hpp>
    #include <iostream>
    using namespace cv;
    void main(int argc, char ** argv)
    {
        Mat src, dst;
        double time, timeconsume;
        src = imread("D:/hankin/opencv/images/test1_3.png", IMREAD_COLOR);
        if (!src.data)
        {
            printf("could not load image..");
            return;
        }
        namedWindow("input image", CV_WINDOW_AUTOSIZE);
        imshow("input image", src);

        dst = Mat::zeros(src.size(), src.type());//生成与src一样大小与类型的Mat,且图像数据初始值为0,这也导致了掩膜后的图像边框变成了黑色,用构造函数创建dst,边框是白色
        int cols = (src.cols-1) * src.channels();//图像通道数(可以理解为宽度),图像数据列数(最后一列排除)乘以通道数,src.channels()表示通道数,RGB的图像有三个通道
        int offsetX = src.channels();//每个图像数据的通道数
        int rows = src.rows;//可以理解为图像高度
        time = getTickCount();//tick当量
        for (int row = 1; row < rows-1; row++) //因为是3*3的掩膜矩阵,所以row是从1开始,到倒数第二行,列同理,所以dst图像数据的第一、倒数第一行、列 颜色数据为初始数据
        {
            const uchar* current = src.ptr<uchar>(row);//src.ptr表示图像数据指针,row指向第几行
            const uchar* previous = src.ptr<uchar>(row - 1);//上一行
            const uchar* next = src.ptr<uchar>(row + 1);//下一行
            uchar* output = dst.ptr<uchar>(row);//图像数据输出
            for (int col = offsetX; col < cols; col++)
            {
                //公式: I(i,j) = 5*I(i,j) - [I(i-1,j)+I(i+1,j)+I(i,j-1)+I(i,j+1)]  i,j表示像素的位置,第i行,第j列, I(i,j) 表示每个通道颜色数据
                //像素范围处理 saturate_cast 将颜色数据控制在 0-255 之间,不然图像会出现一些坑坑洞洞   +-offsetX 是当前颜色通道数据对应相应前后的 颜色通道数据
                output[col] = saturate_cast<uchar>(5 * current[col] - (current[col - offsetX] + current[col + offsetX] + previous[col] + next[col]));
            }
        }
        timeconsume = (getTickCount() - time) / getTickFrequency();//耗时,单位秒
        printf("1timeconsume=%f\n", timeconsume);//7毫秒。。
        namedWindow("output image", CV_WINDOW_AUTOSIZE);
        imshow("output image", dst);

        //利用opencv函数进行掩膜操作
        Mat dst_opencv;//构造函数,颜色数据默认为白色
        time = getTickCount();
        Mat kernel = (Mat_<char>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);//定义掩膜 3*3矩阵,前三个数表示第一列还是第一行?  右值的最外层括号在语法上仅仅只是包裹的意思
        filter2D(src, dst_opencv, src.depth(), kernel);//调用OpenCV函数进行掩膜操作,src.depth表示位图深度,有32、24、8等,如果不知道也可以传-1,表示跟输入图像一样
        timeconsume = (getTickCount() - time) / getTickFrequency();//耗时
        printf("2timeconsume=%f", timeconsume);//3毫秒。。
        namedWindow("opencv output image", CV_WINDOW_AUTOSIZE);
        imshow("opencv output image", dst_opencv);//用opencv函数掩膜结果,边框是白色

        waitKey(0);
    }

效果图

这里写图片描述

android代码

@BindView(R.id.iv_opencv1_3_origin) ImageView mOriginIv;
@BindView(R.id.iv_opencv1_3_mask1) ImageView mMask1Iv;
@BindView(R.id.iv_opencv1_3_mask2) ImageView mMask2Iv;
private Bitmap mOriginBmp;
private Bitmap mMask1Bmp;
private Bitmap mMask2Bmp;
private Mat mOriginMat = new Mat();
private Mat mMask1Mat;
private Mat mMask2Mat = new Mat();//构造函数,颜色数据默认为白色

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_cv1_3);
    mUnbinder = ButterKnife.bind(this);

    mOriginBmp = CV310Utils.getBitmapFromAssets(this, "opencv/test1_3.png");
    mOriginIv.setImageBitmap(mOriginBmp);
    mMask1Bmp = Bitmap.createBitmap(mOriginBmp.getWidth(), mOriginBmp.getHeight(), Bitmap.Config.RGB_565);
    mMask2Bmp = Bitmap.createBitmap(mOriginBmp.getWidth(), mOriginBmp.getHeight(), Bitmap.Config.RGB_565);
    Utils.bitmapToMat(mOriginBmp, mOriginMat);
    //生成与src一样大小与类型的Mat,且图像数据初始值为0,这也导致了掩膜后的图像边框变成了黑色,用构造函数创建dst,边框是白色
    mMask1Mat = Mat.zeros(mOriginMat.size(), mOriginMat.type());
}

/*自写算法,掩膜*/
private void mask1() {
    int cols = (mOriginMat.cols()-1) * mOriginMat.channels();//图像通道数(可以理解为宽度),图像数据列数(最后一列排除)乘以通道数,src.channels()表示通道数,RGB的图像有三个通道
    int offsetX = mOriginMat.channels();//每个图像数据的通道数
    int rows = mOriginMat.rows();//可以理解为图像高度
    double time1 = Core.getTickCount();//tick当量
    for (int row = 1; row < rows-1; row++) //因为是3*3的掩膜矩阵,所以row是从1开始,到倒数第二行,列同理,所以dst图像数据的第一、倒数第一行、列 颜色数据为初始数据
    {
        int len = mOriginMat.cols()*mOriginMat.channels();//一行图像数据的所有通道的颜色数据长度
        //用 CV310Utils.byte2unsignedchar 将java的有符号byte转换为无符号的数值
        byte current[] = new byte[len];//当前行图像数据,数据应该是无符号一字节数值,但是java没有 unsigned char 。。   这里用int数组会报错
        int shuCur = mOriginMat.row(row).get(0, 0, current);//row函数返回第row行的矩阵数据,get函数,从第0行0列开始回去图像数据
        byte previous[] = new byte[len];//上一行
        int shuPre = mOriginMat.row(row-1).get(0, 0, previous);
        byte next[] = new byte[len];//下一行
        int shuNext = mOriginMat.row(row+1).get(0, 0, next);
        LogUtils.d("mydebug---", "len="+len+",shuCur="+shuCur+",shuPre="+shuPre+",shuNext="+shuNext);//1268
        byte output[] = new byte[len];//每行图像数据输出,这里也应该是无符号一字节数值,但是用 mMask1Mat.put 设值没有问题
        for (int col = offsetX; col < cols; col++)
        {
            //公式: I(i,j) = 5*I(i,j) - [I(i-1,j)+I(i+1,j)+I(i,j-1)+I(i,j+1)]  i,j表示像素的位置,第i行,第j列, I(i,j) 表示每个通道颜色数据
            //像素范围处理 saturate_cast 将颜色数据控制在 0-255 之间,不然图像会出现一些坑坑洞洞   +-offsetX 是当前颜色通道数据对应相应前后的 颜色通道数据
            int ret = 5 * CV310Utils.byte2unsignedchar(current[col]) - (CV310Utils.byte2unsignedchar(current[col - offsetX]) +
                    CV310Utils.byte2unsignedchar(current[col + offsetX]) + CV310Utils.byte2unsignedchar(previous[col]) + CV310Utils.byte2unsignedchar(next[col]));
            output[col] = (byte) CV310Utils.saturateCastUchar(ret);//saturateCastUchar返回的是0-225之间的int型,但是这里直接强转为byte赋给Mat是没问题的
        }
        mMask1Mat.put(row, 0, output);//为掩膜后的矩阵每行数据设值,从第row行、0列开始设置
    }
    double timeconsume1 = (Core.getTickCount() - time1) / Core.getTickFrequency();//耗时,单位秒
    LogUtils.d("mydebug---", "timeconsume1="+timeconsume1);//700毫秒左右
    Utils.matToBitmap(mMask1Mat, mMask1Bmp);
    mMask1Iv.setImageBitmap(mMask1Bmp);
}

/*调用opencv函数,掩膜*/
private void mask2() {
    double time2 = Core.getTickCount();//tick当量
    float[] maskMat = new float[]{0, -1, 0, -1, 5, -1, 0, -1, 0};//矩阵元素的数据类型是float, Mat的type用 CvType.CV_32FC1
    Mat kernel = new Mat(3, 3, CvType.CV_32FC1);//定义掩膜 3*3矩阵,前三个数表示第一列还是第一行?  右值的最外层括号在语法上仅仅只是包裹的意思
    kernel.put(0, 0, maskMat);//给矩阵设值,get 函数获取值
    //调用OpenCV函数进行掩膜操作,src.depth表示位图深度,有32、24、8等,如果不知道也可以传-1,表示跟输入图像一样
    Imgproc.filter2D(mOriginMat, mMask2Mat, mOriginMat.depth(), kernel);
    double timeconsume2 = (Core.getTickCount() - time2) / Core.getTickFrequency();//耗时,单位秒
    LogUtils.d("mydebug---", "timeconsume2="+timeconsume2);//170毫秒左右
    Utils.matToBitmap(mMask2Mat, mMask2Bmp);
    mMask2Iv.setImageBitmap(mMask2Bmp);
}

@OnClick({R.id.btn_opencv1_3_mask1, R.id.btn_opencv1_3_mask2})
void click(View view) {
    switch (view.getId()) {
        case R.id.btn_opencv1_3_mask1://自写算法,掩膜
            mask1();
            break;
        case R.id.btn_opencv1_3_mask2://调用opencv函数,掩膜
            mask2();
            break;
    }
}

效果图

这里写图片描述

猜你喜欢

转载自blog.csdn.net/huanghuangjin/article/details/80864230