c++ opencv 常用API

读取图片

cv::Mat backimg = cv::imread("../imgs/3789.png");

图片转换

cv::cvtColor(backimg, backimg, cv::COLOR_BGR2GRAY);

图片resize

void resize( InputArray src, OutputArray dst,
             Size dsize, double fx = 0, double fy = 0,
             int interpolation = INTER_LINEAR );
  • InputArray src,输入图像,如Mat类型。
  • OutputArray dst,输出图像,其尺寸由第三个参数dsize决定。
  • Size dsize,输出图像的大小。如果它等于,则大小等于Size(round(fxsrc.cols),round(fysrc.rows))。
  • double fx,沿水平轴的缩放系数,默认值为0,当其等于0时,其数值由该式计算:(double)dsize.width/src.cols;
  • double fy,沿垂直轴的缩放系数,默认值为0,当其等于0时,其数值由该式计算:(double)dsize.height/src.rows;
  • int interpolation,表示不同的插值方式,默认为INTER_LINEAR(线性插值)。
    INTER_NEAREST,最邻近插值。
    INTER_LINEAR,线性插值。
    INTER_CUBIC,三次样条插值,适合放大图像用。
    INTER_AREA,区域插值,适合缩小图像用。
    INTER_LANCZOS4,Lancazos插值。
    INTER_LINEAR_EXACT ,位精确双线性插值。
    INTER_MAX,内插码掩模。
    WARP_FILL_OUTLIERS,官方解释: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_INVERSE_MAP,官方解释:flag, inverse transf4ormation。

图像翻转

OpenCV中,X轴是横轴,即列数;Y轴是竖轴,即行数。Y行X列

void flip(InputArray src, OutputArray dst, int flipCode);
  • InputArray src,输入图像,即源图像,Mat类的对象即可。图像类型一般是CV_8U、CV_16U、CV_16S、CV_32F、CV_64F之一。
  • OutputArray dst,输出图像,也是目标图像,和输入图像一致的类型和尺寸。
  • int filpCode,设置翻转类型的参数。filpCode大于0时,代表水平翻转,即沿Y轴翻转;等于0时,代表垂直翻转,即沿X轴翻转;小于0时,代表对角翻转,即沿X和Y轴一起翻转。

二值化


enum
{
    CV_THRESH_BINARY      =0,  /* value = value > threshold ? max_value : 0       */
 
    CV_THRESH_BINARY_INV  =1,  /* value = value > threshold ? 0 : max_value       */
 
    CV_THRESH_TRUNC       =2,  /* value = value > threshold ? threshold : value   */
 
    CV_THRESH_TOZERO      =3,  /* value = value > threshold ? value : 0           */
 
    CV_THRESH_TOZERO_INV  =4,  /* value = value > threshold ? 0 : value           */
 
    CV_THRESH_OTSU        =8  /* use Otsu algorithm to choose the optimal threshold value; combine the flag with one of the above CV_THRESH_* values */
}
cv::threshold(imag, result, 30, 255, CV_THRESH_BINARY);

当图像的直方图具有明显单峰特征时,二值化分割阈值选取时可以考虑以下方法
在这里插入图片描述

// 单峰三角阈值法
cv::Mat Thresh_Unimodal(cv::Mat &src, int& idx)
{
	cv::Mat result = cv::Mat::zeros(src.size(), CV_8UC1);
 
	// 统计直方图
	cv::Mat hist = cv::Mat::zeros(1, 256, CV_32FC1);
	for (int i = 0; i < src.rows; ++i)
	{
		for (int j = 0; j < src.cols; ++j)
		{
			hist.at<float>(0, src.at<uchar>(i, j))++;
		}
	}
	hist.at<float>(0, 255) = 0;
	hist.at<float>(0, 0) = 0;
	// 搜索最大值位置
	float max = 0;
	int maxidx = 0;
	for (int i = 0; i < 256; ++i)
	{
		if (hist.at<float>(0, i) > max)
		{
			max = hist.at<float>(0, i);
			maxidx = i;
		}
	}
	// 判断最大点在哪一侧,true为左侧,false为右侧
	bool lr = maxidx < 127;
 
	float maxd = 0;
	int maxdidx = 0;
	// 假设在左侧
	if (lr)
	{
		float A = float(-max);
		float B = float(maxidx - 255);
		float C = float(max * 255);
 
		for (int i = maxidx + 1; i < 256; ++i)
		{
			float x0 = float(i);
			float y0 = hist.at<float>(0, i);
			float d = abs(A * x0 + B * y0 + C) / std::sqrt(A * A + B * B);
			if (d > maxd)
			{
				maxd = d;
				maxdidx = i;
			}
		}
	}
	// 假设在右侧
	else {
		float A = float(-max);
		float B = float(maxidx);
		float C = 0.0f;
 
		for (int i = 0; i < maxidx; ++i)
		{
			float x0 = float(i);
			float y0 = hist.at<float>(0, i);
			float d = abs(A * x0 + B * y0 + C) / std::sqrt(A * A + B * B);
			if (d > maxd)
			{
				maxd = d;
				maxdidx = i;
			}
		}
	}
 
	// 二值化
	result.setTo(255, src > maxdidx);
	idx = maxdidx;
	return result;
}

两张图片相减

cv::subtract(backimg, img_mat, sub_img);

画图

  • 直线
cv::line	(	InputOutputArray 	img,
Point 	pt1,
Point 	pt2,
const Scalar & 	color,
int 	thickness = 1,
int 	lineType = LINE_8,
int 	shift = 0 
)
  • 箭头
void cv::arrowedLine	(	InputOutputArray 	img,
Point 	pt1,
Point 	pt2,
const Scalar & 	color,
int 	thickness = 1,
int 	line_type = 8,
int 	shift = 0,
double 	tipLength = 0.1 
)	
void cv::circle	(	InputOutputArray 	img,
Point 	center,
int 	radius,
const Scalar & 	color,
int 	thickness = 1,
int 	lineType = LINE_8,
int 	shift = 0 
)	
  • 矩形
void cv::rectangle	(	InputOutputArray 	img,
Point 	pt1,
Point 	pt2,
const Scalar & 	color,
int 	thickness = 1,
int 	lineType = LINE_8,
int 	shift = 0 
)	
  • 多边形
void cv::polylines	(	Mat & 	img,
const Point *const * 	pts,
const int * 	npts,
int 	ncontours,
bool 	isClosed,
const Scalar & 	color,
int 	thickness = 1,
int 	lineType = LINE_8,
int 	shift = 0 
)	
  • 椭圆
void cv::ellipse	(	InputOutputArray 	img,
Point 	center,
Size 	axes,
double 	angle,
double 	startAngle,
double 	endAngle,
const Scalar & 	color,
int 	thickness = 1,
int 	lineType = LINE_8,
int 	shift = 0 
)	
  • 标记
void cv::drawMarker	(	Mat & 	img,
Point 	position,
const Scalar & 	color,
int 	markerType = MARKER_CROSS,
int 	markerSize = 20,
int 	thickness = 1,
int 	line_type = 8 
)	
  • 文字
void cv::putText	(	InputOutputArray 	img,
const String & 	text,
Point 	org,
int 	fontFace,
double 	fontScale,
Scalar 	color,
int 	thickness = 1,
int 	lineType = LINE_8,
bool 	bottomLeftOrigin = false 
)	

绘制多边形

void fillConvexPoly(InputOutputArray img, InputArray points,
                    const Scalar& color, int lineType = LINE_8,
                    int shift = 0);
void fillPoly(InputOutputArray img, InputArrayOfArrays pts,
              const Scalar& color, int lineType = LINE_8, int shift = 0,
              Point offset = Point() );
  • InputOutputArray img,输入图像也是输出图像,如Mat类型。
  • InputArrayOfArrays pts,存放多个多边形的顶点集合;InputArray类型的points,存放单个凸多边形的顶点集合。
  • Scalar color,文字颜色。
  • int line_type,绘制线的类型,-1就是FILLED(填满),4是LINE_4(4连通域),8是LINE_8(8连通域),LINE_AA(抗锯齿线)。
  • int shift,顶点坐标中的小数位数。
  • Point offset,轮廓的所有点的可选偏移量。
