图像基本操作(2)
读取像素
// 读取一个GRAY像素点的像素值( CV_8UC1)
Scalar intensity = img.at<ychar>(y,x);
// 读取一个RGB像素点的像素值
Vec3f intensity = img.at<Vec3f>(y,x);
float blue = intensity.val[0];
float green = intensity.val[1];
float red = intensity.val[2];
// 空白图像赋值
img=Scalar(0);
// ROI选择
Rect r(10, 10, 100, 100);
Mat smallImg = img(r);
#include <opencv2/opencv.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include<iostream>
#include<cmath>
using namespace std;
using namespace cv;
int main()
{
Mat gray,gray_img;
Mat img = imread("tahiti.jpg");
//namedWindow("塔西提航空", WINDOW_FREERATIO);
imshow("imput", img);
cvtColor(img, gray_img, COLOR_BGR2GRAY);
imshow("output", gray_img);
// 图像的高度宽度
int height = gray_img.rows;
int width = gray_img.cols;
// 读取像素
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
gray = gray_img.at<uchar>(row, col);
}
}
waitKey(0);
return 0;
}
修改像素值
通过修改通道数或三条通道的各值来改变图像各像素像素值
#include<iostream>
#include<opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
Mat img, gray_img, dst;
img = imread("tahiti.jpg");
if (img.empty())
{
cout << "image loading failed..." << endl;
return -1;
}
imshow("input", img);
cvtColor(img, gray_img, COLOR_BGR2GRAY);
//imshow("gray_img", gray_img);
int height = gray_img.rows;
int weight = gray_img.cols;
int nc = gray_img.channels();
//printf("the channel of this image is %d\n", nc);
if (nc == 1)//单通道
{
for (int row = 0; row < height; row++)
{
for (int col = 0; col < weight; col++)
{
int gray = gray_img.at<uchar>(row, col);
gray_img.at<uchar>(row, col) = 255 - gray;
}
}
imshow("output", gray_img);
}
else
if (nc == 3)//三通道
{
dst.create(img.size(), img.type());
for (int row = 0; row < height; row++)
{
for (int col = 0; col < weight; col++)
{
int b = img.at<Vec3b>(row, col)[0];//读取三个通道的像素值
int g = img.at<Vec3b>(row, col)[1];
int r = img.at<Vec3b>(row, col)[2];
dst.at<Vec3b>(row, col)[0] = 255 - b;//反射操作
dst.at<Vec3b>(row, col)[1] = 255 - g;
dst.at<Vec3b>(row, col)[2] = 255 - r;
gray_img.at<uchar>(row, col) = min(r, min(g, b));
}
}
imshow("gray_img", gray_img);
imshow("dst", dst);
}
//bitwise_not(img, dst);
waitKey(0);
return 0;
}
图像混合
图像混合的重要前提:大小和类型必须相同.
void addWeighted(InputArray src1,//表示需要加权的第一个数组,常常填一个Mat。
double alpha,//表示第一个数组的权重
InputArray src2,//表示第二个数组,它需要和第一个数组拥有相同的尺寸和通道数
double beta, //表示第二个数组的权重值。
double gamma, //一个加到权重总和上的标量值。看下面的式子自然会理解
OutputArray dst,//输出的数组,它和输入的两个数组拥有相同的尺寸和通道数
int dtype=-1);//输出阵列的可选深度,有默认值-1。;当两个输入数组具有相同的深度时,这个参数设置为-1(默认值),即等同于src1.depth()。
#include <opencv2/opencv.hpp>
#include<iostream>
#include<math.h>
#include <string>
#include<fstream>
using namespace cv::face;
using namespace cv;
using namespace std;
using namespace cv::xfeatures2d;
int main() {
Mat src1, src2, dst;
src1=imread("C:\\Users\\Administrator\\Desktop\\pic\\2.jpg");
src2=imread("C:\\Users\\Administrator\\Desktop\\pic\\3.jpg");
double alpha = 0.5;
if (src1.rows == src2.rows&&src2.cols == src1.cols) {
addWeighted(src1, alpha, src2, (1.0 - alpha), 0.0, dst);
imshow("图片1", src1);
imshow("图片2", src2);
imshow("混合后图片", dst);
waitKey(0);
return 0;
}
else {
//cout << "图片大小不一样" << endl;
printf("图片大小不一样");
return -1;
}
}
运行:
修改图像亮度
Mat new_image = Mat::zeros(image.size(), image.type()); | 创建一张跟原图像大小和类型一致的空白图像,像素值初始化为0 |
saturate_cast<uchar>(value) | 确保value大小范围为0~255之间 |
Mat.at<Vec3b>(y, x)[index]=value | 给每个像素点每个通道赋值 |
下面是一个示例:
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
using namespace std;
using namespace cv;
double alpha; /**< 控制对比度 */
int beta; /**< 控制亮度 */
int main(int argc, char** argv)
{
/// 读入用户提供的图像
Mat image = imread("tahiti.jpg");
Mat new_image = Mat::zeros(image.size(), image.type());
/// 初始化
cout << " Basic Linear Transforms " << endl;
cout << "-------------------------" << endl;
cout << "* Enter the alpha value [1.0-3.0]: ";
cin >> alpha;
cout << "* Enter the beta value [0-100]: ";
cin >> beta;
/// 执行运算 new_image(i,j) = alpha*image(i,j) + beta
for (int y = 0; y < image.rows; y++)
{
for (int x = 0; x < image.cols; x++)
{
for (int c = 0; c < 3; c++)
{
new_image.at<Vec3b>(y, x)[c] = saturate_cast<uchar>(alpha * (image.at<Vec3b>(y, x)[c]) + beta);
}
}
}
/// 创建窗口
namedWindow("Original Image", 1);
namedWindow("New Image", 1);
/// 显示图像
imshow("Original Image", image);
imshow("New Image", new_image);
/// 等待用户按键
waitKey(0);
return 0;
}
绘制基本图形和文字
cv::line (LINE_4\ LINE_8\ LINE_AA) | 画线 |
cv::ellipse | 画椭圆 |
cv::rectangle | 画矩形 |
cv::circle | 画圆 |
cv::fillPoly | 画填充 |
下面是一个示例:
#include <opencv2/opencv.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include<iostream>
using namespace std;
using namespace cv;
int main()
{
Mat image = Mat::zeros(300, 600, CV_8UC3);
circle(image, Point(300, 200), 100, Scalar(266, 122 ,288), -100);
circle(image, Point(400, 200), 100, Scalar(98, 277, 127), -100);
imshow("Show Window", image);
waitKey(0);
return 0;
}
随机绘制大小不同的圆:
#include<opencv2/opencv.hpp>
using namespace cv;
Mat dst, src1;
void randomCircle();
int main()
{
src1 = imread("tahiti.jpg");
dst = Mat::zeros(src1.size(),src1.type());
randomCircle();
return 0;
}
void randomCircle()
{
RNG rng(0);
Point p1;
int r;
for(int i =0; i < 10000; i++)
{
p1.x = rng.uniform(0, dst.cols);
p1.y = rng.uniform(0, dst.rows);
r = rng.uniform(0, dst.rows/3);
Scalar color = Scalar(rng.uniform(0,255), rng.uniform(0, 255), rng.uniform(0, 255));
if(waitKey(100) > 0)
{
break;
}
circle(dst,p1,r,color,2);
imshow("circle", dst);
}
}
图像模糊
均值模糊和高斯模糊:
#include<opencv2\opencv.hpp>
#include<iostream>
#include<math.h>
using namespace cv;
using namespace std;
Mat src,dst, gblur;
int main()
{
src = imread("C:/Users/asus/Desktop/tupian/1.jpg");
if (!src.data)
{
printf("could not load image\n");
return -1;
}
char input_title[] = "input title";
char out_title []= "out_title";
namedWindow(input_title, WINDOW_AUTOSIZE);
namedWindow(out_title, WINDOW_AUTOSIZE);
imshow(input_title, src);
blur(src, dst, Size(11, 11), Point(-1, -1));
imshow(out_title, dst);
GaussianBlur(src, gblur, Size(11, 11), 11, 11);
imshow("GaussianBlur", gblur);
waitKey(0);
return 0;
}
原图
均值模糊
高斯模糊
除此之外比较常用的还有:中值滤波 medianblur函数 和 双边滤波 bilateralFilter函数.
矩阵的掩膜操作
掩膜其实就是一个矩阵,然后根据这个矩阵重新计算图片中像素的值。
掩膜主要有以下用途:
1.提取感兴趣区,用预先制作的感兴趣区掩模与待处理图像相乘,得到感兴趣区图像,感兴趣区内图像值保持不变,而区外图像值都为0。
2.屏蔽作用,用掩模对图像上某些区域作屏蔽,使其不参加处理或不参加处理参数的计算,或仅对屏蔽区作处理或统计。
3.结构特征提取,用相似性变量或图像匹配方法检测和提取图像中与掩模相似的结构特征。
4.特殊形状图像的制作。
掩膜操作实现图像对比度的提高
图像像素指针的获取
//获取第row行的图像像素指针。图像的行数从0开始计数
Mat.ptr<uchar>(row)
//获取点P(row,col)的像素值
P(row.col)= Mat.ptr<uchar>(row)[col]
使用的掩膜
** 红色是中心元素I(i,j)。** 掩膜操作公式:
I ( i , j ) = 5 ∗ I ( i , j ) − [ I ( i − 1 , j ) + I ( i + 1 , j ) + I ( i , j − 1 ) + I ( i , j + 1 ) ]
用此掩膜从上至下,从左至右对图像进行操作,得到的图像就是增强对比度的图像。
Mat src,dest;
src = imread("tahiti.jpg");
if(!src.data){
cout << "文件打开失败" << endl;
return ;
}
namedWindow("掩膜操作前",WINDOW_AUTOSIZE);
imshow("掩膜操作前",src);
dest = Mat::zeros(src.size(),src.type());//生成一个和源图像大小相等类型相同的全0矩阵
int cols = (src.cols-1)*src.channels();//获取图像的列数,一定不要忘记图像的通道数
int rows = src.rows;//获取图像的行数
int offsetx = src.channels();
for (int row = 1; row < rows-1;row++){
uchar* previous = src.ptr<uchar>(row-1);
uchar* current = src.ptr<uchar>(row);
uchar* next = src.ptr<uchar>(row+1);
uchar* output = dest.ptr<uchar>(row);
for (int col = offsetx; col < cols; col++){
output[col] = 5*current[col] - (previous[col]+next[col]+current[col-1]+current[col+1]);
}
namedWindow("掩膜操作后",WINDOW_AUTOSIZE);
imshow("掩膜操作后",dest);
waitKey(0);
destroyAllWindows();
return 0;
}
我们可以看见掩膜操作后的图像对比度明显提高了,但是美中不足的是出现了一些不好的小斑点。这是因为这项像素点的值的范围不在0~255之间了。
解决方法:
使用函数saturate_cast(像素值),这个函数的作用就是确保RGB的值在0~255之间。
saturate_cast(-100) | 返回0 |
saturate_cast(100) | 返回100 |
saturate_cast(280) | 返回255 |
进行该函数添加:
//添加前
output[col] = 5*current[col] - (previous[col]+next[col]+current[col-1]+current[col+1]);
//添加后
output[col] = saturate_cast<uchar>( 5*current[col] - (previous[col]+next[col]+current[col-1]+current[col+1]));
OpenCV使用函数filter2D来实现掩膜操作
Mat src,dest;
src = imread("tahiti.jpg");
if(!src.data){
cout << "文件打开失败" << endl;
return ;
}
namedWindow("掩膜操作前",CV_WINDOW_AUTOSIZE);
imshow("掩膜操作前",src);
Mat kernel = (Mat_<char>(3,3)<<0,-1,0,-1,5,-1,0,-1,0);//定义掩膜
//调用filter2D
filter2D(src,dest,src.depth(),kernel);
namedWindow("掩膜操作后",CV_WINDOW_AUTOSIZE);
imshow("掩膜操作后",dest);
waitKey(0);
destroyAllWindows();
return 0;
Mat 对象
常用构造函数
Mat::Mat() | 无参数构造方法 |
Mat::Mat(int rows, int cols, int type | 创建行数为 rows,列数为 col,类型为 type 的图像; |
Mat::Mat(Size size, int type) | 创建大小为 size,类型为 type 的图像; |
Mat::Mat(int rows, int cols, int type, const Scalar& s) | 创建行数为 rows,列数为 col,类型为 type 的图像,并将所有元素初始化为值 s |
Mat::Mat(Size size, int type, const Scalar& s) | 创建大小为 size,类型为 type 的图像,并将所有元素初始化为值 s |
Mat::Mat(const Mat& m) | 将m赋值给新创建的对象,此处不会对图像数据进行复制,m和新对象共用图像数据,属于浅拷贝 |
Mat::Mat(int rows, int cols, int type, void* data, size_t step=AUTO_STEP) |
创建行数为rows,列数为col,类型为type的图像,此构造函数不创建图像数据所需内存,而是直接使用data所指内存,图像的行步长由 step指定 |
Mat::Mat(Size size, int type, void* data, size_t step=AUTO_STEP) | 创建大小为size,类型为type的图像,此构造函数不创建图像数据所需内存,而是直接使用data所指内存,图像的行步长由step指定 |
Mat::Mat(const Mat& m, const Range& rowRange, const Range& colRange) | 创建的新图像为m的一部分,具体的范围由rowRange和colRange指定,此构造函数也不进行图像数据的复制操作,新图像与m共用图像数据; |
Mat::Mat(const Mat& m, const Rect& roi) | 创建的新图像为m的一部分,具体的范围roi指定,此构造函数也不进行图像数据的复制操作,新图像与m共用图像数据 |
这些构造函数中,很多都涉及到类型type。type可以是CV_8UC1,CV_16SC1,…,CV_64FC4 等。
里面的 8U 表示 8 位无符号整数,16S 表示 16 位有符号整数,64F表示 64 位浮点数(即 double 类型);C 后面的数表示通道数,例如 C1 表示一个
通道的图像,C4 表示 4 个通道的图像,以此类推。
如果你需要更多的通道数,需要用宏 CV_8UC(n),例如:
Mat M(3,2, CV_8UC(5));//创建行数为 3,列数为 2,通道数为 5 的图像。
常用方法
void Mat::copyTo(Mat mat) | 把矩阵复制到另一个矩阵中 |
void Mat::convertTo(Mat dst, int type) | 在缩放或不缩放的情况下转换为另一种数据类型 |
Mat Mat::clone() | 创建一个数组及其基础数据的完整副本 |
int Mat::channels() | 返回矩阵通道的数目 |
int Mat::depth() | 返回一个矩阵元素的深度 |
bool Mat::empty(); | 如果数组有没有 elemens,则返回 true |
uchar* Mat::ptr( i=0) | 返回指定矩阵行的指针 |