opencv图片处理
1 图片处理 1.1 显示图片 #include <opencv2/core.hpp> #include <opencv2/imgcodecs.hpp> #include <opencv2/highgui.hpp> using namespace cv; int main(int argc, char *argv[]) { Mat image = imread("test.jpeg"); namedWindow("img"); imshow("img", image); waitKey(0); } 1.2 旋转图片
旋转图片用的是rotate(InputArray src, OutputArray dst, int rotateCode)方法,opencv提供了以下3种旋转方式。
enum RotateFlags { ROTATE_90_CLOCKWISE = 0, //顺时针旋转90度 ROTATE_180 = 1, //旋转180度 ROTATE_90_COUNTERCLOCKWISE = 2 //逆时针旋转90度 }; #include <opencv2/core.hpp> #include <opencv2/imgcodecs.hpp> #include <opencv2/highgui.hpp> using namespace cv; int main(int argc, char *argv[]) { Mat src= imread("test.jpeg"); Mat dst; rotate(src, dst, ROTATE_90_CLOCKWISE); //顺时针旋转90度 namedWindow("img"); imshow("img", dst); waitKey(0); } 1.3 合并图片把两张图片合并到一张。 思路:创建一个能容纳两张图片的Mat,然后对左右两个区域分别填充像素。
#include <opencv2/core.hpp> #include <opencv2/imgcodecs.hpp> #include <opencv2/highgui.hpp> #include <opencv2/imgproc.hpp> using namespace cv; int main(int argc, char *argv[]) { Mat img1 = imread("test1.jpeg"); Mat img2 = imread("test.jpeg"); Mat dst; dst.create(img1.rows > img2.rows ? img1.rows : img2.rows, img1.cols + img2.cols, img1.type()); Mat r1 = dst(Rect(0, 0, img1.cols, img1.rows)); img1.copyTo(r1); Mat r2 = dst(Rect(img1.cols, 0, img2.cols, img2.rows)); img2.copyTo(r2); namedWindow("img"); imshow("img", dst); waitKey(0); } 1.4、Mat类opencv的Mat类对象可以用来存放图像的像素数据,它有多种储存模式,比较常用的有以下四种。
#define CV_8UC1 CV_MAKETYPE(CV_8U,1) #define CV_8UC2 CV_MAKETYPE(CV_8U,2) #define CV_8UC3 CV_MAKETYPE(CV_8U,3) #define CV_8UC4 CV_MAKETYPE(CV_8U,4)它们的含义可以根据名字来判别,比如CV_8UC3,代表一个像素由3个8位的uchar储存,我们平时看到的rgb三色图就可以用这种类型来储存。
1.4.1、像素的储存结构Mat对象的像素数据存放在一个char类型数据上,成员名叫data。 以一张CV_8UC3类型、大小为2 * 2的图像为例,它对应的储存结构可以以下图表示。 在显示图像时,映射成下图所示。
1.4.2、访问像素数据从上一节我们可以看到,像素数据里的红蓝绿数据是以蓝(0),绿(1),红(2)的顺序来排列的。比如我们要访问第一行第一列的绿色数据,可以用mat.data[1]来访问,访问第二行第一列的红色数据,可以用mat.data[8]。
下面举例说明opencv修改像素数据的方式。 比如我们要用opencv来画一幅红蓝间条的图片,可以用以下程序来实现。
#include <opencv2/core.hpp> #include <opencv2/highgui.hpp> using namespace cv; int main(int argc, char *argv[]) { Mat mat(400, 300, CV_8UC3); //创建一张400 * 300的图片 int pixelSize = mat.elemSize(); //单个像素的大小,本例中大小为3 int size = mat.rows * mat.cols * pixelSize; //行*列*像素大小 = 缓存区大小 for (int i = 0; i < size; i += pixelSize) { /* 每隔十行切换一次颜色 */ int row = i / (mat.cols * pixelSize); if (row % 20 < 10) { /* 画蓝色像素点 */ mat.data[i] = 255; mat.data[i + 1] = 0; mat.data[i + 2] = 0; } else { /* 画红色像素点 */ mat.data[i] = 0; mat.data[i + 1] = 0; mat.data[i + 2] = 255; } } namedWindow("mat"); imshow("mat", mat); waitKey(0); }opencv中除了用mat.data可以访问像素数据外,还提供了几种访问方式,如下表所示。 mat.step代表一行的大小,即colSum * ps。
编号访问方式特点例子(访问第r行第c列的蓝色)1mat.data[index]遍历速度和访问速度都是最快的,但使用不便。data[r * mat.step + c + 0]2mat.ptr(row, col)速度比第一种稍慢,可以通过行号和列号来直接访问。mat.ptr(row, col)[0]3mat.at(row, col)速度最慢,可以通过行号和列号来直接访问。mat.at(row, col)[0]以上效率是用opencv3.4测得的,不同版本的效率可能不一样。 测试例程:
#include <opencv2/core.hpp> #include <opencv2/highgui.hpp> #include <QDebug> #include <QElapsedTimer> using namespace cv; int main(int argc, char *argv[]) { // printMs(); Mat mat(3000, 4000, CV_8UC3); int pixelSize = mat.elemSize(); QElapsedTimer timer; timer.start(); for (int r = 0; r < mat.rows; ++r) { for (int c = 0; c < mat.cols; ++c) { int rowStep = r * mat.step; int colIndex = c * pixelSize ; mat.data[rowStep + colIndex ] = 0;//B mat.data[rowStep + colIndex + 1] = 255;//G mat.data[rowStep + colIndex + 2] = 0;//R } } qDebug() << QString("%1: %2ms").arg("step").arg(timer.elapsed()); timer.start(); for (int r = 0; r < mat.rows; ++r) { for (int c = 0; c < mat.cols; ++c) { Vec3b *pixel = mat.ptr<Vec3b>(r, c); pixel->val[0] = 0;//B pixel->val[1] = 255;//G pixel->val[2] = 0;//R } } qDebug() << QString("%1: %2ms").arg("ptr").arg(timer.elapsed()); timer.start(); for (int r = 0; r < mat.rows; ++r) { for (int c = 0; c < mat.cols; ++c) { Vec3b &pixel = mat.at<Vec3b>(r, c); pixel.val[0] = 0;//B pixel.val[1] = 255;//G pixel.val[2] = 0;//R } } qDebug() << QString("%1: %2ms").arg("at").arg(timer.elapsed()); timer.start(); auto it = mat.begin<Vec3b>(), end = mat.end<Vec3b>(); for (; it != end; ++it) { (*it).val[0] = 0;//B (*it).val[1] = 255;//G (*it).val[2] = 255;//R } qDebug() << QString("%1: %2ms").arg("it").arg(timer.elapsed()); namedWindow("mat"); imshow("mat", mat); waitKey(0); } "step: 45ms" "ptr: 54ms" "at: 96ms" "it: 102ms" 1.6、rgb转灰度图 #include <opencv2/core.hpp> #include <opencv2/imgcodecs.hpp> #include <opencv2/highgui.hpp> #include <opencv2/imgproc.hpp> using namespace cv; int main(int argc, char *argv[]) { Mat img = imread("test.jpeg"); Mat gray; cvtColor(img , gray, COLOR_BGR2GRAY); namedWindow("gray"); imshow("gray", gray); waitKey(0); } 1.7、二值化二值化含义:把灰度大于阀值的用白色显示,小于阀值的像素用黑色显示。 opencv函数threshold(src, dst, thresh, maxval, type);
#include <opencv2/core.hpp> #include <opencv2/imgcodecs.hpp> #include <opencv2/highgui.hpp> #include <opencv2/imgproc.hpp> using namespace cv; int main(int argc, char *argv[]) { Mat src = imread("test.jpeg"); Mat gray; Mat bin; cvtColor(src, gray, COLOR_BGR2GRAY); threshold(gray, bin, 100, 255, THRESH_BINARY); // threshold(gray, bin, 100, 255, THRESH_BINARY_INV); namedWindow("img"); imshow("img", bin); waitKey(0); } 1.8、对比度和亮度对比度和亮度利用公式来计算:g(i, j) = a * f(i, j) + b a代表对比度(1.0 ~ 3.0),b代表亮度(0 ~ 100)。
void myConvert(Mat &src, Mat &dst, float a, float b) { dst.create(src.rows, src.cols, src.type); for (int r = 0; r < src.rows; ++r) { for (int c = 0; c < src.cols; ++c) { for (int i = 0; i < 3; ++i) { /* saturate_cast<uchar>限制表达式的结果最大为uchar的大小,也就是255 */ dst.at<Vec3b>(r,, c)[i] = saturate_cast<uchar>(a * src.at<Vec3b>(r, c)[i] + b); } } } }opencv提供的接口是convertTo。
#include <opencv2/core.hpp> #include <opencv2/imgcodecs.hpp> #include <opencv2/highgui.hpp> #include <opencv2/imgproc.hpp> using namespace cv; int main(int argc, char *argv[]) { Mat src = imread("test.jpeg"); Mat dst; src.convertTo(dst, -1, 2.0, 50); //类型填负数代表与src一致 namedWindow("img"); imshow("img", dst); waitKey(0); } 1.9、图片缩放 1.9.1、resizeopencv为图片缩放提供了resize(src, dst, dsize, fx = 0, fy = 0, interpolation = INTER_LINEAR)函数。
#include <opencv2/core.hpp> #include <opencv2/imgcodecs.hpp> #include <opencv2/highgui.hpp> #include <opencv2/imgproc.hpp> using namespace cv; int main(int argc, char *argv[]) { Mat src = imread("test.jpeg"); Mat dst; resize(src, dst, Size(300, 300), 0, 0, INTER_NEAREST); namedWindow("src"); imshow("src", src); namedWindow("dst"); imshow("dst", dst); waitKey(0); }resize的最后一个参数interpolation是缩放使用的算法类型,opencv提供了以下几种算法。
enum InterpolationFlags{ /** nearest neighbor interpolation */ INTER_NEAREST = 0, /** bilinear interpolation */ INTER_LINEAR = 1, /** bicubic interpolation */ INTER_CUBIC = 2, /** resampling using pixel area relation. It may be a preferred method for image decimation, as it gives moire'-free results. But when the image is zoomed, it is similar to the INTER_NEAREST method. */ INTER_AREA = 3, /** Lanczos interpolation over 8x8 neighborhood */ INTER_LANCZOS4 = 4, /** Bit exact bilinear interpolation */ INTER_LINEAR_EXACT = 5, /** mask for interpolation codes */ INTER_MAX = 7, /** flag, fills all of the destination image pixels. If some of them correspond to outliers in the source image, they are set to zero */ WARP_FILL_OUTLIERS = 8, /** flag, inverse transformation For example, #linearPolar or #logPolar transforms: - flag is __not__ set: \f$dst( \rho , \phi ) = src(x,y)\f$ - flag is set: \f$dst(x,y) = src( \rho , \phi )\f$ */ WARP_INVERSE_MAP = 16 };下面对INTER_NEAREST(临近点算法)和INTER_LINEAR(双线性内插值)做介绍。
临近点算法原理:
特点:速度最快,会失真,不够平滑。
双线性内插值原理:用像素点的临近四个点做平均计算。
特点:速度稍慢一点,平滑效果比临近点算法好,但图像的边界的棱角会被平均而模糊掉。
1.9.2、金字塔缩放opencv提供了向上重建(拉普拉斯金字塔,放大)的pyrUp()和向下采样(高斯金字塔,缩小)的pyrDown(),可以比较好地还原图像特征,它们无法指定放大的倍数,只能成倍地放大或缩小,比如1616的图片调用pyrUp,就变为6464,而调用pyrDown,就变为4*4。 用法如下:
#include <opencv2/core.hpp> #include <opencv2/imgcodecs.hpp> #include <opencv2/highgui.hpp> #include <opencv2/imgproc.hpp> using namespace cv; int main(int argc, char *argv[]) { Mat src = imread("test.jpeg"); Mat gdst; //高斯,缩小 Mat ldst; //拉普拉斯,放大 pyrDown(src, gdst); pyrUp(src, ldst); namedWindow("src"); imshow("src", src); namedWindow("gdst"); imshow("gdst", gdst); namedWindow("ldst"); imshow("ldst", ldst); waitKey(0); } 1.10、图片叠加原理: dst = src1 * a + src2 * (1 - a) + gamma; a代表透明度(0 ~ 1.0),gamma代表增益,用来调整颜色深度。 opencv提供了addWeighted(src1, alpha, src2, beta, gamma, dst, dtype = -1)函数,可以用来叠加图片。
#include <opencv2/core.hpp> #include <opencv2/imgcodecs.hpp> #include <opencv2/highgui.hpp> #include <opencv2/imgproc.hpp> using namespace cv; int main(int argc, char *argv[]) { Mat src = imread("test1.jpeg"); Mat src2 = imread("test.jpeg"); Mat src1 = src(Rect(0, 0, src2.cols, src2.rows)); //把在图一中截取图二相同大小的部分 Mat dst; float a = 0.5; //透明度 addWeighted(src1, a, src2, 1 - a, 1, dst); namedWindow("blending"); imshow("blending", dst); waitKey(0); }