// 绘制多边形集合
fillPoly(result, pic, Scalar(0, 0, 255), 16, 0);
// 绘制单个凸多边形
fillConvexPoly(result2, points2, Scalar(0, 0, 255), 16, 0);

注意fillPoly在绘制多个多边形时,如果某两个多边形有交叉,则该交叉区域便取消填充,并保持原样,可以想象成将一个flag从false设为true,又设为false。
除此之外,两个多边形绘制函数的原理有所差异。区别除了一个是绘制单个多边形,另一个可以绘制多个多边形;
还有个更关键的差异,fillConvexPoly绘制的是凸多边形,而fillPoly可以绘制任意多边形

绘制标记符

void drawMarker(InputOutputArray img, Point position, const Scalar& color,
                int markerType = MARKER_CROSS, int markerSize=20, int thickness=1,
                int line_type=8);
  • InputOutputArray img,输入图像也是输出图像,如Mat类型。
  • Point position,绘制图形中心点。
  • Scalar color,文字颜色。
  • int markerType,标记符类型。
  • int markerSize,标记符尺寸。
  • int thickness,标记符线条宽度。
  • int line_type,绘制线的类型,-1就是FILLED(填满),4是LINE_4(4连通域),8是LINE_8(8连通域),LINE_AA(抗锯齿线)。
drawMarker(result, Point(src.cols / 2, src.rows / 2), Scalar(0, 0, 255), MARKER_TILTED_CROSS, 200, 5, 16);

ARKER_CROSS为十字,MARKER_TILTED_CROSS为叉,MARKER_STAR为星星,MARKER_DIAMOND为菱形,MARKER_SQUARE为正方形,MARKER_TRIANGLE_UP为正三角,MARKER_TRIANGLE_DOWN为倒三角。

最值计算

void minMaxIdx(InputArray src, double* minVal, double* maxVal = 0,
               int* minIdx = 0, int* maxIdx = 0, InputArray mask = noArray());
  • InputArray src,输入图像,如Mat类型。
  • double* minVal,最小值。
  • double* maxVal,最大值。
  • int* minIdx,最小值所在位置的索引
  • int* maxIdx,最大值所在位置的索引
  • InputArray mask,需要计算最值的范围
void minMaxLoc(InputArray src, CV_OUT double* minVal,
               CV_OUT double* maxVal = 0, CV_OUT Point* minLoc = 0,
               CV_OUT Point* maxLoc = 0, InputArray mask = noArray());
  • InputArray src,输入图像,如Mat类型。
  • double* minVal,最小值。
  • double* maxVal,最大值。
  • Point* minLoc,最小值所在位置的索引
  • Point* maxLoc,最大值所在位置的索引
  • InputArray mask,需要计算最值的范围

矩阵归一化

void normalize( InputArray src, OutputArray dst, double alpha = 1, double beta = 0,
                int norm_type = NORM_L2, int dtype = -1, InputArray mask = noArray());
  • InputArray src,输入图像,如Mat类型。
  • OutputArray dst,输出图像。
  • double alpha,归一化相关的数值。
  • double beta,归一化相关的数值。
  • int norm_type,归一化类型。
  • int dtype,默认值-1,与输出矩阵的类型和通道相关。
  • InputArray mask,掩膜。

针对第三个参数alpha和第四个参数beta,在不同归一化类型时,作用不一样:

  • NORM_MINMAX :alpha和beta的最大值是归一化的最大值,两者的最小值是归一化的最小值,alpha为1,beta为0,同alpha为0,beta为1,是一致的。
  • NORM_INF:beta值无用;原始矩阵数值除以矩阵最大值的结果,alpha可以控制倍数。
  • NORM_L1:beta值无用;原始矩阵数值除以矩阵数据绝对值和的结果,alpha可以控制倍数。
  • NORM_L2:beta值无用;原始矩阵数值除以矩阵数据平方和再开根号的结果,alpha可以控制倍数。

灰度直方图

const int channels[] = { 0 };
cv::Mat hist;//定义输出Mat类型
int dims = 1;//设置直方图维度
const int histSize[] = { 256 }; //直方图每一个维度划分的柱条的数目
//每一个维度取值范围
float pranges[] = { 0, 255 };//取值区间
const float* ranges[] = { pranges };
calcHist(&sub_img, 1, channels, cv::Mat(), hist, dims, histSize, ranges, true, false);//计算直方图

// 直方图绘制方法一
int scale = 2;
int hist_height = 256;
cv::Mat hist_img = cv::Mat::zeros(hist_height, 256 * scale, CV_8UC3); //创建一个黑底的8位的3通道图像,高256,宽256*2
double max_val;
minMaxLoc(hist, 0, &max_val, 0, 0);//计算直方图的最大像素值
//将像素的个数整合到 图像的最大范围内
//遍历直方图得到的数据
for (int i = 0; i < 256; i++)
{
    float bin_val = hist.at<float>(i);   //遍历hist元素(注意hist中是float类型)
    int intensity = cvRound(bin_val*hist_height / max_val);  //绘制高度
    rectangle(hist_img, cv::Point(i*scale, hist_height - 1), cv::Point((i + 1)*scale - 1, hist_height - intensity), cv::Scalar(255, 255, 255));//绘制直方图
}

// 直方图绘制方法二
int hist_h = 300;//直方图的图像的高
int hist_w = 512; //直方图的图像的宽
int bin_w = hist_w / histSize[0];//直方图的等级
cv::Mat histImage(hist_h, hist_w, CV_8UC3, cv::Scalar(0, 0, 0));//绘制直方图显示的图像

//绘制并显示直方图
normalize(hist, hist, 0, hist_h, cv::NORM_MINMAX, -1, cv::Mat());//归一化直方图
for (int i = 1; i < histSize[0]; i++)
{
    cv::line(histImage, cv::Point((i - 1) * bin_w, hist_h - cvRound(hist.at<float>(i - 1))),
         cv::Point((i)*bin_w, hist_h - cvRound(hist.at<float>(i))), cv::Scalar(255, 255, 255), 2, 8, 0);
}

直方图均衡化

通过图像数据的直方图,可以快速判断图像的亮度和质量。
直方图均衡化就是通过图像变换使得直方图均匀分布,起到对比度增强的效果。针对离散形式的图像数据,最常用的一种方法就是累计概率分布。首先统计0-255灰度值所占像素个数;再计算出像素个数与总像素的比,表示为出现的概率;从0开始进行累计概率分布,即从0慢慢累加各层概率值直到1;则均衡化图像的灰度值=原灰度值所对应的累计概率*255。

equalizeHist(sub_img, equal_gray);

通道分离与合并

OpenCV是BGR色彩空间,第一个通道是蓝色通道Blur,第二个通道是绿色通道Green,第三个通道是红色通道Red

分离

void split(InputArray m, OutputArrayOfArrays mv);
  • InputArray m,输入的需要分离通道的图像。
  • OutputArrayOfArrays mv,输出的vector容器,装载不同通道的图像信息。
cv::split(src, channels);

合并

void merge(const Mat* mv, size_t count, OutputArray dst);
void merge(InputArrayOfArrays mv, OutputArray dst);
  • mv,输入合并的图像阵列,一般用vector。
  • count,代表需要合并的矩阵个数
  • dst,输出矩阵。
cv::merge(channels, result);

调整图片亮度和对比度

  • 直方图均衡化增强
int img_equalize(cv::Mat &src_img){
    Mat imageRGB[3];
    split(src_img, imageRGB);
    for (int i = 0; i < 3; i++){
        equalizeHist(imageRGB[i], imageRGB[i]);
    }
    merge(imageRGB, 3, src_img);
}
  • 线性增强
