掩膜操作: ┌ 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;
}
}