读取图片
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());
- InputOutputArray image:单通道图像矩阵,可以是灰度图,但更常用的是二值图像,一般是经过Canny、拉普拉斯等边缘检测算子处理过的二值图像;
- OutputArrayOfArrays contours:定义为“vector<vector> contours”,是一个向量,并且是一个双重向量,向量内每个元素保存了一组由连续的Point点构成的点的集合的向量,每一组Point点集就是一个轮廓。有多少轮廓,向量contours就有多少元素。
- 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。 - int mode:定义轮廓的检索模式:
取值一:CV_RETR_EXTERNAL只检测最外围轮廓,包含在外围轮廓内的内围轮廓被忽略;
取值二:CV_RETR_LIST 检测所有的轮廓,包括内围、外围轮廓,但是检测到的轮廓不建立等级关系,彼此之间独立,没有等级关系,这就意味着这个检索模式下不存在父轮廓或内嵌轮廓,所以hierarchy向量内所有元素的第3、第4个分量都会被置为-1;
取值三:CV_RETR_CCOMP 检测所有的轮廓,但所有轮廓只建立两个等级关系,外围为顶层,若外围内的内围轮廓还包含了其他的轮廓信息,则内围内的所有轮廓均归属于顶层;
取值四:CV_RETR_TREE, 检测所有轮廓,所有轮廓建立一个等级树结构。外层轮廓包含内层轮廓,内层轮廓还可以继续包含内嵌轮廓。 - 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 近似算法 - 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