cv::Mat img_enhance(cv::Mat ori_img){
    int height = ori_img.rows;
    int width = ori_img.cols;
    cv::Mat dst = cv::Mat::zeros(ori_img.size(), ori_img.type());
    float alpha = 3;
    float beta = 1.5;
    for (int row = 0; row < height; row++) {
        for (int col = 0; col < width; col++) {
            int b = ori_img.at<cv::Vec3b>(row, col)[0];
            int g = ori_img.at<cv::Vec3b>(row, col)[1];
            int r = ori_img.at<cv::Vec3b>(row, col)[2];

            dst.at<cv::Vec3b>(row, col)[0] = cv::saturate_cast<uchar>(b * alpha + beta);
            dst.at<cv::Vec3b>(row, col)[1] = cv::saturate_cast<uchar>(g * alpha + beta);
            dst.at<cv::Vec3b>(row, col)[2] = cv::saturate_cast<uchar>(r * alpha + beta);
        }
    }
    normalize(dst, dst, 0, 255, NORM_MINMAX);
    convertScaleAbs(dst, dst);
    return dst;
}
  • 对数Log变换增强
cv::Mat img_enhance_log(cv::Mat ori_img){
    int height = ori_img.rows;
    int width = ori_img.cols;
    cv::Mat dst = cv::Mat::zeros(ori_img.size(), ori_img.type());
    for (int row = 0; row < height; row++) {
        for (int col = 0; col < width; col++) {
            dst.at<cv::Vec3b>(row, col)[0] = log(1 + ori_img.at<cv::Vec3b>(row, col)[0]);
            dst.at<cv::Vec3b>(row, col)[1] = log(1 + ori_img.at<cv::Vec3b>(row, col)[1]);
            dst.at<cv::Vec3b>(row, col)[2] = log(1 + ori_img.at<cv::Vec3b>(row, col)[2]);
        }
    }
    normalize(dst, dst, 0, 255, NORM_MINMAX);
    convertScaleAbs(dst, dst);
    return dst;
}
  • gamma变换增强
cv::Mat img_enhance_gamma(cv::Mat ori_img){
    Mat imageGamma(ori_img.size(), CV_32FC3);
    for (int i = 0; i < ori_img.rows; i++)
    {
        for (int j = 0; j < ori_img.cols; j++)
        {
            imageGamma.at<Vec3f>(i, j)[0] = (ori_img.at<Vec3b>(i, j)[0])*(ori_img.at<Vec3b>(i, j)[0])*(ori_img.at<Vec3b>(i, j)[0]);
            imageGamma.at<Vec3f>(i, j)[1] = (ori_img.at<Vec3b>(i, j)[1])*(ori_img.at<Vec3b>(i, j)[1])*(ori_img.at<Vec3b>(i, j)[1]);
            imageGamma.at<Vec3f>(i, j)[2] = (ori_img.at<Vec3b>(i, j)[2])*(ori_img.at<Vec3b>(i, j)[2])*(ori_img.at<Vec3b>(i, j)[2]);
        }
    }
    //归一化到0~255
    normalize(imageGamma, imageGamma, 0, 255, NORM_MINMAX);
    //转换成8bit图像显示
    convertScaleAbs(imageGamma, imageGamma);
    return imageGamma;
}
  • 拉普拉斯增强
int img_laplace(cv::Mat &ori_img){
    cv::Mat kernel = (Mat_<float>(3, 3) << 0, -1, 0, 0, 5, 0, 0, -1, 0);
    filter2D(ori_img, ori_img, CV_8UC3, kernel);
}

canny边缘检测

Canny(grayimg, img_canny, 150, 100, 3);

腐蚀,膨胀,开运算,闭运算,黑帽运算,顶帽运算,形态学梯度计算

void morphologyEx( InputArray src, OutputArray dst,
                   int op, InputArray kernel,
                   Point anchor = Point(-1,-1), int iterations = 1,
                   int borderType = BORDER_CONSTANT,
                   const Scalar& borderValue = morphologyDefaultBorderValue() );
  • InputArray src,输入图像,如Mat类型。
  • OutputArray dst,输出图像。
  • int op,选择不同的运算操作,黑帽运算则是MORPH_BLACKHAT。
  • Point anchor,锚点。默认值(-1,-1),表示位于单位中心,一般不用。
  • int iterations,迭代使用的次数,默认值为1。
  • int borderType,推断图像外部像素的边界模式,默认值为BORDER_CONSTANT。如果图像边界需要扩展,则不同的模式下所扩展的像素,其生成原则不同。
  • const Scalar& borderValue,当边界为常数时的边界值,默认值为morphologyDefaultBorderValue()。
cv::Mat kernel = getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3));//创建结构元
cv::erode(sub_img, erode_mat, kernel);

cv::dilate(sub_img, dilate_mat, kernel);

// 形态学处理--开运算
morphologyEx(sub_img, morph_open_mat, cv::MORPH_OPEN, kernel, cv::Point(-1, -1));

// 形态学处理--闭运算
morphologyEx(sub_img, morph_close_mat, cv::MORPH_CLOSE, kernel, cv::Point(-1, -1));    

// 黑帽运算就是将闭运算后的图像减去原图,突出了比原图轮廓周围区域更暗的区域
cv::morphologyEx(test, result, MORPH_BLACKHAT, kernel);

// 顶帽运算就是将原图减去开运算后的图像,放大了裂痕或局部低亮度区域
cv::morphologyEx(test, result, MORPH_TOPHAT, kernel);

// 形态学梯度计算就是将膨胀后的图像减去腐蚀后的图像,可以有效提取边缘轮廓信息
cv::morphologyEx(test, result, MORPH_GRADIENT, kernel);

图像锐化

图像锐化(image sharpening)是补偿图像的轮廓,增强图像的边缘及灰度跳变的部分,使图像变得清晰,分为空间域处理和频域处理两类。图像锐化是为了突出图像上地物的边缘、轮廓,或某些线性目标要素的特征。这种滤波方法提高了地物边缘与周围像元之间的反差,因此也被称为边缘增强。

cv::Mat sharp = (cv::Mat_<int>(3, 3) << 1, 1, 1, 1, -8, 1, 1, 1, 1);
cv::Mat result;
filter2D(morph_open_mat, result, -1, sharp, cv::Point(-1, -1), 0, cv::BORDER_DEFAULT);
convertScaleAbs(result, result);

mask掩码图像

Mat roi = Mat::zeros(Size(scale_width, scale_height), CV_8UC1);
std::vector<std::vector<Point>> contour;
std::vector<Point> pts;
pts.emplace_back(Point(min_x, min_y));
pts.emplace_back(Point(min_x, max_y));
pts.emplace_back(Point(max_x, max_y));
pts.emplace_back(Point(max_x, min_y));
contour.push_back(pts);
drawContours(roi, contour, 0, Scalar::all(255), -1);
rgbImg1.copyTo(dstImg1, roi);

轮廓周长,面积

double arcLength( InputArray curve, bool closed );
  • InputArray curve,输入的向量,二维点(轮廓顶点),可以为std::vector或Mat类型。
  • bool closed,用于指示曲线是否封闭的标识符,一般设置为true。
double contourArea( InputArray contour, bool oriented = false );
  • InputArray contour,输入的向量,二维点(轮廓顶点),可以为std::vector或Mat类型。
  • bool oriented,面向区域标识符。若其为true,会返回一个带符号的面积值,正负取决于轮廓的方向。

查找连通域

