近来给自己开个新坑,打算学习下OpenCV这一计算机视觉库。
【以下内容默认版本OpenCV 2.3.1】
最简单的操作莫过于对于一张图片上的像素的遍历了,然而,遍历的方式有很多种,如何取舍很是关键。
简单介绍几种常见的方式:
1.指针遍历:
int row = image.rows;
int col = image.cols * image.channels();
for(int i = 0; i < row; i++) {
uchar* data = image.ptr<uchar>(i);
for(int j = 0; j < col; j++) {
...
}
}
原理简单易懂,类似遍历一个二维矩阵一样。首先得到行数和列数(像素列数*通道数),例如黑白图片的通道数为1,彩色图片的通道数为3。然后每行将指针指向该行的首地址即可。需要说明的一点是,之所以每次都需要获取一下行的首地址,是因为处于效率的考虑,每行的结尾元素的地址的下一个元素地址并不等于下一行首元素的地址,而是会额外补充一些像素。原因是这样处理会使一些多媒体处理芯片可以更高效的处理图像。
基于以上这点特性,我们可以得到如下改进版:
int row = image.rows;
int col = image.cols * image.channels();
if(image.isContinuous()) { //判断有没有额外补充像素
col = col * row;
row = 1;
}
for(int i = 0; i < row; i++) {
uchar* data = image.ptr<uchar>(i);
for(int j = 0; j < col; j++) {
...
}
}
如代码所示,对于没有额外补充像素的图片,可以大大减少ptr这个函数的调用,这样对于处理多个小图片的情况来说是是极为可取的。
2.迭代器遍历:
迭代器通过隐藏内部实现方式的方法,提供了相对通用且普遍的使用方法。大大提升了代码的可读性。
cv::Mat_<cv::Vec3b>::iterator it = image.begin<cv::Vec3b>();
cv::Mat_<cv::Vec3b>::iterator itend = image.end<cv::Vec3b>();
for(; it != itend; ++it) {
...
}
在遍历的过程中,由于处理的是彩色图像,我们可以通过操作符[]来访问,如(*it)[0]和
(*it)[2]。
于是乎,我们在get遍历技能之后,就可以简单的实现一些如图片锐化的基础操作了。下面附上一下我基于控制台的代码,以及一张简单的效果示例图。
#include <opencv2\opencv.hpp>
#include <iostream>
#include <string>
#include <stdio.h>
using namespace std;
using namespace cv;
/*-------------------添加椒盐噪点---------------------*/
void salt(const cv::Mat &input, cv::Mat &output, int n) {
input.copyTo(output);
int col = output.cols;
int row = output.rows;
int channel = output.channels();
for(int i = 0; i < n; i++) {
int x = rand() % row;
int y = rand() % col;
if(channel == 1) {
output.at<uchar>(x, y) = 0;
} else {
output.at<cv::Vec3b>(x, y)[0] = 0;
output.at<cv::Vec3b>(x, y)[1] = 0;
output.at<cv::Vec3b>(x, y)[2] = 0;
}
}
}
/*-------------------颜色缩减函数---------------------*/
void colorReduce(const cv::Mat &input, cv::Mat &output, int div = 64) {
if(input.isContinuous()) {
input.reshape(1, input.cols * input.rows);
}
int row = input.rows;
int nc = input.cols * input.channels();
for(int i = 0; i < row; i++) {
const uchar* data_in = input.ptr<uchar>(i);
uchar* data_out = output.ptr<uchar>(i);
for(int j = 0; j < nc; j++) {
data_out[j] = data_in[j] / div * div + div / 2;
}
}
}
/*-------------------颜色缩减函数---------------------*/
void sharpen2D(const cv::Mat &input, cv::Mat &output) {
//构造核(并将其他项初始化为0)
cv::Mat kernel(3, 3, CV_32F, cv::Scalar(0));
//对核元素进行赋值
kernel.at<float>(0, 1) = -1.0;
kernel.at<float>(1, 0) = -1.0;
kernel.at<float>(1, 2) = -1.0;
kernel.at<float>(2, 1) = -1.0;
kernel.at<float>(1, 1) = 5.0;
//对图像进行滤波
cv::filter2D(input, output, input.depth(), kernel);
}
/*-------------------添加水印函数---------------------*/
void getWaterMark(cv::Mat &logo, cv::Mat &output) {
//定义图像ROI
cv::Mat imageROI;
int row = output.rows, col = output.cols;
int logo_row = logo.rows, logo_col = logo.cols;
if(row < logo_row || col < logo_col) {
puts("logo过大或图片过小,水印加载失败");
return ;
}
imageROI = output(cv::Rect(col - logo_col, row - logo_row, logo_col, logo_row));
//加载掩模, 并插入logo
logo.copyTo(imageROI, logo);
}
/*--------------------主 函 数----------------------*/
int main() {
std::string str;
cv::Mat output;
puts("输入图片名称,含文件后缀名部分:");
cin >> str;
cv::Mat input = cv::imread(str);
if(input.empty()) {
cout<< "error" << endl;
return -1;
}
cout << "已成功打开图片, 图片的大小为 : ";
cout << input.size().width << " * " << input.size().height << "像素." << endl;
int type;
int vis[10] = {0};
cout << "1.显示原图像" << endl;
cout << "2.增加椒盐噪点" << endl;
cout << "3.颜色缩减" << endl;
cout << "4.图像锐化" << endl;
cout << "5.添加logo" << endl;
cout << "6.显示当前修改图像" << endl;
cout << "0.退出" << endl;
while(true) {
cout << "请输入您需要的操作序号(按Enter键结束):" << endl;
cin >> type;
if(type < 0 || type > 6) {
cout << "无效操作" << endl;
continue;
}
if(vis[type]) {
cout << "该操作已经执行过,请尝试其他功能" << endl;
} else vis[type] = 1;
if(type == 1) {
cv::imshow("原图像", input);
cv::waitKey();
} else if(type == 2) {
salt(input, output, 3000);
} else if(type == 3) {
colorReduce(input, output);
} else if(type == 4) {
sharpen2D(input, output);
} else if(type == 5) {
bool exist = false;
for(int i = 1; i <= 6; i++) {
if(i == 5) continue;
else if(vis[i]) exist = true;
}
if(!exist) {
cout << "当前输出图片不存在" << endl;
vis[type] = false;
continue;
}
cv::Mat logo = cv::imread("gx_logo.png");
getWaterMark(logo, output);
} else if(type == 6) {
cv::namedWindow("dog");
cv::imshow("dog", output);
cv::waitKey();
} else if(type == 0) {
break;
}
}
cv::imwrite("haha.bmp", output);
puts("运行结束");
system("pause");
return 0;
}