findContours( InputOutputArray image, OutputArrayOfArrays contours,
                              OutputArray hierarchy, int mode,
                              int method, Point offset=Point());
  1. InputOutputArray image:单通道图像矩阵,可以是灰度图,但更常用的是二值图像,一般是经过Canny、拉普拉斯等边缘检测算子处理过的二值图像;
  2. OutputArrayOfArrays contours:定义为“vector<vector> contours”,是一个向量,并且是一个双重向量,向量内每个元素保存了一组由连续的Point点构成的点的集合的向量,每一组Point点集就是一个轮廓。有多少轮廓,向量contours就有多少元素。
  3. OutputArray hierarchy:定义为“vector hierarchy”,Vec4i的定义:typedef Vec<int, 4> Vec4i;
    Vec4i是Vec<int,4>的别名,定义了一个“向量内每一个元素包含了4个int型变量”的向量。所以从定义上看,hierarchy也是一个向量,向量内每个元素保存了一个包含4个int整型的数组。向量hiararchy内的元素和轮廓向量contours内的元素是一一对应的,向量的容量相同。hierarchy向量内每一个元素的4个int型变量——hierarchy[i][0] ~hierarchy[i][3],分别表示第i个轮廓的后一个轮廓、前一个轮廓、父轮廓、内嵌轮廓的索引编号。如果当前轮廓没有对应的后一个轮廓、前一个轮廓、父轮廓或内嵌轮廓的话,则hierarchy[i][0] ~hierarchy[i][3]的相应位被设置为默认值-1。
  4. int mode:定义轮廓的检索模式:
    取值一:CV_RETR_EXTERNAL只检测最外围轮廓,包含在外围轮廓内的内围轮廓被忽略;
    取值二:CV_RETR_LIST 检测所有的轮廓,包括内围、外围轮廓,但是检测到的轮廓不建立等级关系,彼此之间独立,没有等级关系,这就意味着这个检索模式下不存在父轮廓或内嵌轮廓,所以hierarchy向量内所有元素的第3、第4个分量都会被置为-1;
    取值三:CV_RETR_CCOMP 检测所有的轮廓,但所有轮廓只建立两个等级关系,外围为顶层,若外围内的内围轮廓还包含了其他的轮廓信息,则内围内的所有轮廓均归属于顶层;
    取值四:CV_RETR_TREE, 检测所有轮廓,所有轮廓建立一个等级树结构。外层轮廓包含内层轮廓,内层轮廓还可以继续包含内嵌轮廓。
  5. int method:定义轮廓的近似方法
    取值一:CV_CHAIN_APPROX_NONE 保存物体边界上所有连续的轮廓点到contours向量内;
    取值二:CV_CHAIN_APPROX_SIMPLE 仅保存轮廓的拐点信息,把所有轮廓拐点处的点保存入contours向量内,拐点与拐点之间直线段上的信息点不予保留;
    取值三和四:CV_CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS使用teh-Chinl chain 近似算法
  6. Point offset:Point偏移量,所有的轮廓信息相对于原始图像对应点的偏移量,相当于在每一个检测出的轮廓点上加上该偏移量,并且Point还可以是负值!
vector<vector<cv::Point>> contours;
cv::findContours(sub_img, contours, cv::RETR_CCOMP, cv::CHAIN_APPROX_SIMPLE);
for (int i = 0; i < contours.size(); i++) {
    if(cv::contourArea(contours[i]) < 100) continue;
    cv::Scalar color(255, 255, 255);
    drawContours(sub_img, contours, i, color, 2);
}

cv::Mat labels, stats, centroids;
int num = connectedComponentsWithStats(sub_img, labels, stats, centroids);
vector<cv::Vec3b> color(num + 1);
color[0] = cv::Vec3b(0, 0, 0);//背景色
for (int m = 1; m <= num; m++) {
    color[m] = cv::Vec3b(rand() % 256, rand() % 256, rand() % 256);
    if (stats.at<int>(m - 1, cv::CC_STAT_AREA) < 100)
        color[m] = cv::Vec3b(0, 0, 0);
}
cv::Mat src_color = cv::Mat::zeros(sub_img.size(), CV_8UC3);
for (int x = 0; x < sub_img.rows; x++)
    for (int y = 0; y < sub_img.cols; y++)
    {
        int label = labels.at<int>(x, y);//注意labels是int型,不是uchar.
        src_color.at<cv::Vec3b>(x, y) = color[label];
    }
imshow("labelMap", src_color);

滤波

blur(sub_img, sub_img, cv::Size(3, 3));
medianBlur(sub_img, sub_img, 3);

提取像素值

// 灰度
int pv = dstImg1.at<uchar>(int(keypoints_img1[matches[i].queryIdx].pt.y), int(keypoints_img1[matches[i].queryIdx].pt.x));
// RGB图
Vec3b bgr = dstImg1.at<Vec3b>(int(keypoints_img1[matches[i].queryIdx].pt.y), int(keypoints_img1[matches[i].queryIdx].pt.x));

图像添加水印

水印图:
请添加图片描述

底图:
请添加图片描述
将水印加到图片左上角,首先在左上角划定一个和logo图片一样大小的ROI区域出来,然后将logo添加到ROI区域里

#include <iostream>
#include <fstream>
#include <opencv2/opencv.hpp>

using namespace std;

int main() {
    cv::Mat logo = cv::imread("../logo.png");
    stringstream imgname;
    imgname << "../000001.jpg";
    cv::Mat ori_img = cv::imread(imgname.str());
    cv::Mat imgROI = ori_img(cv::Rect(20, 20, logo.cols, logo.rows));
    logo.copyTo(imgROI);
}

结果图:
在这里插入图片描述

void bitwise_and(InputArray src1, InputArray src2,OutputArray dst, InputArray mask=noArray());//dst = src1 & src2
void bitwise_or(InputArray src1, InputArray src2,OutputArray dst, InputArray mask=noArray());//dst = src1 | src2
void bitwise_not(InputArray src, OutputArray dst,InputArray mask=noArray());//dst = ~src
void bitwise_xor(InputArray src1, InputArray src2,OutputArray dst, InputArray mask=noArray());//dst = src1 ^ src2
  • bitwise_and:对二进制数据进行“与”操作,即对图像(灰度图像或彩色图像均可)每个像素值进行二进制“与”操作,1&1=1,1&0=0,0&1=0,0&0=0
  • bitwise_or:对图像(灰度图像或彩色图像均可)每个像素值进行二进制“或”操作,1|1=1,1|0=1,0|1=1,0|0=0
  • bitwise_xor:对图像(灰度图像或彩色图像均可)每个像素值进行二进制“异或”操作,1 ^ 1=0,1 ^ 0=1,0 ^ 1=1,0 ^ 0=0
  • bitwise_not:对图像(灰度图像或彩色图像均可)每个像素值进行二进制“非”操作,~1=0, ~ 0=1

将四个字抠出来做水印,需要转灰度,然后阈值化操作

#include <iostream>
#include <fstream>
#include <opencv2/opencv.hpp>

using namespace std;

int main() {
    cv::Mat logo = cv::imread("../logo.png");
    stringstream imgname;
    imgname << "../000001.jpg";
    cv::Mat ori_img = cv::imread(imgname.str());
	// 先定义一个掩膜Mask(黑色区域被忽略,仅剩下白色区域),然后将logo图像转为灰度图像存入到Mask中
    cv::Mat mask;
    cv::cvtColor(logo, mask, cv::COLOR_BGR2GRAY);
	// 对掩膜Mask进行取反操作
    bitwise_not(mask, mask);
    // 取反后的图进行阈值化操作
    cv::threshold(mask, mask, 10, 255, cv::THRESH_BINARY);

    cv::Mat imgROI = ori_img(cv::Rect(20, 20, logo.cols, logo.rows));
    // 将logo拷贝到imgROI上,掩码为不为0的部分起作用,为0的部分不起作用
    logo.copyTo(imgROI, mask);

    cv::imwrite("../img.jpg", ori_img);
    cv::waitKey(0);
}

结果图:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

请添加图片描述

霍夫直线检测

cv::HoughLinesP(
InputArray src, // 输入图像(8位灰度图像)
OutputArray lines, // 输出直线两点坐标(vector)
double rho, // 生成极坐标时候的像素扫描步长
double theta, //生成极坐标时候的角度步长(一般取CV_PI/180)
int threshold, // 累加器阈值,获得足够交点的极坐标点才被看成是直线
double minLineLength=0;// 直线最小长度
double maxLineGap=0;// 直线最大间隔
)

int main()
{
    Mat img = imread("D:\\pdf2jpg\\nn\\00010.jpg");

    //GetRedComponet(img);
    Mat contours,res;
    Canny(img,contours,125,350);
    cvtColor( contours, res, CV_GRAY2BGR );  
    vector<Vec4i> lines;  
    HoughLinesP(contours, lines, 1, CV_PI/180, 80, 30, 10 );  
    for( size_t i = 0; i < lines.size(); i++ )  
    {  
        Vec4i l = lines[i];  
        line(res, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(0,0,255), 2);  
    }  
    imwrite("D:\\dst1.jpg",res);
}

曲线拟合

    int cv::solve(
        cv::InputArray X, // 左边矩阵X, nxn
        cv::InputArray Y, // 右边矩阵Y,nx1
        cv::OutputArray A, // 结果,系数矩阵A,nx1
        int method = cv::DECOMP_LU // 估算方法
    );
void PolyFit(std::vector<cv::Point> &points, const int order,
             cv::Mat &coeff) {
    const int n = points.size();
    cv::Mat A = cv::Mat::zeros(order + 1, order + 1, CV_64FC1);
    cv::Mat B = cv::Mat::zeros(order + 1, 1, CV_64FC1);

    // 构建A矩阵
    for (int i = 0; i < order + 1; ++i) {
        for (int j = 0; j < order + 1; ++j) {
            for (int k = 0; k < n; ++k) {
//                A.at<double>(i, j) += std::pow(points.at(k).x, i + j);
                A.at<double>(i, j) += std::pow(points.at(k).y, i + j);
            }
        }
    }

    // 构建B矩阵
    for (int i = 0; i < order + 1; ++i) {
        for (int k = 0; k < n; ++k) {
//            B.at<double>(i, 0) += std::pow(points.at(k).x, i) * points.at(k).y;
            B.at<double>(i, 0) += std::pow(points.at(k).y, i) * points.at(k).x;
        }
    }

    coeff = cv::Mat::zeros(order + 1, 1, CV_64FC1);
    clock_t start = clock();
    // 求解
    if (!cv::solve(A, B, coeff, cv::DECOMP_LU)) {
        std::cout << "Failed to solve !" << std::endl;
    }
    clock_t end = clock();
    cout << "solve consume: " << (double)(end - start) / CLOCKS_PER_SEC * 1000 << endl;
    cout << "coeff: " << coeff << endl;
}

多边形拟合曲线

void approxPolyDP(InputArray curve, OutputArray approxCurve, double epsilon, bool closed)
  • InputArray curve:输入的点集
  • OutputArray approxCurve:输出的点集,当前点集是能最小包容指定点集的。画出来即是一个多边形
  • double epsilon:指定的精度,也即是原始曲线与近似曲线之间的最大距离
  • bool closed:若为true,则说明近似曲线是闭合的;反之,若为false,则断开
approxPolyDP(contourMat, approxCurve, 10, true);

光流

  • 稀疏光流
 calcOpticalFlowPyrLK( InputArray prevImg, InputArray nextImg,
                                        InputArray prevPts, InputOutputArray nextPts,
                                        OutputArray status, OutputArray err,
                                        Size winSize = Size(21,21), int maxLevel = 3,
                                        TermCriteria criteria = TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 30, 0.01),
                                        int flags = 0, double minEigThreshold = 1e-4 );
  • prevImg: 第一帧(跟踪图像的前一帧,一般是定位特征点)
  • nextImg: 第二帧/当前帧
  • prev_Pts: 第一帧特征点集,点坐标必须是单精度浮点数
  • next_Pts: 计算输出的第二帧光流特征点集
  • status : 状态标志位,如果对应特征的光流被发现,数组中的每一个元素都被设置为 1, 否则设置为 0
  • err:双精度数组,包含原始图像碎片与移动点之间的误差
  • winSize:在每个金字塔水平搜寻窗口的尺寸
  • maxLevel:最大金字塔层数; 如果设置为0,则不使用金字塔(单层),如果设置为1,则使用两个层次,依此类推; 如果将金字塔传递给输入,则算法将使用与金字塔一样多的级别,但不超过maxLevel。
  • criteria:指定迭代搜索算法的终止标准(指定的最大迭代次数criteria.maxCount或搜索窗口移动小于criteria.epsilon)
  • flags:操作标志
  • minEigThreshold:计算光流方程的2×2标准矩阵的最小特征值除以窗口中的像素数量;如果这个值小于minEigThreshold,那么一个相应的特征被过滤出来,且它的光流不被处理,所以它允许去除坏点提升性能。
// lucas_kanade.cpp
#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/videoio.hpp>
#include <opencv2/video.hpp>
#include <opencv2/optflow.hpp>
#include <sys/stat.h>
using namespace cv;
using namespace std;

int lucas_kanade(const string& filename, bool save)
{
    VideoCapture capture(filename);
    if (!capture.isOpened()){
        //打开视频输入错误
        cerr << "Unable to open file!" << endl;
        return 0;
    }
    // 创建一些随机的颜色
    vector<Scalar> colors;
    RNG rng;
    for(int i = 0; i < 100; i++)
    {
        int r = rng.uniform(0, 256);
        int g = rng.uniform(0, 256);
        int b = rng.uniform(0, 256);
        colors.push_back(Scalar(r,g,b));
    }
    Mat old_frame, old_gray;
    vector<Point2f> p0, p1;
    // 取第一帧并在其中找到角点
    capture >> old_frame;
    cvtColor(old_frame, old_gray, COLOR_BGR2GRAY);
    goodFeaturesToTrack(old_gray, p0, 100, 0.3, 7, Mat(), 7, false, 0.04);
    // 创建用于绘图的掩模图像
    Mat mask = Mat::zeros(old_frame.size(), old_frame.type());
    int counter = 0;
    while(true){
        Mat frame, frame_gray;
        capture >> frame;
        if (frame.empty())
            break;
        cvtColor(frame, frame_gray, COLOR_BGR2GRAY);
        // 计算光流
        vector<uchar> status;
        vector<float> err;
        TermCriteria criteria = TermCriteria((TermCriteria::COUNT) + (TermCriteria::EPS), 10, 0.03);
        calcOpticalFlowPyrLK(old_gray, frame_gray, p0, p1, status, err, Size(15,15), 2, criteria);
        vector<Point2f> good_new;
        for(uint i = 0; i < p0.size(); i++)
        {
            // 选择比较好的点
            if(status[i] == 1) {
                good_new.push_back(p1[i]);
                // 画出轨迹
                line(mask,p1[i], p0[i], colors[i], 2);
                circle(frame, p1[i], 5, colors[i], -1);
            }
        }
        Mat img;
        add(frame, mask, img);
        if (save) {
            string save_path = "./optical_flow_frames/frame_" + to_string(counter) + ".jpg";
            imwrite(save_path, img);
        }
        imshow("flow", img);
        int keyboard = waitKey(25);
        if (keyboard == 'q' || keyboard == 27)
            break;
        // 创建用于绘图的掩模图像
        old_gray = frame_gray.clone();
        p0 = good_new;
        counter++;
    }
}

  • 稠密光流
void cv::calcOpticalFlowFarneback( InputArray _prev0, InputArray _next0,
                               OutputArray _flow0, double pyr_scale, int levels, int winsize,
                               int iterations, int poly_n, double poly_sigma, int flags )
  • _prev0:输入前一帧8-bit单通道图像
  • _next0:输入后一帧图像,与前一帧保持同样的格式、尺寸
  • _flow0:输出的光流
  • pyr_scale:金字塔上下两层之间的尺度关系
  • levels:金字塔层数
  • winsize:均值窗口大小,越大越能denoise并且能够检测快速移动目标,但会引起模糊运动区域
  • iterations:迭代次数
  • poly_n:像素领域大小,一般为5,7等
  • poly_sigma:高斯标注差,一般为1-1.5
  • flags:计算方法。主要包括OPTFLOW_USE_INITIAL_FLOW和OPTFLOW_FARNEBACK_GAUSSIAN
#include "stdafx.h"
#include "opencv2/video/tracking.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
 
#include <iostream>
 
using namespace cv;
using namespace std;
 
static void drawOptFlowMap(const Mat& flow, Mat& cflowmap, int step,
	double, const Scalar& color)
{
	for(int y = 0; y < cflowmap.rows; y += step)
		for(int x = 0; x < cflowmap.cols; x += step)
		{
			const Point2f& fxy = flow.at<Point2f>(y, x);
			line(cflowmap, Point(x,y), Point(cvRound(x+fxy.x), cvRound(y+fxy.y)),
				color);
			circle(cflowmap, Point(x,y), 2, color, -1);
		}
}
 
int main(int, char**)
{
	VideoCapture cap(0);
	if( !cap.isOpened() )
		return -1;
 
	Mat prevgray, gray, flow, cflow, frame;
	namedWindow("flow", 1);
 
	for(;;)
	{
		cap >> frame;
		cvtColor(frame, gray, COLOR_BGR2GRAY);
 
		if( prevgray.data )
		{
			calcOpticalFlowFarneback(prevgray, gray, flow, 0.5, 3, 15, 3, 5, 1.2, 0);
			cvtColor(prevgray, cflow, COLOR_GRAY2BGR);
			drawOptFlowMap(flow, cflow, 16, 1.5, Scalar(0, 255, 0));
			imshow("flow", cflow);
		}
		if(waitKey(30)>=0)
			break;
		std::swap(prevgray, gray);
	}
	return 0;
}

漫水填充

int floodFill( InputOutputArray image,
               Point seedPoint, Scalar newVal, CV_OUT Rect* rect = 0,
               Scalar loDiff = Scalar(), Scalar upDiff = Scalar(),
               int flags = 4 );
int floodFill( InputOutputArray image, InputOutputArray mask,
               Point seedPoint, Scalar newVal, CV_OUT Rect* rect=0,
               Scalar loDiff = Scalar(), Scalar upDiff = Scalar(),
               int flags = 4 );
  • InputOutputArray image,输入输出图像。
  • InputOutputArray mask,掩膜区,掩膜区非零区域不被填充,且掩膜区的宽高要比输入图像各多两个像素,输入图像中(x,y)位置对应掩膜区(x+1,y+1)位置,掩膜可以为漫水填充提供方向的引导,即指挥填充的路线。
  • Point seedPoint,漫水填充的起点。
  • Scalar newVal,像素点被填充后呈现的颜色。
  • Rect* rect,漫水填充后重绘区域的最小边界矩形区域。
  • Scalar loDiff,当前观测像素与种子像素颜色负差的最大值。通俗地讲,就是观测像素各个通道的值都要比种子像素小,且小的数值要低于loDiff,满足该条件才会被填充。具体使用将在下方进行举例演示。
  • Scalar upDiff,当前观测像素与种子像素颜色正差的最大值。通俗地讲,就是观测像素各个通道的值都要比种子像素大,且大的数值要低于upDiff,满足该条件才会被填充。具体使用将在下方进行举例演示。
  • int flags,操作标识符,是32位二进制数。低八位表示算法连通性,一般取4或者8,4为上下左右,8加上对角四点;高八位可选两种标识符,分别为FLOODFILL_FIXED_RANGE和FLOODFILL_MASK_ONLY,也可以用or(|)组合它们;中八位是填充掩膜的数值,搭配FLOODFILL_MASK_ONLY使用。

通过选中和种子点相连相近的区域,将其转换为指定颜色,以达到标记或者分离图像的目的,进而完成对图像的一些分析和处理。
loDiff为Scalar(1, 1, 1),upDiff为Scalar(10, 10, 10),表示当前观测点的像素X与周围已被填充的像素点数值Y,需满足X-Y<10,且Y-X<1,才被填充。
loDiff为Scalar(10, 10, 10),upDiff为Scalar(1, 1, 1),则需满足X-Y<1,且Y-X<10,才被填充。

Mat src = imread("test.jpg");
Rect roi;
int flags = 8;
Mat mask = Mat::zeros(src.rows + 2,src.cols + 2, CV_8UC1);
mask.at<uchar>(src.rows / 2, src.cols / 2) = 255;
floodFill(src, mask, Point(src.cols / 2, src.rows / 2), Scalar(255, 0, 255), &roi, Scalar(10, 10, 10), Scalar(1, 1, 1), flags);

加减乘除,加权

void add(InputArray src1, 
        InputArray src2, 
        OutputArray dst,
        InputArray mask = noArray(), 
        int dtype = -1
);

void subtract(InputArray src1, 
        InputArray src2, 
        OutputArray dst,
        InputArray mask = noArray(), 
        int dtype = -1
);
  • InputArray src1 ,第一个输入数组或scalar。
  • InputArray src2 ,第二个输入数组或scalar。
  • OutputArray dst ,输出图像,图像的尺寸、通道数和输入图像相同。
  • InputArray mask,可选操作掩码-8位单通道数组,指定要更改的输出数组元素。
  • int dtype,输出数组的可选深度。
// 两个数组相乘,对应位置上的元素相乘,得到该位置上的值
void multiply(InputArray src1, 
        InputArray src2, 
        OutputArray dst,
        double scale = 1,
        int dtype = -1
);
  • InputArray src1 ,第一个输入数组或scalar。
  • InputArray src2 ,第二个输入数组或scalar。
  • OutputArray dst ,输出图像,图像的尺寸、通道数和输入图像相同。
  • double scale,可选比例因子。
  • int类型的dtype,输出数组的可选深度。
// 两个数组或标量按数组的每个元素的除法的功能
// 首先,除法可能是一个float数据和Mat的除,这个时候,计算的是float和Mat中每个数值的除;如果是两个Mat除,那就是对应位置做除法。
// 其次,除数Mat中可能会存在0,这个位置求出的值直接取零。
// src1 / src2 * scale
void divide(InputArray src1, 
        InputArray src2, 
        OutputArray dst,
        double scale = 1,
        int dtype = -1
);
// scale / src2
void divide(double scale, 
        InputArray src2,                         
        OutputArray dst, 
        int dtype = -1
);
  • InputArray src1 ,第一个输入数组或scalar。
  • InputArray src2 ,第二个输入数组或scalar。
  • OutputArray dst ,输出图像,图像的尺寸、通道数和输入图像相同。
  • double scale,scalar因子。
  • int dtype,控制输出图像的类型,如果src1和src2类型一致,该值就是-1;如果不一致,该值就对应图像类型,比如当值为0的时候,输出的结果就是CV_8U,值为5的时候,输出的结果就是CV_32F。
void addWeighted(InputArray src1, double alpha, InputArray src2,
                 double beta, double gamma, OutputArray dst, int dtype = -1);
  • InputArray src1,输入的第一个需要加权的图像。
  • double alpha,第一个数组的权重。
  • InputArray src2,输入的第二个需要加权的图像。
  • double beta,第二个数组的权重。
  • double gamma,加到权重总和上的标量值。
  • OutputArray dst,输出的数组。
  • int dtype,输出阵列的可选深度,默认值-1。

模板匹配

void matchTemplate( InputArray image, InputArray templ,OutputArray result, 
											int method, InputArray mask = noArray());
  • InputArray image,输入图像。
  • InputArray templ,待匹配图像。
  • OutputArray result,输出匹配结果。
  • int method,匹配方法
    CV_TM_SQDIFF,平方差匹配法。得到的数值越小,说明越匹配。
    CV_TM_SQDIFF_NORMED,归一化平方差匹配法。得到的数值越小,说明越匹配。
    CV_TM_CCORR,相关匹配法。得到的数值越大,说明越匹配。该方法容易受背景干扰。
    CV_TM_CCORR_NORMED,归一化相关匹配法。得到的数值越大,说明越匹配。该方法容易受背景干扰。
    CV_TM_CCOEFF,相关系数法,也是零均值互相关法,将均值减去后,再计算相关性。得到的数值越大,说明越匹配。该方法应用性较优。
    CV_TM_CCOEFF_NORMED,归一化相关系数法。得到的数值越大,说明越匹配。该方法应用性较优。
  • InputArray mask,掩膜。
cv::Mat src = imread("test1.jpg");
cv::Mat sample = imread("t.png");
// 匹配
cv::Mat result;
matchTemplate(src, sample, result, CV_TM_CCOEFF);
// 归一化
normalize(result, result, 0, 1, NORM_MINMAX, -1, Mat());
// 获取最小值
double minValue; double maxValue; Point minLocation; Point maxLocation;
Point matchLocation;
minMaxLoc(result, &minValue, &maxValue, &minLocation, &maxLocation, Mat());
matchLocation = maxLocation;
// 框选结果
cv::Mat draw = src.clone();
rectangle(draw, matchLocation, Point(matchLocation.x + sample.cols, matchLocation.y + sample.rows), Scalar(255, 0, 0), 2, 8, 0);

矩形边框

  • 最小外接矩形边框
cv::Rect boundingRect( InputArray array );

输入:InputArray类型的array,输入灰度图像或二维点集。
输出:Rect类型的矩形信息,包括矩形尺寸和位置。

  • 最小包围旋转矩形边框
cv::RotatedRect minAreaRect( InputArray points );

输入:InputArray类型的points,输入灰度图像或二维点集。
输出:RotatedRect类型的旋转矩形信息,即矩形四角点位置。

一个小面积孔洞闭合的函数

void Clear_MicroConnected_Areas(cv::Mat src, cv::Mat &dst, double min_area)
{
    // 备份复制
    dst = src.clone();
    std::vector<std::vector<cv::Point> > contours;  // 创建轮廓容器
    std::vector<cv::Vec4i> 	hierarchy;

    // 寻找轮廓的函数
    // 第四个参数CV_RETR_EXTERNAL,表示寻找最外围轮廓
    // 第五个参数CV_CHAIN_APPROX_NONE,表示保存物体边界上所有连续的轮廓点到contours向量内
    cv::findContours(src, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_NONE, cv::Point());

    if (!contours.empty() && !hierarchy.empty())
    {
        std::vector<std::vector<cv::Point> >::const_iterator itc = contours.begin();
        // 遍历所有轮廓
        while (itc != contours.end())
        {
            // 定位当前轮廓所在位置
            cv::Rect rect = cv::boundingRect(cv::Mat(*itc));
            // contourArea函数计算连通区面积
            double area = contourArea(*itc);
            // 若面积小于设置的阈值
            if (area < min_area)
            {
                // 遍历轮廓所在位置所有像素点
                for (int i = rect.y; i < rect.y + rect.height; i++)
                {
                    uchar *output_data = dst.ptr<uchar>(i);
                    for (int j = rect.x; j < rect.x + rect.width; j++)
                    {
                        // 将连通区的值置0
                        if (output_data[j] == 255)
                        {
                            output_data[j] = 0;
                        }
                    }
                }
            }
            itc++;
        }
    }
}

hole = 255 - mask;
Clear_MicroConnected_Areas(hole, hole, row * col / 300);
mask = 255 - hole;
Clear_MicroConnected_Areas(mask, mask, row * col / 300);

在这里插入图片描述

白平衡(完美反射算法)

白平衡的意义在于,对在特定光源下拍摄时出现的偏色现象,通过加强对应的补色来进行补偿,使白色物体能还原为白色。
完美反射算法是白平衡各种算法中较常见的一种,比灰度世界算法更优。它假设图像世界中最亮的白点是一个镜面,能完美反射光照;基于白点,将三通道的数值进行适当地调整,以达到白平衡效果;除此之外,还需要统计最亮的一定区间的三通道均值,该均值与该通道最大值的差距决定了该通道调整的力度。
实现流程如下:
1.计算图像RGB三通道各自的灰度最大值Rmax、Gmax、Bmax。
2.利用三通道数值和,确定图像最亮区间的下限T。
3.计算图像三通道数值和大于T的点的三通道均值Rm、Gm、Bm。
4.计算三通道的补偿系数,即单通道最大值除以单通道亮区平均值。

#include <iostream>
#include <opencv.hpp>

using namespace std;

// 白平衡-完美反射
cv::Mat WhiteBalcane_PRA(cv::Mat src)
{
	cv::Mat result = src.clone();
	if (src.channels() != 3)
	{
		cout << "The number of image channels is not 3." << endl;
		return result;
	}

	// 通道分离
	vector<cv::Mat> Channel;
	cv::split(src, Channel);

	// 定义参数
	int row = src.rows;
	int col = src.cols;
	int RGBSum[766] = { 0 };
	uchar maxR, maxG, maxB;

	// 计算单通道最大值
	for (int i = 0; i < row; ++i)
	{
		uchar *b = Channel[0].ptr<uchar>(i);
		uchar *g = Channel[1].ptr<uchar>(i);
		uchar *r = Channel[2].ptr<uchar>(i);
		for (int j = 0; j < col; ++j)
		{
			int sum = b[j] + g[j] + r[j];
			RGBSum[sum]++;
			maxB = max(maxB, b[j]);
			maxG = max(maxG, g[j]);
			maxR = max(maxR, r[j]);
		}
	}

	// 计算最亮区间下限T
	int T = 0;
	int num = 0;
	int K = static_cast<int>(row * col * 0.1);
	for (int i = 765; i >= 0; --i)
	{
		num += RGBSum[i];
		if (num > K)
		{
			T = i;
			break;
		}
	}
	
	// 计算单通道亮区平均值
	double Bm = 0.0, Gm = 0.0, Rm = 0.0;
	int count = 0;
	for (int i = 0; i < row; ++i)
	{
		uchar *b = Channel[0].ptr<uchar>(i);
		uchar *g = Channel[1].ptr<uchar>(i);
		uchar *r = Channel[2].ptr<uchar>(i);
		for (int j = 0; j < col; ++j)
		{
			int sum = b[j] + g[j] + r[j];
			if (sum > T)
			{
				Bm += b[j];
				Gm += g[j];
				Rm += r[j];
				count++;
			}

		}
	}
	Bm /= count;
	Gm /= count;
	Rm /= count;

	// 通道调整
	Channel[0] *= maxB / Bm;
	Channel[1] *= maxG / Gm;
	Channel[2] *= maxR / Rm;

	// 合并通道
	cv::merge(Channel, result);

	return result;
}

int main()
{
	// 载入原图
	cv::Mat src = cv::imread("test21.jpg");

	// 白平衡-完美反射
	cv::Mat result = WhiteBalcane_PRA(src);

	// 显示
	cv::imshow("src", src);
	cv::imshow("result", result);
	cv::waitKey(0);

	return 0;
}

图像金字塔

void buildPyramid( InputArray src, OutputArrayOfArrays dst,
                   int maxlevel, int borderType = BORDER_DEFAULT );
  • InputArray src,输入图像。
  • OutputArrayOfArrays dst,输出图像集合,一般为容器。
  • int maxlevel,图像金字塔层级。
  • int borderType,推断图像边缘像素的边界模式。
cv::Mat src = imread("test.jpg");
vector<cv::Mat> ths;
buildPyramid(src, ths, 3, 4);
imshow("original", src);
imshow("level 0", ths[0]);
imshow("level 1", ths[1]);
imshow("level 2", ths[2]);
imshow("level 3", ths[3]);

图像采样

// 向上采样
void pyrUp( InputArray src, OutputArray dst,
            const Size& dstsize = Size(), int borderType = BORDER_DEFAULT );
// 向下采样
void pyrDown( InputArray src, OutputArray dst,
              const Size& dstsize = Size(), int borderType = BORDER_DEFAULT );
  • InputArray src,输入图像。
  • OutputArray dst,输出图像。
  • const Size& dstsize,输出图像尺寸,一般默认即可。
  • int borderType,推断图像边缘像素的边界模式。
// 向下采样。高斯平滑+缩小尺寸
pyrDown(src, th1, Size(0, 0), 4);
// 向上采样。放大尺寸+高斯平滑
pyrUp(th1, th2, Size(0, 0), 4);

自适应阈值

void adaptiveThreshold( InputArray src, OutputArray dst,
                        double maxValue, int adaptiveMethod,
                        int thresholdType, int blockSize, double C );
  • InputArray src,输入图像。
  • OutputArray dst,输出图像。
  • double maxval,阈值最大值。
  • int adaptiveMethod,自适应阈值算法类型。0为ADAPTIVE_THRESH_MEAN_C(均值法获取阈值),1为ADAPTIVE_THRESH_GAUSSIAN_C(高斯窗加权和获取阈值)。
  • int thresholdType,阈值操作的类型,0为THRESH_BINARY(标准的二值化阈值法,大于thresh的设为maxval,小于的设为0),1为THRESH_BINARY_INV(反向二值化),2为THRESH_TRUNC(截断阈值法,大于thresh的设为thresh,小于则不变),3为THRESH_TOZERO(零化阈值法,大于thresh的不变,小于则零化),4为THRESH_TOZERO_INV(反向零化),7为THRESH_MASK(没测试出来什么用法,都是黑屏),8为THRESH_OTSU(大津算法,适合双峰直方图的图像,通过分析最大的背景前景类间方差,自动调节阈值),16为THRESH_TRIANGLE(三角法,适合单峰直方图图像,建立谷底和峰顶直线,距离直线垂直距离最大的直方图位置,即阈值thresh)。
  • int blockSize,窗口的大小,只能为奇数。
  • double C,自适应阈值算法得到平均值或加权平均值后,再减的常数值。
adaptiveThreshold(src, th1, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 7, 5);

滤镜

// 饱和度
cv::Mat Saturation(cv::Mat src, int percent);
// 明度
cv::Mat Lightness(cv::Mat src, float percent);
// 对比度
cv::Mat Contrast(cv::Mat src, int percent);
// 图像锐化
cv::Mat Sharpen(cv::Mat input, int percent, int type);
// 图像阴影选取
cv::Mat Shadow(cv::Mat input, int light);
// 图像高光选取
cv::Mat HighLight(cv::Mat input, int light);
// 色温调节
cv::Mat ColorTemperature(cv::Mat input, int percent);

cv::Mat src = imread("chocolate.jpg");
cv::Mat sat = Saturation(src, 20);
cv::Mat lig = Lightness(sat, -15);
cv::Mat con = Contrast(lig, 35);
cv::Mat sha = Sharpen(con, 10, 0);
cv::Mat sdo = Shadow(sha, 25);
cv::Mat hig = HighLight(sdo, -5);

图像拼接

cv::Mat ImageSplicing(vector<cv::Mat> images,int type)
{
	if (type != 0 && type != 1)
		type = 0;
	
	int num = images.size();
	int newrow = 0;
	int newcol = 0;
	cv::Mat result;

	// 横向拼接
	if (type == 0)
	{
		int minrow = 10000;
		for (int i = 0; i < num; ++i)
		{
			if (minrow > images[i].rows)
				minrow = images[i].rows;
		}
		newrow = minrow;
		for (int i = 0; i < num; ++i)
		{
			int tcol = images[i].cols*minrow / images[i].rows;
			int trow = newrow;
			cv::resize(images[i], images[i], cv::Size(tcol, trow));
			newcol += images[i].cols;
			if (images[i].type() != images[0].type())
				images[i].convertTo(images[i], images[0].type());
		}
		result = cv::Mat(newrow, newcol, images[0].type(), cv::Scalar(255, 255, 255));

		cv::Range rangerow, rangecol;
		int start = 0;
		for (int i = 0; i < num; ++i)
		{
			rangerow = cv::Range((newrow - images[i].rows) / 2, (newrow - images[i].rows) / 2 + images[i].rows);
			rangecol = cv::Range(start, start + images[i].cols);
			images[i].copyTo(result(rangerow, rangecol));
			start += images[i].cols;
		}
	}
	// 纵向拼接
	else if (type == 1) {
		int mincol = 10000;
		for (int i = 0; i < num; ++i)
		{
			if (mincol > images[i].cols)
				mincol = images[i].cols;
		}
		newcol = mincol;
		for (int i = 0; i < num; ++i)
		{
			int trow = images[i].rows*mincol / images[i].cols;
			int tcol = newcol;
			cv::resize(images[i], images[i], cv::Size(tcol, trow));
			newrow += images[i].rows;
			if (images[i].type() != images[0].type())
				images[i].convertTo(images[i], images[0].type());
		}
		result = cv::Mat(newrow, newcol, images[0].type(), cv::Scalar(255, 255, 255));

		cv::Range rangerow, rangecol;
		int start = 0;
		for (int i = 0; i < num; ++i)
		{
			rangecol= cv::Range((newcol - images[i].cols) / 2, (newcol - images[i].cols) / 2 + images[i].cols);
			rangerow = cv::Range(start, start + images[i].rows);
			images[i].copyTo(result(rangerow, rangecol));
			start += images[i].rows;
		}
	}
	
	return result;
}

寻找非零点

void findNonZero( InputArray src, OutputArray idx );
  • InputArray src,输入图像,如Mat类型。
  • OutputArray idx,非零点存放集合。
vector<cv::Point> idx;
cv::findNonZero(test, idx);
cout << "number:" << idx.size() << endl;
for (auto i : idx)
{
	cout << "x:" << i.x << " y:" << i.y << endl;
}

扩充图像边界

void copyMakeBorder(InputArray src, OutputArray dst,
                    int top, int bottom, int left, int right,
                    int borderType, const Scalar& value = Scalar() );
  • InputArray src,输入图像,如Mat类型。
  • OutputArray dst,输出图像。
  • int top,表示向上扩展多少像素。
  • int bottom,表示向下扩展多少像素。
  • int left,表示向左扩展多少像素。
  • int right,表示向右扩展多少像素。
  • int borderType,推断图像边缘像素的边界模式。
    CONSTANT,Scalar& value设置为255,那就扩展的数据全是255;
    REPLICATE,最边缘的数据是什么,那么下面那一列或者行都是这个数据;
    WRAP,把另一头的数据拿到下面来补上,类似于周期性的感觉;
    REFLECT,将图像下方数据镜像反转,对称式填充;
    REFLECT101,0不动,只对称1,和图3不一样的地方在于,它把最边缘的那一行数据作为轴线,不参与翻转,也就是从次边缘开始对称填充
  • const Scalar& value,有默认值Scalar(),即0。
cv::copyMakeBorder(src, padded, 0, h - src.rows, 0, w - src.cols, cv::BORDER_CONSTANT, cv::Scalar::all(0));

参考

https://www.caxkernel.com/author/93

  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 卡尔曼滤波是一种运动状态预测方法,能够对目标的未来位置进行预测,在目标跟踪中非常常用。而OpenCV是一款开源的计算机视觉库,其中包含了很多算法和函数,能够方便地进行图像处理和目标跟踪。在OpenCV中,提供了另一种目标跟踪方法——基于卡尔曼滤波的目标跟踪算法。 卡尔曼滤波c opencv目标跟踪算法的基本思想是利用对目标运动规律的预测,不断更新目标的位置,在目标运动中不断调整跟踪目标的位置,从而进行目标跟踪。首先通过图像处理或者计算机视觉算法获取目标的位置,然后通过卡尔曼滤波来对目标的运动状态进行预测,并更新目标的位置。在预测过程中,一般会考虑目标的速度和方向等因素。通过不断地预测和更新目标的位置,就实现了目标的跟踪。 卡尔曼滤波c opencv目标跟踪算法的优点是具有预测和适应性,能够在目标运动过程中实时对目标的位置进行跟踪,同时能够自适应地处理信号噪声和测量误差等问题。但是需要注意的是,卡尔曼滤波的精度和效果受到很多因素的影响,比如目标的速度、光照条件、背景变化等,因此在应用过程中需要根据具体情况进行调整和优化。 ### 回答2: 卡尔曼滤波是一种常见的状态估计算法,也被广泛应用于目标跟踪中。这种算法能够通过对目标状态的预测和实际观测值之间的差异进行最优估计,实现对目标轨迹的准确预测和跟踪。 在OpenCV中,卡尔曼滤波的应用主要包括以下几个步骤: 1. 定义状态空间和观测空间:根据跟踪目标的特点和场景要求,建立目标状态和观测值之间的数学模型。 2. 初始化滤波器:设置目标的初始状态,以及各协方差矩阵的初始值。 3. 预测目标状态:通过上一时刻的状态和运动模型,预测目标当前的状态和协方差矩阵。 4. 测量更新:获取目标的观测值,并通过观测值与预测值之间的差异,更新目标状态和协方差矩阵。 5. 迭代计算:循环进行预测和更新步骤,完成对目标轨迹的跟踪。 卡尔曼滤波在目标跟踪中的应用,能够有效解决目标漂移、噪声干扰等问题,提高跟踪的准确性和稳定性。同时,OpenCV提供了丰富的API和样例代码,方便用户进行快速开发。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值