基于win10、OpenCV4.4.0、VS2019
找到build\x64\vc15\lib下的lib文件
复制lib全名,粘贴到附加依赖项中
#include
#include
using namespace cv;
int main()
{
Mat src = imread("D:/2.jpg");
if (src.empty()) {
printf("Could not load image.");
return -1;
}
namedWindow("test", WINDOW_AUTOSIZE);
imshow("test", src);
waitKey(0);
return 0;
}
若报错,错误为计算机丢失opencv_world440d.dll,重新启动一下电脑。
若仍未解决,将build\x64\vc15\bin下的同名dll拷贝到C:\Windows\System32下即可。
imread功能是加载图像文件成为一个Mat对象
第一个参数:图像文件名称
第二个参数:加载的图像类型
IMREAD_UNCHANGED(<0)表示加载原图,不做任何改变
IMREAD_GRAYSCALE(0)表示把原图作为灰度图像加载进来
IMREAD_COLOR(>0)表示把原图作为RGB图像加载进来
OpenCV支持JPG、PNG、TIFF等常见图像格式文件加载
namedWindow功能是创建一个OpenCV窗口,它是由OpenCV自动创建与释放,无需销毁它
namedWindow("Window Title", WINDOW_AUTOSIZE)
WINDOW_AUTOSIZE会自动根据图像大小,显示窗口大小,不能人为改变窗口大小
WINDOW_NORMAL,跟QT集成时使用,允许修改窗口大小
imshow根据窗口名称显示图像到指定的窗口上去,
第一个参数:表示窗口名称
第二个参数:表示Mat对象
cv::cvtColor的功能是把图像从一个彩色空间转换到另一个色彩空间,有三个参数
第一个参数表示源图像
第二个参数表示色彩空间转换之后的图像
第三个参数表示源和目标色彩空间,如:COLOR_RGB2HLS、COLOR_BGR2GRAY等
cvtColor(image, gray_image, COLOR_BGR2GRAY);
// ptr
int nl = image.rows; //行数
int nc = image.cols * image.channels();
for (int j = 0; j<nl; j++)
{
uchar* data = image.ptr<uchar>(j);
for (int i = 0; i<nc; i++)
{
data[i] = data[i] / div*div + div / 2;
}
}
// at
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
if (nc == 1) { //单通道图像
int gray = gray_src.at<uchar>(row, col);
gray_src.at<uchar>(row, col) = 255 - gray;
}
else if(nc == 3) { //三通道图像
int b = src.at<Vec3b>(row, col)[0];
int g = src.at<Vec3b>(row, col)[1];
int r = src.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;
}
}
}
//上面at段代码的效果 等同于 bitwise_not(src, dst);
保存图像文件到指定的目录路径
只有8位、16位的PNG、JPG、TIFF文件的格式而且是单通道或者三通道的BGR的图像才可以通过这种方式保存
保存PNG格式的时候可以保存透明通道的图片
可以指定压缩参数
#include
#include
using namespace cv;
int main()
{
Mat src = imread("D:/2.jpg");
if (src.empty()) {
printf("Could not load image.");
return -1;
}
namedWindow("test", WINDOW_AUTOSIZE);
imshow("test", src);
namedWindow("modify", WINDOW_AUTOSIZE);
Mat output_image;
cvtColor(src, output_image, COLOR_BGR2GRAY);
imshow("modify", output_image);
imwrite("D:/1.png", output_image);
waitKey(0);
return 0;
}
所谓掩膜其实就是一个矩阵,然后根据这个矩阵重新计算图片中像素的值。
掩膜主要有以下用途:
这个函数的功能是确保RGB值的范围在0~255之间
掩膜操作是指根据掩膜矩阵(掩膜mask,也称作核kernel)重新计算图像中每个像素的值,实现图像对比度提高。
下面这个公式表示用5倍当前像素的值减去该像素上、下、左、右四个像素值和,得到的结果赋值给当前像素。
I(i,j)=5∗I(i,j)−[I(i−1,j)+I(i+1,j)+I(i,j−1)+I(i,j+1)]
// 1_2OpencvMaskOperator.cpp : This file contains the 'main' function. Program execution begins and ends there.
//
#include
#include
using namespace std;
using namespace cv;
int main()
{
Mat src, dst;
src = imread("D:/1.jpg");
if (!src.data)
return -1;
namedWindow("input image", WINDOW_AUTOSIZE);
imshow("input image", src);
/*
int cols = (src.cols - 1) * src.channels();
int offsetx = src.channels();
int rows = src.rows;
dst = Mat::zeros(src.size(), src.type());
for (int row = 1; row < (rows - 1); row++) {
const uchar* previous = src.ptr(row - 1);
const uchar* current = src.ptr(row);
const uchar* next = src.ptr(row + 1);
uchar* output = dst.ptr(row);
for (int col = offsetx; col < cols; col++) {
output[col] = saturate_cast(5 * current[col] - (current[col - offsetx] + current[col + offsetx] + previous[col] + next[col]));
}
}
*/
Mat kernel = (Mat_<char>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
filter2D(src, dst, src.depth(), kernel);
namedWindow("output image", WINDOW_AUTOSIZE);
imshow("output image", dst);
waitKey(0);
return 0;
}
Mat()
Mat(int rows, int cols, int type)
Mat(Size size, int type)
Mat(int rows, int cols, int type, const Scalar &s)
Mat(Size size, int type, const Scalar &s)
Mat(int ndims, const int * sizes, int type)
Mat(int ndims, const int * sizes, int type, const Scalar &s)
void copyTo(Mat mat)
void convertTo(Mat dst, int type)
Mat clone()
int channels()
int depth()
bool empty()
uchar* ptr(i=0)
Mat A = imread(imgFilePath);
Mat B(A);
Mat B = A.clone();
Mat C;
A.copyTo(C);
等同于
int sz[3] = {2,2,2};
Mat L(3, sz, CV_8UC1,Scalar::all(0));
Mat::zeros(src.size(), src.type());
Mat::eye(2,2,CV_8UC1);
Mat kernel = (Mat_<char>(3,3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
// 1_3OpencvMat.cpp : This file contains the 'main' function. Program execution begins and ends there.
//
#include
#include
using namespace std;
using namespace cv;
int main()
{
Mat src;
src = imread("D:/1.jpg");
if (src.empty()) {
cout << "could not load image." << endl;
return -1;
}
namedWindow("input", WINDOW_AUTOSIZE);
imshow("input", src);
/*Mat dst;
dst = Mat(src.size(), src.type());
dst = Scalar(127, 0, 255);*/
//Mat dst = src.clone();
Mat dst;
//src.copyTo(dst);
namedWindow("output", WINDOW_AUTOSIZE);
/*cvtColor(src, dst, COLOR_BGR2GRAY);
cout << "innput image channels : " << src.channels() << endl;
cout << "output image channels : " << dst.channels() << endl;
imshow("output", dst);
int cols = dst.cols;
int rows = dst.rows;
cout << "rows : " << rows << " cols : " << cols << endl;
const uchar* firstRow = dst.ptr(0);
cout << "first pixel value : " << firstRow << endl;*/
/*
前两个参数 表示行(row)跟列(col)
第三个参数 CV_8UC3,8表示每个通道占8位、U表示无符号、C表示char类型、3表示通道数目为3
第四个参数 表示初始化每个像素值是多少,向量长度对应通道数目一致
*/
/*Mat m(100, 100, CV_8UC3, Scalar(0, 0, 255));
imshow("output", m);*/
Mat m1;
m1.create(src.size(), src.type());
m1 = Scalar(0, 0, 255);
imshow("output", m1);
waitKey(0);
return 0;
}
g(x) = (1 - α)∫₀(x) +α ∫₁(x)
其中α取值范围为0~1之间
addWeighted(InputArray src1, double alpha,InputArray src2,double beta, double gamma,OutputArray dst,int dtype=-1)
参数1:输入图像1
参数2:输入图像1的alpha值
参数3:输入图像2
参数4:输入图像2的alpha值
参数5:gamma值
参数6:输出混合图像
注意:两张图像的大小和类型必须一致
// 1_5OpencvImageMixing.cpp : This file contains the 'main' function. Program execution begins and ends there.
//
#include
#include
using namespace std;
using namespace cv;
int main()
{
Mat src1, src2, dst;
src1 = imread("D:/2.jpg");
src2 = imread("D:/3.jpg");
if (!src1.data) {
cout << "Could not load image 1." << endl;
return -1;
}
if (!src2.data) {
cout << "Could not load image 2." << endl;
return -1;
}
double alpha = 0.5;
if (src1.rows == src2.rows && src1.cols == src2.cols && src1.type() == src2.type()) {
addWeighted(src1, alpha, src2, (1.0 - alpha), 0.0, dst);
//multiply(src1, src2, dst, 1.0);
imshow("src1", src1);
imshow("src2", src2);
namedWindow("Blend demo", WINDOW_AUTOSIZE);
imshow("Blend demo", dst);
}
else {
cout << "Could not blend images, the size of images is not same." << endl;
return -1;
}
waitKey(0);
return 0;
}
调整图像亮度和对比度属于像素变换-点操作
邻域操作,一般用于图像的卷积、图像整体特征的提取、图像梯度的计算等
g(i, j) = α∫(i, j) + β
其中α > 0, β是增益变量
β调整亮度,α调整对比度
// 1_6OpencvAdjustImageBrightnessAndContrast.cpp : This file contains the 'main' function. Program execution begins and ends there.
//
#include
#include
using namespace std;
using namespace cv;
int main()
{
Mat src, dst;
src = imread("D:/1.jpg");
if (!src.data) {
cout << "Could not load image. " << endl;
return -1;
}
char input_win[] = "input image";
namedWindow(input_win, WINDOW_AUTOSIZE);
imshow(input_win, src);
int height = src.rows;
int width = src.cols;
dst = Mat::zeros(src.size(), src.type());
float alpha = 1.2;
float beta = 30;
//使用Vec3f时需要先将图像转换成对应的类型
/*Mat m1;
src.convertTo(m1, CV_32F);*/
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
if (src.channels() == 3) {
float b = src.at<Vec3b>(row, col)[0];
float g = src.at<Vec3b>(row, col)[1];
float r = src.at<Vec3b>(row, col)[2];
dst.at<Vec3b>(row, col)[0] = saturate_cast<uchar>(b*alpha + beta);
dst.at<Vec3b>(row, col)[1] = saturate_cast<uchar>(g*alpha + beta);
dst.at<Vec3b>(row, col)[2] = saturate_cast<uchar>(r*alpha + beta);
}
else if(src.channels() == 1) {
float v = src.at<uchar>(row, col);
dst.at<Vec3b>(row, col) = saturate_cast<uchar>(v * alpha + beta);
}
}
}
char output_title[] = "contrast and brightness change demo.";
namedWindow(output_title, WINDOW_AUTOSIZE);
imshow(output_title, dst);
waitKey(0);
return 0;
}
Point p;
p.x = 10;
p.y = 8;
//or
p = Point(10,8);
Scalar(b,g,r); //表示BGR三个通道
// 1_7OpencvDrawShapesAndText.cpp : This file contains the 'main' function. Program execution begins and ends there.
//
#include
#include
using namespace std;
using namespace cv;
Mat bgImage;
const char* drawdemo_win = "draw shapes and text demo";
void MyLine();
void MyRectangle();
void MyEllipse();
void MyCircle();
void MyPolygon();
void RandomLineDemo();
int main()
{
bgImage = imread("D:/1.jpg");
if (!bgImage.data) {
cout << "Could not load image." << endl;
return -1;
}
/*MyLine();
MyRectangle();
MyEllipse();
MyCircle();
MyPolygon();
putText(bgImage, "Hello OpenCV", Point(200, 300), FONT_ITALIC, 1.0, Scalar(255, 255, 200), 3, 8);
RandomLineDemo();*/
HeartDemo();
namedWindow(drawdemo_win, WINDOW_AUTOSIZE);
imshow(drawdemo_win, bgImage);
waitKey(0);
return 0;
}
void MyLine() {
Point p1 = Point(20, 30);
Point p2 = Point(200, 200);
Scalar color = Scalar(0, 0, 255);
line(bgImage, p1, p2, color, 1, LINE_8);
//LINE_AA反锯齿
}
void MyRectangle() {
Rect rect = Rect(200, 100, 100, 200);
Scalar color = Scalar(255, 0, 0);
rectangle(bgImage, rect, color, 2, LINE_8);
}
void MyEllipse() {
Scalar color = Scalar(0, 255, 0);
ellipse(bgImage, Point(bgImage.cols / 2, bgImage.rows / 2), Size(bgImage.cols / 4, bgImage.rows / 8), 90, 0, 360, color, 2, LINE_8);
}
void MyCircle(){
Scalar color = Scalar(0, 255, 255);
Point center = Point(bgImage.cols / 2, bgImage.rows / 2);
circle(bgImage, center, 150, color, 2, LINE_8);
}
void MyPolygon() {
Point pts[1][5];
pts[0][0] = Point(100, 100);
pts[0][1] = Point(100, 200);
pts[0][2] = Point(200, 200);
pts[0][3] = Point(200, 100);
pts[0][4] = Point(100, 100);
const Point* ppts[] = { pts[0] };
int npt[] = { 5 };
Scalar color = Scalar(255, 12, 255);
fillPoly(bgImage, ppts, npt, 1, color, 8);
}
void RandomLineDemo() {
RNG rng(12345);
Point p1, p2;
Mat bg = Mat::zeros(bgImage.size(), bgImage.type());
namedWindow("random", WINDOW_AUTOSIZE);
for (int i = 0; i < 100000; i++) {
p1.x = rng.uniform(0, bgImage.cols);
p2.x = rng.uniform(0, bgImage.cols);
p1.y = rng.uniform(0, bgImage.rows);
p2.y = rng.uniform(0, bgImage.rows);
Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
if (waitKey(50) > 0)
break;
line(bg, p1, p2, color, 1, LINE_8);
imshow("random", bg);
}
}
Smooth/Blur是图像处理中最简单和常用的操作之一
给图像预处理时减低噪声
Smooth/Blur操作卷积公式
g(i, j) = ∑∫(i+k, j+l) h(k,l)
通常这些卷积算子计算都是线性操作,所以又叫线性滤波
特点:均值滤波本身存在着固有的缺陷,即它不能很好地保护图像细节,在图像去噪的同时也破坏了图像的细节部分,从而使图像变得模糊,不能很好地去除噪声点。
特点:图像大多数噪声均属于高斯噪声,因此高斯滤波器应用也较广泛。高斯滤波是一种线性平滑滤波,适用于消除高斯噪声,广泛应用于图像去噪。
特点:中值滤波对脉冲噪声(椒盐噪声)有良好的滤除作用,特别是在滤除噪声的同时,能够保护信号的边缘,使之不被模糊。这些优良特性是线性滤波方法所不具有的。
特点:双边滤波器顾名思义比高斯滤波多了一个高斯方差
,它是基于空间分布的高斯滤波函数,所以在边缘附近,离的较远的像素不会太多影响到边缘上的像素值,这样就保证了边缘附近像素值的保存。但是由于保存了过多的高频信息,对于彩色图像里的高频噪声,双边滤波器不能够干净的滤掉,只能够对于低频信息进行较好的滤波。双边滤波常用于美颜磨皮等应用中。
blur(Mat src, Mat dst, Size(xradius, yradius), Point(-1, -1));
均值模糊无法克服边缘像素信息丢失缺陷,因为均值滤波是基于平均权重
GaussianBlur(Mat src, Mat dst, Size(11,11), sigmax, sigmay);
其中Size(x, y),x,y必须是正数且是奇数
高斯模糊部分克服了边缘像素信息丢失缺陷,但无法完全避免,因为没有考虑像素值的不同
medianBlur(Mat src, Mat dest,int ksize)
ksize必须是大于1且必须是奇数
bilateralFilter(Mat src, Mat dest, d = 15, 150, 3)
15为计算的半径,半径之内的像素都会被纳入计算,如果为-1,则会根据sigma space 参数取值
150为sigma color 决定多少差值之内的像素会被计算
3 sigma space 如果d的值大于0则声明无效,否则根据它来计算d值
// 1_8OpencvBlurredImage.cpp : This file contains the 'main' function. Program execution begins and ends there.
//
#include
#include
using namespace std;
using namespace cv;
int main()
{
Mat src, dst;
src = imread("D:/1.jpg");
if (!src.data)
return -1;
char input_title[] = "input image";
char output_title[] = "blur image";
namedWindow(input_title, WINDOW_AUTOSIZE);
namedWindow(output_title, WINDOW_AUTOSIZE);
imshow(input_title, src);
//均值模糊
//blur(src, dst, Size(11, 11), Point(-1, 1));
Mat gblur;
//高斯模糊
GaussianBlur(src, gblur, Size(11, 11), 11, 11);
imshow("gaussian blur", gblur);
//中值滤波可以去除椒盐噪声
//medianBlur(src, dst, 3);
//双边模糊可以美化
bilateralFilter(src, dst, 15, 150, 3);
imshow(output_title, dst);
waitKey(0);
return 0;
}
跟卷积操作类似, 假设有图像A和结构元素B,结构元素B在A上面移动,其中B定义其中心为描点,计算B覆盖下A的最大像素值用来替换描点的像素,其中B作为结构体可以是任意形状。
作用是在结构元素的约束下将与目标区域相接触的背景合并到该目标物中,使目标边界向外部扩张,物体的面积增大了相应数量的点。
腐蚀跟膨胀操作的做成类似,唯一不同的是以最小值替换描点重叠下图像的像素值
先腐蚀后膨胀
可以去掉小的对象,在纤细点处分离物体和平滑较大物体的边界而有不明显改变其面积和形状。假设对象是前景色,背景是黑色
先膨胀后腐蚀
可以填充小的洞,将断开的邻近目标连接,在不明显改变物体面积和形状的情况下平滑其边界。假设对象是前景色,背景是黑色
基本梯度
膨胀减去腐蚀之后的得到的插值图像
内部梯度
原图像减去腐蚀之后的得到的插值图像
外部梯度
图像膨胀之后再减去原图像之后的得到的插值图像
方向梯度
使用X方向与Y方向的直线作为结构元素之后得到的图像梯度,X的结构元素分别膨胀与腐蚀得到图像之后求差值得到称为X方向梯度,用Y方向直线做结构分别膨胀与腐蚀之后得到图像求差值之后成为Y方向梯度。
原图像与开操作之间的差值图像。开运算放大了裂缝或者局部低亮度的区域,所以,从原图中减去开运算后的图,得到的结果突出了比原图轮廓周围的区域更明亮的区域,这个操作与选择的核的大小有关。TopHat运算一般用来分离比邻近点亮一些的斑块,可以使用这个运算提取背景。
闭操作图像与原图的差值图像。黑帽运算的结果突出了比原图轮廓周围区域更暗的区域,所以黑帽运算用来分离比邻近点暗一些的斑块。
用于获取结构元素
第一个参数 MORPH_RECT\MORPH_CROSS\MORPH_ELLIPSE
第二个参数 大小,要求是奇数
第三个参数 描点,默认是Point(-1,-1),意思是中心像素
腐蚀
第一个参数 原图像
第二个参数 目标图像
第三个参数 腐蚀操作的内核,如果不指定,默认为一个简单的3X3矩阵,可以使用getStructuringElement()
第四个参数 默认为Point(-1,-1), 内核中心点
第五个参数 腐蚀次数,默认为1
第六个参数 推断边缘类型,默认为BORDER_DEFAULT
第七个参数 边缘值
膨胀
参数与腐蚀一致
第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。图像位深应该为以下五种之一:CV_8U, CV_16U,CV_16S, CV_32F 或CV_64F。
第二个参数,OutputArray类型的dst,即目标图像,函数的输出参数,需要和源图片有一样的尺寸和类型。
第三个参数,int类型的op,表示形态学运算的类型,可以是如下之一的标识符:
MORPH_OPEN – 开运算(Opening operation):先腐蚀后膨胀,去掉小对象
MORPH_CLOSE – 闭运算(Closing operation):先膨胀后腐蚀,可以填充一些小的空洞(fill hole)
MORPH_GRADIENT - 形态学梯度(Morphological gradient):膨胀后的图减去腐蚀后的图(设置恰当的参数可以得到目标的大致边缘)
MORPH_TOPHAT - “顶帽”(“Top hat”):原图像与开操作之间的差值图像(可以用来观察开运算除去了哪些小目标)
MORPH_BLACKHAT - “黑帽”(“Black hat“):闭操作图像与源图像的差值图像(可以观察闭运算的效果)
第四个参数,结构元素即形态学运算的内核
第五个参数,Point类型的anchor,锚的位置,其有默认值( - 1, - 1),表示锚位于中心。
第六个参数,int类型的iterations,迭代使用函数的次数,默认值为1。
第七个参数,int类型的borderType,用于推断图像外部像素的某种边界模式。注意它有默认值BORDER_ CONSTANT。
第八个参数,const Scalar&类型的borderValue,当边界为常数时的边界值,有默认值morphologyDefaultBorderValue(),一般我们不用去管他。需要用到它时,可以看官方文档
第一个参数,InputArray类型的src,输入图像,填单通道,单8位浮点类型Mat即可。
第二个参数,函数运算后的结果存放在这。即为输出图像(与输入图像同样的尺寸和类型)。
第三个参数,预设满足条件的最大值。
第四个参数,指定自适应阈值算法。可选择ADAPTIVE_THRESH_MEAN_C 或 ADAPTIVE_THRESH_GAUSSIAN_C两种。
ADAPTIVE_THRESH_MEAN_C,为局部邻域块的平均值,该算法是先求出块中的均值,再减去常数C。
ADAPTIVE_THRESH_GAUSSIAN_C,为局部邻域块的高斯加权和。该算法是在区域中(x, y)周围的像素根据高斯函数按照他们离中心点的距离进行加权计算,再减去常数C。
第五个参数,指定阈值类型。可选择THRESH_BINARY或者THRESH_BINARY_INV两种。(即二进制阈值或反二进制阈值)。
第六个参数,表示邻域块大小,用来计算区域阈值,一般选择为3、5、7…等。
第七个参数,参数C表示与算法有关的参数,它是一个从均值或加权均值提取的常数,可以是负数。(具体见下面的解释)。
第一个参数 trackbar的名称
第二个参数 需要显示trackbar的窗体名称
第三个参数 trackbar当前的值
第四个参数 trackbar的量程
第五个参数 需要调用的回调函数
#include
#include
using namespace std;
using namespace cv;
int main()
{
Mat src, dst;
//输入图像彩色图像imread
src = imread("D:/1.jpg");
if (!src.data)
return -1;
namedWindow("input image", WINDOW_AUTOSIZE);
imshow("input image", src);
Mat gray_src;
//转为灰度图像
cvtColor(src, gray_src, COLOR_BGR2GRAY);
Mat binary_src;
//转为二值图像
adaptiveThreshold(~gray_src, binary_src, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 15, -2);
//定义结构元素
Mat hline = getStructuringElement(MORPH_RECT, Size(src.cols / 16, 1), Point(-1, -1));
Mat vline = getStructuringElement(MORPH_RECT, Size(1, src.rows / 16), Point(-1, -1));
Mat temp;
//开操作提取水平与垂直线,下面注释代码片段等同于下面未注释的代码
/*erode(binary_src, temp, hline);
dilate(temp, dst, hline);*/
morphologyEx(binary_src, dst, MORPH_OPEN, vline);
bitwise_not(dst, dst);
blur(dst, dst, Size(3, 3), Point(-1, -1));
char output_title[] = "result image";
namedWindow(output_title, WINDOW_AUTOSIZE);
imshow(output_title, dst);
waitKey(0);
return 0;
}
图像金字塔是以多个分辨率来表示图像的一种有效且概念简单的结构,一个图像金字塔由一系列的图像组成,最底下一张是图像尺寸最大,最上方的图像尺寸最小,从空间上从上向下看,就像一个古代的金字塔。
上采样:生成的图像是原图在宽与高各放大两倍
降采样:生成的图像是原图在宽与高各缩小两倍
归一化数据。该函数分为范围归一化与数据值归一化。
第一个参数,输入数组;
第二个参数,输出数组,数组的大小和原数组一致;
第三个参数,1,用来规范值,2.规范范围,并且是下限;
第四个参数,只用来规范范围并且是上限;
第五个参数,归一化选择的数学公式类型;
有NORM_INF, NORM_MINMAX,NORM_L1和NORM_L2四种。
1、在 NORM_MINMAX 模式下,alpha表示归一化后的最小值,beta表示归一化后的最大值。
2、在NORM_L1、NORM_L2、NORM_INF 模式下,alpha表示执行相应归一化后矩阵的范数值,beta不使 用。
3、稀疏矩阵归一化仅支持非零像素
第六个参数,当为负,输出在大小深度通道数都等于输入,当为正,输出只在深度与输如不同,不同的地方游dtype决定;
第七个参数,掩码。选择感兴趣区域,选定后只能对该区域进行操作。
#include
#include
using namespace std;
using namespace cv;
int main()
{
Mat src, dst;
src = imread("D:/1.jpg");
if (!src.data)
return -1;
namedWindow("input image", WINDOW_AUTOSIZE);
imshow("input image", src);
//上采样
//pyrUp(src, dst, Size(src.cols * 2, src.rows * 2));
//降采样
pyrDown(src, dst, Size(src.cols / 2, src.rows / 2));
char output_title[] = "result image";
namedWindow(output_title, WINDOW_AUTOSIZE);
imshow(output_title, dst);
Mat gray_src, g1, g2, dogImg;
cvtColor(src, gray_src, COLOR_BGR2GRAY);
GaussianBlur(gray_src, g1, Size(3, 3), 0, 0);
GaussianBlur(g1, g2, Size(3, 3), 0, 0);
subtract(g1, g2, dogImg, Mat());
//归一化显示
normalize(dogImg, dogImg, 255, 0, NORM_MINMAX);
imshow("DOG Image", dogImg);
waitKey(0);
return 0;
}
像素值大于阈值,=255
像素值小于阈值,=0
像素值小于阈值,=255
像素值大于阈值,=0
像素值大于阈值,=阈值
像素值小于阈值,保持不变
像素大于等于阈值,保持不变
像素小于阈值,=0
像素值小于阈值,=0
像素值大于阈值,保持不变
第一个参数 源图像
第二个参数 输出图像
第三个参数 阈值
第四个参数 输出图像中的最大值
第五个参数 阈值类型
编号 | 阈值类型枚举 | 注意 |
---|---|---|
1 | THRESH_BINARY | |
2 | THRESH_BINARY_INV | |
3 | THRESH_TRUNC | |
4 | THRESH_TOZERO | |
5 | THRESH_TOZERO_INV | |
6 | THRESH_MASK | 不支持 |
7 | THRESH_OTSU | 不支持32位 |
8 | THRESH_TRIANGLE | 不支持32位 |
#include
#include
using namespace std;
using namespace cv;
Mat src, dst, gray_src;
int threshold_value = 127;
int threshold_max = 255;
int type_value = 2;
int type_max = 4;
char output_title[] = "result image";
void ThresHold_Demo(int, void*);
int main()
{
src = imread("C:/Users/ThinkPad/Desktop/1.jpg");
if (!src.data)
return -1;
namedWindow("input image", WINDOW_AUTOSIZE);
namedWindow(output_title, WINDOW_AUTOSIZE);
imshow("input image", src);
createTrackbar("Threshold Value : ", output_title, &threshold_value, threshold_max, ThresHold_Demo);
createTrackbar("Type Value : ", output_title, &type_value, type_max, ThresHold_Demo);
ThresHold_Demo(0, 0);
waitKey(0);
return 0;
}
void ThresHold_Demo(int, void*) {
cvtColor(src, gray_src, COLOR_BGR2GRAY);
threshold(gray_src, dst, threshold_value, threshold_max, type_value);
imshow(output_title, dst);
}
是图像处理中一个操作,是kernel在图像的每个像素上的操作。
任意一对互相垂直方向上的差分可以看成求梯度的近似方法
优缺点:
边缘定位精度较高,对于陡峭边缘且噪声低的图像效果较好,但没有进行平滑处理,没有抑制噪声能力
应用
具有陡峭的低噪声的图像处理效果较好
robert X方向
1 | 0 |
---|---|
0 | -1 |
robert Y方向
0 | 1 |
---|---|
-1 | 0 |
用来计算图像灰度的近似梯度,进行了平滑处理,对噪声具有一定的抑制能力,但容易出现多像素宽度。
应用
检测方法对灰度渐变和噪声较多的图像处理效果较好
sobel X方向
-1 | 0 | 1 |
---|---|---|
-2 | 0 | 2 |
-1 | 0 | 1 |
sobel Y方向
-1 | -2 | -1 |
---|---|---|
0 | 0 | 0 |
1 | 2 | 1 |
对噪声较为敏感,使噪声能力成分得到加强,容易丢失部分边缘方向信息,造成一些不连续的检测边缘,同时抗噪声能力较差。所以很少用该算子检测边缘
应用
用来判断边缘像素视为与图像的明区还是暗区。
0 | -1 | 0 |
---|---|---|
-1 | 4 | -1 |
0 | -1 | 0 |
最优化思想的边缘检测算子,同时采用高斯函数对图像进行平滑处理,但会造成将高频边缘平滑掉,造成边缘丢失,采用双阈值算法检测和链接边缘。
利用内核实现对图像的卷积运算
第一个参数,表示输入图像
第二个参数,表示输出的边缘图,其大小和通道数与输入图像相同
第三个参数,表示目标图像所需的深度
第四个参数,表示卷积核,一个单通道浮点型矩阵
第五个参数,表示内核的基准点,其默认值为(-1,-1),位于中心位置
第六个参数,表示在储存目标图像前可选的添加到像素的值,默认值为0
第七个参数,表示边框模式
第一个参数,8位的输入图像
第二个参数,输出图像
第三个参数,输出图像深度
第四个参数,x方向的几阶导数
第五个参数,y方向的几阶导数
第六个参数,kernel大小,必须是奇数
第七个参数,scale因子,是sobel过滤内核因子的缩放因子
第八个参数,delta,sobel边缘检测后在影像中加上delta
第九个参数,边界类型
第一个参数,8位的输入图像。
第二个参数,输出图像。
第三个参数,输出图像深度。
第四个参数,x方向上的差分阶数。
第五个参数,y方向上的差分阶数。
第六个参数,计算导数值时可选的缩放因子,默认值1,表示默认情况下没用应用缩放。
第七个参数,表示在结果存入输出图像之前可选的delta值,默认值0。
第八个参数,边界模式。
参数含义同sobel
第一个参数,8位的输入图像
第二个参数,输出图像
第三个参数,表示设置的低阈值
第四个参数,表示设置的高阈值,一般设定位低阈值的3倍
第五个参数,sobel算子的大小,必须是奇数
第六个参数,如果为真,则使用更精确的L2范数进行计算(即两个方向的倒数的平方和再开方),否则使用L1范数(直接将两个方向导数的绝对值相加)
给图像添加边缘API
第一个参数,输入图像
第二个参数,添加边缘图像
第三/四/五/六个参数,边缘长度,一般上下左右都取相同值
第五个参数,边缘类型
第六个参数,颜色
#include
#include
using namespace std;
using namespace cv;
Mat src, dst;
char output_title[] = "result image";
int main()
{
src = imread("D:/1.jpg");
if (!src.data)
return -1;
namedWindow("input image", WINDOW_AUTOSIZE);
namedWindow(output_title, WINDOW_AUTOSIZE);
imshow("input image", src);
//Robert X direction
//Mat kernel = (Mat_(2, 2) << 1, 0, 0, -1);
//Robert Y direction
//Mat kernel = (Mat_(2, 2) << 0, 1, -1, 0);
//Sobel X direction
//Mat kernel = (Mat_(3, 3) << -1, 0, 1, -2, 0, 2, -1, 0, 1);
//Sobel Y direction
//Mat kernel = (Mat_(3, 3) << -1, -2, -1, 0, 0, 0, 1, 2, 1);
//Laplace
//Mat kernel = (Mat_(3, 3) << 0, -1, 0, -1, 4, -1, 0, -1, 0);
//filter2D(src, dst, -1, kernel, Point(-1, -1), 0.0);
int c = 0;
int index = 0;
int ksize = 0;
while (true) {
c = waitKey(500);
if ((char)c == 27) //ESC
break;
ksize = 4 + (index % 5) * 2 + 1;
Mat kernel = Mat::ones(Size(ksize, ksize), CV_32F / (float)(ksize * ksize));
filter2D(src, dst, -1, kernel, Point(-1, -1));
index++;
imshow(output_title, dst);
}
return 0;
}
#include
#include
using namespace std;
using namespace cv;
Mat src, dst;
char output_title[] = "result image";
int main()
{
src = imread("D:/1.jpg");
if (!src.data)
return -1;
namedWindow("input image", WINDOW_AUTOSIZE);
namedWindow(output_title, WINDOW_AUTOSIZE);
imshow("input image", src);
int top = (int)(0.05 * src.rows);
int bottom = (int)(0.05 * src.rows);
int left = (int)(0.05 * src.cols);
int right = (int)(0.05 * src.cols);
RNG rng(12345);
int borderType = BORDER_DEFAULT;
int c = 0;
while (true) {
c = waitKey(500);
//ESC
if ((char)c == 27)
break;
if ((char)c == 'r')
borderType = BORDER_REPLICATE;
else if ((char)c == 'w')
borderType = BORDER_WRAP;
else if ((char)c == 'c')
borderType = BORDER_CONSTANT;
Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
copyMakeBorder(src, dst, top, bottom, left, right, borderType, color);
imshow(output_title, dst);
}
waitKey(0);
return 0;
}
#include
#include
using namespace std;
using namespace cv;
Mat src, dst;
char output_title[] = "result image";
int main()
{
src = imread("D:/1.jpg");
if (!src.data)
return -1;
namedWindow("input image", WINDOW_AUTOSIZE);
namedWindow(output_title, WINDOW_AUTOSIZE);
imshow("input image", src);
GaussianBlur(src, dst, Size(3, 3), 0, 0);
Mat gray_src;
cvtColor(dst, gray_src, COLOR_BGR2GRAY);
imshow("gray image", gray_src);
Mat xgrad, ygrad;
/*Sobel(gray_src, xgrad, CV_16S, 1, 0, 3);
Sobel(gray_src, ygrad, CV_16S, 0, 1, 3);*/
Scharr(gray_src, xgrad, CV_16S, 1, 0);
Scharr(gray_src, ygrad, CV_16S, 0, 1);
//用于实现对整个图像数组中的每一个元素进行图像增强等相关操作
convertScaleAbs(xgrad, xgrad);
convertScaleAbs(ygrad, ygrad);
imshow("xgrad", xgrad);
imshow("ygrad", ygrad);
/*Mat xygrad;
addWeighted(xgrad, 0.5, ygrad, 0.5, 0, xygrad);
imshow("Final image", xygrad);*/
Mat xygrad = Mat(xgrad.size(), xgrad.type());
int width = xgrad.cols;
int height = xgrad.rows;
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
int xg = xgrad.at<char>(row, col);
int yg = ygrad.at<char>(row, col);
int xy = xg + yg;
xygrad.at<char>(row, col) = saturate_cast<uchar>(xy);
}
}
imshow(output_title, xygrad);
waitKey(0);
return 0;
}
#include
#include
using namespace std;
using namespace cv;
Mat src, dst;
char input_title[] = "input image";
char output_title[] = "result image";
int main()
{
src = imread("D:/1.jpg");
if (!src.data)
return -1;
namedWindow(input_title, WINDOW_AUTOSIZE);
namedWindow(output_title, WINDOW_AUTOSIZE);
imshow(input_title, src);
Mat gray_src, edge_image;
GaussianBlur(src, dst, Size(3, 3), 0, 0);
cvtColor(dst, gray_src, COLOR_BGR2GRAY);
Laplacian(gray_src, edge_image, CV_16S, 3);
convertScaleAbs(edge_image, edge_image);
threshold(edge_image, edge_image, 0, 255, THRESH_OTSU | THRESH_BINARY);
imshow(output_title, edge_image);
waitKey(0);
return 0;
}
#include
#include
using namespace std;
using namespace cv;
Mat src, dst, gray_src;
char input_title[] = "input image";
char output_title[] = "result image";
int t1_value = 50;
int max_value = 255;
void Canny_Demo(int, void*);
int main()
{
src = imread("D:/1.jpg");
if (!src.data)
return -1;
namedWindow(input_title, WINDOW_AUTOSIZE);
namedWindow(output_title, WINDOW_AUTOSIZE);
imshow(input_title, src);
//Canny算法步骤
//1、高斯模糊 GaussianBlur
//2、灰度转换 cvtColor
//3、计算梯度 Sobel/Scharr
//4、非最大信号抑制
//5、高低阈值输出二值图像
cvtColor(src, gray_src, COLOR_BGR2GRAY);
createTrackbar("Threshold Value : ", output_title, &t1_value, max_value, Canny_Demo);
Canny_Demo(0, 0);
waitKey(0);
return 0;
}
void Canny_Demo(int, void*) {
Mat edge_output;
blur(gray_src, gray_src, Size(3, 3), Point(-1, -1), BORDER_DEFAULT);
Canny(gray_src, edge_output, t1_value, (t1_value * 2), 3, false);
dst.create(src.size(), src.type());
src.copyTo(dst, edge_output);
imshow(output_title, dst);
}
优点:
可以更好的减少噪声干扰
圆周上任意三点所确定的圆,经Hough变换后在三维参数空间应对应一点。遍历圆周上所有点,任意三个点所确定的候选圆进行投票。遍历结束后,得票数最高点(理论上圆周上任意三点确定的圆在Hough变换后均对应三维参数空间中的同一点)所确定的圆即为该圆周上,绝大多数点所确定的圆(以下称为当选圆),即绝大多数点均在该当选圆的圆周上,以此确定该圆。
第一个参数,输入图像,必须是8位的灰度图像
第二个参数,输出的极坐标来表示直线
第三个参数,生成极坐标时的像素扫描步长
第四个参数,生成极坐标时的角度步长,一般取值CV_PI/180
第五个参数,阈值,只有获得足够焦点的极坐标才被看作是直线
第六个参数,double类型的srn,有默认值0。对于多尺度的霍夫变换,这是第三个参数进步尺寸rho的除数距离。粗略的累加器进步尺寸直接是第三个参数rho,而精确的累加器进步尺寸为rho/srn。
第七个参数,double类型的stn,有默认值0,对于多尺度霍夫变换,srn表示第四个参数进步尺寸的单位角度theta的除数距离。且如果srn和stn同时为0,就表示使用经典的霍夫变换。否则,这两个参数应该都为正数。
第八个参数,表示角度扫描范围0~180之间
第九个参数
此函数在HoughLines的基础上末尾加了一个代表Probabilistic(概率)的P,表明它可以采用累计概率霍夫变换(PPHT)来找出二值图像中的直线。
第一个参数,InputArray类型的image,输入图像,即源图像,需为8位的单通道二进制图像,可以将任意的源图载入进来后由函数修改成此格式后,再填在这里。
第二个参数,InputArray类型的lines,经过调用HoughLinesP函数后后存储了检测到的线条的输出矢量,每一条线由具有四个元素的矢量(x_1,y_1, x_2, y_2) 表示,其中,(x_1, y_1)和(x_2, y_2) 是是每个检测到的线段的结束点。
第三个参数,double类型的rho,以像素为单位的距离精度。另一种形容方式是直线搜索时的进步尺寸的单位半径。
第四个参数,double类型的theta,以弧度为单位的角度精度。另一种形容方式是直线搜索时的进步尺寸的单位角度。
第五个参数,int类型的threshold,累加平面的阈值参数,即识别某部分为图中的一条直线时它在累加平面中必须达到的值。大于阈值threshold的线段才可以被检测通过并返回到结果中。
第六个参数,double类型的minLineLength,有默认值0,表示最低线段的长度,比这个设定参数短的线段就不能被显现出来。
第七个参数,double类型的maxLineGap,有默认值0,允许将同一行点与点之间连接起来的最大的距离。
第一个参数,输入图像,必须是8位的单通道灰度图像
第二个参数,用于存储类型是vector****;存储着圆心X,Y,和半径R
第三个参数,方法,一般是梯度,CV_HOUGH_GRADIENT
第四个参数,检测内侧圆心的累加器图像的分辨率于输入图像之比的倒数,如dp=1,累加器和输入图像具有相同的分辨率,如果dp=2,累计器便有输入图像一半那么大的宽度和高度,一般不需要改变就是1
第五个参数,两个圆心相聚的最小距离可以认为是两个圆
第六个参数,Canny边缘检测的高阈值
第七个参数,中心点累加阈值-候选圆心,它表示在检测阶段圆心的累加器阈值,它越小,就越可以检测到更多根本不存在的圆,而它越大的话,能通过检测的圆就更加接近完美的圆形了
第八个参数,最小半径
第九个参数,最大半径
#include
#include
using namespace std;
using namespace cv;
Mat src, dst, gray_src;
char input_title[] = "input image";
char output_title[] = "result image";
int main()
{
src = imread("D:/1.jpg");
if (!src.data)
return -1;
namedWindow(input_title, WINDOW_AUTOSIZE);
namedWindow(output_title, WINDOW_AUTOSIZE);
imshow(input_title, src);
//提取边缘
Canny(src, gray_src, 100, 200);
cvtColor(gray_src, dst, COLOR_GRAY2BGR);
imshow("edge image", gray_src);
vector<Vec4f> plines;
HoughLinesP(gray_src, plines, 1, CV_PI / 180.0, 10, 0, 10);
Scalar color = Scalar(0, 0, 255);
for (size_t i = 0; i < plines.size(); i++) {
Vec4f hline = plines[i];
line(dst, Point(hline[0], hline[1]), Point(hline[2], hline[3]), color, 3, LINE_AA);
}
imshow(output_title, dst);
waitKey(0);
return 0;
}
#include
#include
using namespace std;
using namespace cv;
Mat src, dst, gray_src;
char input_title[] = "input image";
char output_title[] = "result image";
int main()
{
src = imread("C:/Users/ThinkPad/Desktop/1.jpg");
if (!src.data)
return -1;
namedWindow(input_title, WINDOW_AUTOSIZE);
namedWindow(output_title, WINDOW_AUTOSIZE);
imshow(input_title, src);
//中值滤波
Mat m;
medianBlur(src, m, 3);
cvtColor(m, m, COLOR_BGR2GRAY);
//霍夫圆检测
vector<Vec3f> pcircles;
HoughCircles(m, pcircles, HOUGH_GRADIENT, 1, 10, 100, 30, 5, 50);
src.copyTo(dst);
for (size_t i = 0; i < pcircles.size(); i++) {
Vec3f cc = pcircles[i];
circle(dst, Point(cc[0], cc[1]), cc[2], Scalar(0, 0, 255), 2, LINE_AA);
circle(dst, Point(cc[0], cc[1]), cc[2], Scalar(100, 0, 200), 2, LINE_AA);
}
imshow(output_title, dst);
waitKey(0);
return 0;
}
把输入图像中各个像素按照一定的规则映射到另外一张图像的对应位置上去,形成一张新的图像。
第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可,且需为单通道8位或者浮点型图像。
第二个参数,OutputArray类型的dst,函数调用后的运算结果存在这里,即这个参数用于存放函数调用后的输出结果,需和源图片有一样的尺寸和类型。
第三个参数,InputArray类型的map1,它有两种可能的表示对象。
(1).表示点(x,y)的第一个映射。
(2).表示CV_16SC2 , CV_32FC1 或CV_32FC2类型的X值。
第四个参数,InputArray类型的map2,同样,它也有两种可能的表示对象,而且他是根据map1来确定表示哪种对象。
(1).若map1表示点(x,y)时。这个参数不代表任何值。
(2).表示CV_16UC1 , CV_32FC1类型的Y值(第二个值)。
第五个参数,int类型的interpolation,插值方式,之前的resize( )函数中有讲到,需要注意,resize(
)函数中提到的INTER_AREA插值方式在这里是不支持的,所以可选的插值方式如下:
(1)INTER_NEAREST——最近邻插值
(2)INTER_LINEAR——双线性插值(默认)
(3)INTER_CUBIC——双三样条插值(逾4×4像素邻域内的双三次插值)
(4)INTER_LANCZOS4——lanczos插值(逾8×8像素邻域的Lanczos插值)
第六个参数,int类型的borderMode,边界模式,有默认值BORDER_CONSTANT,表示目标图像中“离群点(outliers)”的像素值不会被此函数修改。
第七个参数,const Scalar&类型的borderValue,当有常数边界时使用的值,其有默认值Scalar( ),即默认值为0。
#include
#include
using namespace std;
using namespace cv;
Mat src, dst, map_x, map_y;
char input_title[] = "input image";
char output_title[] = "result image";
int index = 0;
void update_map();
int main()
{
src = imread("C:/Users/ThinkPad/Desktop/1.jpg");
if (!src.data)
return -1;
namedWindow(input_title, WINDOW_AUTOSIZE);
namedWindow(output_title, WINDOW_AUTOSIZE);
imshow(input_title, src);
map_x.create(src.size(), CV_32FC1);
map_y.create(src.size(), CV_32FC1);
int c = 0;
while (true) {
c = waitKey(500);
if ((char)c == 27) {
break;
}
index = c % 4;
update_map();
remap(src, dst, map_x, map_y, INTER_LINEAR, BORDER_CONSTANT, Scalar(0, 255, 255));
imshow(output_title, dst);
}
waitKey(0);
return 0;
}
void update_map() {
for (int row = 0; row < src.rows; row++) {
for (int col = 0; col < src.cols; col++) {
switch (index)
{
case 0:
if (col > (src.cols * 0.25) && col < (src.cols * 0.75) && row >(src.rows * 0.25) && row < (src.rows * 0.75)) {
map_x.at<float>(row, col) = 2 * (col - (src.cols * 0.25));
map_y.at<float>(row, col) = 2 * (row - (src.rows * 0.25));
}
else {
map_x.at<float>(row, col) = 0;
map_y.at<float>(row, col) = 0;
}
break;
case 1:
map_x.at<float>(row, col) = src.cols - col - 1;
map_y.at<float>(row, col) = row;
break;
case 2:
map_x.at<float>(row, col) = col;
map_y.at<float>(row, col) = src.rows - row -1;
break;
case 3:
map_x.at<float>(row, col) = src.cols - col - 1;
map_y.at<float>(row, col) = src.rows - row - 1;
break;
}
}
}
}
对于图像梯度、每个像素的角度、等一切图像的属性值,都可以建立直方图。
常见属性:
dims:表示维度,对于灰度图像来说只有一个通道dims=1
bins:表示在维度中子区域大小划分,bins=256,划分256个级别
range:表示值的范围,灰度值范围为[0~255]之间
图像直方图,是指整个图像在灰度范围内的像素值(0~255)统计出现频率次数,据此生成的直方图,称为图像直方图。直方图反映了图像灰度的分布情况,是图像的统计学特征。
直方图均衡化是一种简单有效的图像增强技术,通过改变图像的直方图来改变图像中各像素的灰度,主要用于增强动态范围偏小的图像的对比度
对输入的两站图像计算得到直方图H1与H2,归一化到相同的尺度空间,然后可以通过计算H1与H2的之间的距离得到两个直方图的相似程度进而比较图像本身的相似程度。
比较方法:
Correlation:相关性比较
Chi-Square:卡方比较
Inersection:十字交叉性
Bhattacharyya distance:巴氏距离
反向投影是反映直方图模型在目标图像中的分布情况
简单点说就是用直方图模型去目标图像中寻找是否有相似的对象。通常用HSV色彩空间的HS两个通道直方图模型
第一个参数,输入图像,必须是8位的灰度图像
第二个参数,输出结果
把多通道图像分为多个单通道图像
第一个参数,输入图像
第二个参数,输出的多通道图像数组
第一个参数,输入图像指针
第二个参数,图像数目
第三个参数,通道数,要计算的通道数的下标,可以传一个数组 {0, 1} 表示计算第0通道与第1通道的直方图,此数组长度要与histsize ranges 数组长度一致
第四个参数,输入mask,可选。如有,则表示只计算mask元素值为255的位置的直方图
第五个参数,输出的直方图数据
第六个参数,维数
第七个参数,直方图级数,对应bins
第八个参数,值域范围
第九个参数,是否归一化到0-1之间
第十个参数,累积标识符,有默认值false,若为true,直方图再分配阶段不会清零。此功能主要是允许从多个阵列中计算单个直方图或者用于再特定的时间更新直方图.
第一/二个参数,直方图数据
第三个参数,比较方法
第一个参数,输入图像,图像深度必须位CV_8U,CV_16U或CV_32F中的一种,尺寸相同,每一幅图像都可以有任意的通道数
第二个参数,输入图像的数量
第三个参数,用于计算反向投影的通道列表,通道数必须与直方图维度相匹配,第一个数组的通道是从0到image[0].channels()-1,第二个数组通道从图像image[0].channels()到image[0].channels()+image[1].channels()-1计数
第四个参数,输入的直方图,直方图的bin可以是密集(dense)或稀疏(sparse)
第五个参数,目标反向投影输出图像,是一个单通道图像,与原图像有相同的尺寸和深度
第六个参数,直方图中每个维度bin的取值范围
第七个参数,可选输出反向投影的比例因子
第八个参数,直方图是否均匀分布(uniform)的标识符,有默认值true
#include
#include
using namespace std;
using namespace cv;
Mat src, dst;
char input_title[] = "input image";
char output_title[] = "result image";
int main()
{
src = imread("D:/1.jpg");
if (!src.data)
return -1;
namedWindow(input_title, WINDOW_AUTOSIZE);
namedWindow(output_title, WINDOW_AUTOSIZE);
cvtColor(src, src, COLOR_BGR2GRAY);
imshow(input_title, src);
//原图必须是8位
equalizeHist(src, dst);
imshow(output_title, dst);
waitKey(0);
return 0;
}
#include
#include
using namespace std;
using namespace cv;
Mat src, dst;
char input_title[] = "input image";
char output_title[] = "result image";
int main()
{
src = imread("D:/1.jpg");
if (!src.data)
return -1;
namedWindow(input_title, WINDOW_AUTOSIZE);
namedWindow(output_title, WINDOW_AUTOSIZE);
imshow(input_title, src);
//分通道显示
vector<Mat> bgr_planes;
//转成单通道
split(src, bgr_planes);
//imshow("single image", bgr_planes[0]);
//计算直方图
int histSize = 256;
float range[] = { 0, 256 };
const float* histRanges = { range };
Mat b_hist, g_hist, r_hist;
calcHist(&bgr_planes[0], 1, 0, Mat(), b_hist, 1, &histSize, &histRanges, true, false);
calcHist(&bgr_planes[1], 1, 0, Mat(), g_hist, 1, &histSize, &histRanges, true, false);
calcHist(&bgr_planes[2], 1, 0, Mat(), r_hist, 1, &histSize, &histRanges, true, false);
//归一化
int hist_h = 400;
int hist_w = 512;
int bin_w = hist_w / histSize;
Mat histImage(hist_w, hist_h, CV_8UC3, Scalar(0, 0, 0));
normalize(b_hist, b_hist, 0, hist_h, NORM_MINMAX, -1, Mat());
normalize(g_hist, g_hist, 0, hist_h, NORM_MINMAX, -1, Mat());
normalize(r_hist, r_hist, 0, hist_h, NORM_MINMAX, -1, Mat());
for (int i = 0; i < histSize; i++) {
line(histImage, Point((i - 1) * bin_w, hist_h - cvRound(b_hist.at<float>(i))), Point((i)*bin_w, hist_h - cvRound(b_hist.at<float>(i))), Scalar(255, 0, 0), 2, LINE_AA);
line(histImage, Point((i - 1) * bin_w, hist_h - cvRound(g_hist.at<float>(i))), Point((i)*bin_w, hist_h - cvRound(g_hist.at<float>(i))), Scalar(0, 255, 0), 2, LINE_AA);
line(histImage, Point((i - 1) * bin_w, hist_h - cvRound(r_hist.at<float>(i))), Point((i)*bin_w, hist_h - cvRound(r_hist.at<float>(i))), Scalar(0, 0, 255), 2, LINE_AA);
}
imshow(output_title, histImage);
waitKey(0);
return 0;
}
#include
#include
using namespace std;
using namespace cv;
Mat src, dst;
char input_title[] = "input image";
char output_title[] = "result image";
int main()
{
src = imread("D:/1.jpg");
if (!src.data)
return -1;
namedWindow(input_title, WINDOW_AUTOSIZE);
namedWindow(output_title, WINDOW_AUTOSIZE);
imshow(input_title, src);
cvtColor(src, src, COLOR_BGR2HSV);
int h_bins = 50;
int s_bins = 60;
int histSize[] = { h_bins, s_bins };
float h_ranges[] = { 0,180 };
float s_ranges[] = { 0,256 };
const float* ranges[] = { h_ranges, s_ranges };
int channels[] = { 0, 1 };
MatND hist_base;
calcHist(&src, 1, channels, Mat(), hist_base, 2, histSize, ranges, true, false);
normalize(hist_base, hist_base, 0, 1, NORM_MINMAX, -1, Mat());
double base = compareHist(hist_base, hist_base, HISTCMP_CORREL);
cout << "value is " << base << endl;
waitKey(0);
return 0;
}
#include
#include
using namespace std;
using namespace cv;
Mat src, dst;
char input_title[] = "input image";
char output_title[] = "result image";
Mat hsv, hue;
int bins = 12;
void Hist_And_Backprojection(int, void*);
int main()
{
src = imread("D:/1.jpg");
if (!src.data)
return -1;
namedWindow(input_title, WINDOW_AUTOSIZE);
namedWindow(output_title, WINDOW_AUTOSIZE);
imshow(input_title, src);
cvtColor(src, hsv, COLOR_BGR2HSV);
hue.create(hsv.size(), hsv.depth());
int nchannels[] = { 0,0 };
mixChannels(&hsv, 1, &hue, 1, nchannels, 1);
createTrackbar("Histogram Bins : ", input_title, &bins, 180, Hist_And_Backprojection);
Hist_And_Backprojection(0, 0);
waitKey(0);
return 0;
}
void Hist_And_Backprojection(int, void*) {
float range[] = { 0, 180 };
const float* histRanges = { range };
Mat h_hist;
if (bins == 0)
return;
calcHist(&hue, 1, 0, Mat(), h_hist, 1, &bins, &histRanges, true, false);
normalize(h_hist, h_hist, 0, 255, NORM_MINMAX, -1, Mat());
Mat backProjImage;
calcBackProject(&hue, 1, 0, h_hist, backProjImage, &histRanges, 1, true);
imshow(output_title, backProjImage);
int hist_h = 400, hist_w = 400;
Mat histImage(hist_w, hist_h, CV_8UC3, Scalar(0, 0, 0));
int bin_w = hist_w / bins;
for (int i = 0; i < bins; i++) {
rectangle(histImage,
Point((i - 1) * bin_w, hist_h - h_hist.at<float>(i) * (400 / 255)),
//Point((i)*bin_w, (hist_h - cvRound(h_hist.at(i) * (400 / 255)))),
Point(i * bin_w, hist_h),
Scalar(0, 0, 255), -1);
}
imshow("Histogram ", histImage);
}
模板匹配(TemplateMatching)就是在一幅图像中寻找和模板图像(template)最相似的区域,该方法原理简单计算速度快,能够应用于目标识别,目标跟踪等多个领域。
匹配算法
计算平方不同
计算相关性
计算相关系数
第一个参数,输入图像。必须为8位或者32位的浮点型。
第二个参数,用于搜索的模板图像。必须小于输入图像并且是一样的数据类型。
第三个参数,匹配结果图像。必须是单通道32位浮点型,且大小是(W-w+1)*(H-h+1),其中W,H分别为输入图像的宽和高,w,h分别为模板图像的宽和高。
第四个参数,相似度衡量的方法
#include
#include
using namespace std;
using namespace cv;
Mat src, dst, temp;
char input_title[] = "input image";
char output_title[] = "result image";
char match_title[] = "template match-demo";
int match_method = TM_SQDIFF;
int max_track = 5;
void Match_Demo(int, void*);
int main()
{
//待检测图像
src = imread("D:/1.jpg");
//模板图像
temp = imread("D:/2.jpg");
if (!src.data)
return -1;
namedWindow(input_title, WINDOW_AUTOSIZE);
namedWindow(output_title, WINDOW_AUTOSIZE);
namedWindow(match_title, WINDOW_AUTOSIZE);
imshow(input_title, src);
const char* trackbar_title = "Match Algo Type : ";
createTrackbar(trackbar_title, output_title, &match_method, max_track, Match_Demo);
Match_Demo(0, 0);
waitKey(0);
return 0;
}
void Match_Demo(int, void*) {
int width = src.cols - temp.cols + 1;
int height = src.rows - temp.rows + 1;
Mat result(width, height, CV_32FC1);
matchTemplate(src, temp, result, match_method, Mat());
normalize(result, result, 0, 1, NORM_MINMAX, -1, Mat());
Point minLoc;
Point maxLoc;
double min, max;
src.copyTo(dst);
Point tempLoc;
minMaxLoc(result, &min, &max, &minLoc, &maxLoc, Mat());
if (match_method == TM_SQDIFF || match_method == TM_SQDIFF_NORMED) {
tempLoc = minLoc;
}
else {
tempLoc = maxLoc;
}
rectangle(dst, Rect(tempLoc.x, tempLoc.y, temp.cols, temp.rows), Scalar(0, 0, 255), 2, 8);
rectangle(result, Rect(tempLoc.x, tempLoc.y, temp.cols, temp.rows), Scalar(0, 0, 255), 2, 8);
imshow(output_title, result);
imshow(match_title, dst);
}
轮廓发现是基于图像边缘提取的基础寻找对象轮廓的方法,所以边缘提取的阈值选定会影响最终轮廓发现结果。
在一个多边形边缘或者内部任意两个点的连线都包含在多边形边界或内部
第一个参数,输入图像,非0的像素被看成1,0的像素值保持不变,8-bit
第二个参数,全部发现的轮廓对象
第三个参数,图像的拓扑结构,可选,该轮廓发现算法正是基于图像拓扑结构实现
第四个参数,轮廓返回的模式
第五个参数,发现方法
第六个参数,轮廓像素的位移,默认(0, 0)没有位移
第一个参数,输出图像
第二个参数,全部发现的轮廓对象
第三个参数,轮廓索引号
第四个参数,绘制时候颜色
第五个参数,绘制线宽
第六个参数,线的类型LINE_8
第七个参数,拓扑结构图
第八个参数,最大层数,0只绘制当前的,1表示绘制绘制当前及其内嵌的轮廓
第九个参数,轮廓位移,可选
第一个参数,输入候选点,来自fingdContours
第二个参数,凸包
第三个参数,true为顺时针方向
第四个参数,true表示返回点个数,如果第二个参数是vector则自动忽略
基于RDP算法实现,目的是减少多边形轮廓点数
第一个参数,输入曲线,数据类型可以为vector。
第二个参数,输出折线,数据类型可以为vector。
第三个参数,判断点到相对应的line segment 的距离的阈值。(距离大于此阈值则舍弃,小于此阈值则保留,epsilon越小,折线的形状越“接近”曲线。)
第四个参数,曲线是否闭合的标志位。
计算并返回指定点集或灰度图像的非零像素的右边界/最小垂直边界矩形。
参数,输入存储在vector或Mat中的灰度图像或2D点集
计算并返回指定点集的最小区域边界矩形(可能已旋转)。
参数,二维点的输入向量,存储在vector或Mat中
#include
#include
using namespace std;
using namespace cv;
Mat src, dst;
char input_title[] = "input image";
char output_title[] = "result image";
int threshold_value = 100;
int threshold_max = 255;
void Demo_contours(int, void*);
int main()
{
src = imread("D:/1.jpg");
if (!src.data)
return -1;
namedWindow(input_title, WINDOW_AUTOSIZE);
namedWindow(output_title, WINDOW_AUTOSIZE);
imshow(input_title, src);
cvtColor(src, src, COLOR_BGR2GRAY);
createTrackbar("Threshold Value : ", output_title, &threshold_value, threshold_max, Demo_contours);
Demo_contours(0, 0);
waitKey(0);
return 0;
}
void Demo_contours(int, void*) {
Mat canny_output;
vector<vector<Point>> contours;
vector<Vec4i> hierachy;
Canny(src, canny_output, threshold_value, threshold_value * 2, 3, false);
findContours(canny_output, contours, hierachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));
dst = Mat::zeros(src.size(), CV_8UC3);
RNG rng(12345);
for (size_t i = 0; i < contours.size(); i++) {
Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
drawContours(dst, contours, i, color, 2, 8, hierachy, 0, Point(0, 0));
}
imshow(output_title, dst);
}
#include
#include
using namespace std;
using namespace cv;
Mat src, dst, src_gray;
char input_title[] = "input image";
char output_title[] = "result image";
int threshold_value = 100;
int threshold_max = 255;
RNG rng(12345);
void Threshold_Callback(int, void*);
int main()
{
src = imread("D:/1.jpg");
if (!src.data)
return -1;
namedWindow(input_title, WINDOW_AUTOSIZE);
namedWindow(output_title, WINDOW_AUTOSIZE);
imshow(input_title, src);
const char* trackbar_label = "Threshold : ";
cvtColor(src, src_gray, COLOR_BGR2GRAY);
blur(src_gray, src_gray, Size(3, 3), Point(-1, -1), BORDER_DEFAULT);
createTrackbar(trackbar_label, output_title, &threshold_value, threshold_max, Threshold_Callback);
Threshold_Callback(0, 0);
waitKey(0);
return 0;
}
void Threshold_Callback(int, void*) {
Mat bin_output;
vector<vector<Point>> contours;
vector<Vec4i> hierachy;
threshold(src_gray, bin_output, threshold_value, threshold_max, THRESH_BINARY);
findContours(bin_output, contours, hierachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));
vector<vector<Point>> convexhulls(contours.size());
for (size_t i = 0; i < contours.size(); i++) {
convexHull(contours[i], convexhulls[i], false, true);
}
dst = Mat::zeros(src.size(), CV_8UC3);;
for (size_t k = 0; k < contours.size(); k++) {
Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
drawContours(dst, contours, k, color, 2, LINE_8, hierachy, 0, Point(0, 0));
drawContours(dst, convexhulls, k, color, 2, LINE_8, Mat(), 0, Point(0, 0));
}
imshow(output_title, dst);
}
#include
#include
using namespace std;
using namespace cv;
Mat src, dst, src_gray, draw_img;
char input_title[] = "input image";
char output_title[] = "result image";
int threshold_value = 100;
int threshold_max = 255;
RNG rng(12345);
void Threshold_Callback(int, void*);
int main()
{
src = imread("D:/1.jpg");
if (!src.data)
return -1;
namedWindow(input_title, WINDOW_AUTOSIZE);
namedWindow(output_title, WINDOW_AUTOSIZE);
imshow(input_title, src);
const char* trackbar_label = "Threshold : ";
cvtColor(src, src_gray, COLOR_BGR2GRAY);
blur(src_gray, src_gray, Size(3, 3), Point(-1, -1), BORDER_DEFAULT);
createTrackbar(trackbar_label, output_title, &threshold_value, threshold_max, Threshold_Callback);
Threshold_Callback(0, 0);
waitKey(0);
return 0;
}
void Threshold_Callback(int, void*) {
Mat binary_output;
vector<vector<Point>> contours;
vector<Vec4i> hierachy;
threshold(src_gray, binary_output, threshold_value, threshold_max, THRESH_BINARY);
imshow("binary image", binary_output);
findContours(binary_output, contours, hierachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(-1, -1));
vector<vector<Point>> contours_ploy(contours.size());
vector<Rect> ploy_rects(contours.size());
vector<Point2f> ccs(contours.size());
vector<float> radius(contours.size());
vector<RotatedRect> minRects(contours.size());
vector<RotatedRect> myellipse(contours.size());
for (size_t i = 0; i < contours.size(); i++) {
approxPolyDP(Mat(contours[i]), contours_ploy[i], 3, true);
ploy_rects[i] = boundingRect(contours_ploy[i]);
minEnclosingCircle(contours_ploy[i], ccs[i], radius[i]);
if (contours_ploy[i].size() > 5) {
myellipse[i] = fitEllipse(contours_ploy[i]);
minRects[i] = minAreaRect(contours_ploy[i]);
}
}
src.copyTo(draw_img);
Point2f pts[4];
for (size_t t = 0; t < contours.size(); t++) {
Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
rectangle(draw_img, ploy_rects[t], color, 2, 8);
circle(draw_img, ccs[t], radius[t], color, 2, 8);
if (contours_ploy[t].size() > 5) {
ellipse(draw_img, myellipse[t], color, 1, 8);
minRects[t].points(pts);
for (int r = 0; r < 4; r++) {
line(draw_img, pts[r], pts[(r + 1) % 4], color, 1, 8);
}
}
}
imshow(output_title, draw_img);
}
通常描述了该图像形状的全局特征,并提供了大量的关于该图像不同类型的几何特性信息,比如大小、位置、方向及形状等。一阶矩与形状有关,二阶矩显示曲线围绕直线平均值的扩展程度,三阶矩则是关于平均值的对称性的测量。由二阶矩和三阶矩可以导出一组共7个不变矩。而不变矩是图像的统计特性,满足平移、伸缩、旋转均不变的不变性,在图像识别领域得到了广泛的应用。
矩的计算
第一个参数,输入数据
第二个参数,是否为二值图像
计算轮廓面积
第一个参数,输入轮廓数据
第二个参数,默认false,返回绝对值
计算轮廓长度
第一个参数,输入曲线数据
第二个参数,是否是封闭曲线
#include
#include
using namespace std;
using namespace cv;
Mat src, dst, src_gray;
char input_title[] = "input image";
char output_title[] = "result image";
int threshold_value = 80;
int threshold_max = 255;
RNG rng(12345);
void Threshold_Callback(int, void*);
int main()
{
src = imread("D:/1.jpg");
if (!src.data)
return -1;
namedWindow(input_title, WINDOW_AUTOSIZE);
namedWindow(output_title, WINDOW_AUTOSIZE);
imshow(input_title, src);
const char* trackbar_label = "Threshold : ";
cvtColor(src, src_gray, COLOR_BGR2GRAY);
blur(src_gray, src_gray, Size(3, 3), Point(-1, -1), BORDER_DEFAULT);
createTrackbar(trackbar_label, output_title, &threshold_value, threshold_max, Threshold_Callback);
Threshold_Callback(0, 0);
waitKey(0);
return 0;
}
void Threshold_Callback(int, void*) {
Mat canny_output;
vector<vector<Point>> contours;
vector<Vec4i> hierachy;
Canny(src_gray, canny_output, threshold_value, threshold_value * 2, 3, false);
findContours(canny_output, contours, hierachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));
vector<Moments> contours_moments(contours.size());
vector<Point2f> ccs(contours.size());
for (size_t i = 0; i < contours.size(); i++) {
contours_moments[i] = moments(contours[i]);
ccs[i] = Point(static_cast<float>(contours_moments[i].m10 / contours_moments[i].m00), static_cast<float>(contours_moments[i].m01 / contours_moments[i].m00));
}
Mat drawImg;
src.copyTo(drawImg);
for (size_t i = 0; i < contours.size(); i++) {
Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
cout << "center point x : " << ccs[i].x << " y : " << ccs[i].y << endl;
cout << "contours " << i << " area : " << contourArea(contours[i]) << " arc length : " << arcLength(contours[i], true) << endl;
drawContours(drawImg, contours, i, color, 2, 8, hierachy, 0, Point(0, 0));
circle(drawImg, ccs[i], 2, color, 2, 8);
}
imshow(output_title, drawImg);
}
测试一个点是否在给定的多边形内部、边缘或外部
第一个参数,输入的轮廓
第二个参数,测试点
第三个参数,是否返回距离值,如果是false,1表示在内面,0表示在边界上,-1表示在外部,true返回实际距离,返回类型是double类型
#include
#include
using namespace std;
using namespace cv;
int main()
{
const int r = 100;
Mat src = Mat::zeros(r * 4, r * 4, CV_8UC1);
vector<Point2f> vert(6);
vert[0] = Point(3 * r / 2, static_cast<int>(1.34 * r));
vert[1] = Point(1 * r, 2 * r);
vert[2] = Point(3 * r / 2, static_cast<int>(2.866 * r));
vert[3] = Point(5 * r / 2, static_cast<int>(2.866 * r));
vert[4] = Point(3 * r, 2 * r);
vert[5] = Point(5 * r / 2, static_cast<int>(1.34 * r));
for (int i = 0; i < 6; i++) {
line(src, vert[i], vert[(i + 1) % 6], Scalar(255), 3, 8, 0);
}
vector<vector<Point>> contours;
vector<Vec4i> hierachy;
Mat csrc;
src.copyTo(csrc);
findContours(csrc, contours, hierachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));
Mat raw_dist = Mat::zeros(csrc.size(), CV_32FC1);
for (int row = 0; row < raw_dist.rows; row++) {
for (int col = 0; col < raw_dist.cols; col++) {
double dist = pointPolygonTest(contours[0], Point2f(static_cast<float>(col), static_cast<float>(row)), true);
raw_dist.at<float>(row, col) = static_cast<float>(dist);
}
}
double minValue, maxValue;
minMaxLoc(raw_dist, &minValue, &maxValue, 0, 0, Mat());
Mat drawImg = Mat::zeros(src.size(), CV_8UC3);
for (int row = 0; row < drawImg.rows; row++) {
for (int col = 0; col < drawImg.cols; col++) {
float dist = raw_dist.at<float>(row, col);
if (dist > 0) {
drawImg.at<Vec3b>(row, col)[0] = (uchar)(abs(1.0 - dist / maxValue) * 255);
}
else if (dist < 0) {
drawImg.at<Vec3b>(row, col)[2] = (uchar)(abs(1.0 - dist / minValue) * 255);
}
else {
drawImg.at<Vec3b>(row, col)[0] = (uchar)(abs(255 - dist));
drawImg.at<Vec3b>(row, col)[1] = (uchar)(abs(255 - dist));
drawImg.at<Vec3b>(row, col)[2] = (uchar)(abs(255 - dist));
}
}
}
namedWindow("input_title", WINDOW_AUTOSIZE);
namedWindow("output_title", WINDOW_AUTOSIZE);
imshow("input_title", src);
imshow("output_title", drawImg);
waitKey(0);
return 0;
}
图像分割的目的是将图像中像素根据一定的规则分为若干个cluster集合,每个集合包含一类像素
算法分类
监督学习方法
无监督学习方法:图像分割算法多数是无监督学习方法-KMeans
基于倒角距离,计算图像中像素点到最近零像素点的距离,也就是零像素点的最短距离。
基于浸泡理论实现
第一个参数,必须是一个8bit 单通道的图像
第二个参数,输出图像
第三个参数,可选的输出2D标签数组(离散Voronoi图)。它的类型为 CV_32SC1,大小与src相同
第四个参数,距离的类型。它可以是 CV_DIST_L1, CV_DIST_L2或 CV_DIST_C。
第五个参数,距离变换蒙版的大小。它可以是3、5或 CV_DIST_MASK_PRECISE(仅第一个函数支持后一个选项)。在CV_DIST_L1 或 CV_DIST_C 距离类型的情况下,该参数被强制为3
第六个参数,构建的标签数组的类型。如果labelType == DIST_LABEL_CCOMP,则src中的`每个零连接分量(以及所有最接近连接分量的非零像素)将被分配相同的标签。如果labelType == DIST_LABEL_PIXEL,则每个零像素(以及所有与其最接近的非零像素)将获得自己的标签。
第一个参数 ,必须是一个8bit 3通道彩色图像矩阵序列
第二个参数 ,单通道整形(IPL_DEPTH_32S),具有相同维数(x,y)的图像
#include
#include
#include
using namespace std;
using namespace cv;
int main()
{
char input_win[] = "input image";
char watershed_win[] = "watershed segmentation demo";
Mat src = imread("D:/1.jpg");
// Mat src = imread("D:/kuaidi.jpg");
if (src.empty()) {
printf("could not load image...\n");
return -1;
}
namedWindow(input_win, WINDOW_AUTOSIZE);
imshow(input_win, src);
// 1. change background
for (int row = 0; row < src.rows; row++) {
for (int col = 0; col < src.cols; col++) {
if (src.at<Vec3b>(row, col) == Vec3b(255, 255, 255)) {
src.at<Vec3b>(row, col)[0] = 0;
src.at<Vec3b>(row, col)[1] = 0;
src.at<Vec3b>(row, col)[2] = 0;
}
}
}
namedWindow("black background", WINDOW_AUTOSIZE);
imshow("black background", src);
// sharpen
Mat kernel = (Mat_<float>(3, 3) << 1, 1, 1, 1, -8, 1, 1, 1, 1);
Mat imgLaplance;
Mat sharpenImg = src;
filter2D(src, imgLaplance, CV_32F, kernel, Point(-1, -1), 0, BORDER_DEFAULT);
src.convertTo(sharpenImg, CV_32F);
Mat resultImg = sharpenImg - imgLaplance;
resultImg.convertTo(resultImg, CV_8UC3);
imgLaplance.convertTo(imgLaplance, CV_8UC3);
imshow("sharpen image", resultImg);
// src = resultImg; // copy back
// convert to binary
Mat binaryImg;
cvtColor(src, resultImg, COLOR_BGR2GRAY);
threshold(resultImg, binaryImg, 40, 255, THRESH_BINARY | THRESH_OTSU);
imshow("binary image", binaryImg);
Mat distImg;
distanceTransform(binaryImg, distImg, DIST_L1, 3, 5);
normalize(distImg, distImg, 0, 1, NORM_MINMAX);
imshow("distance result", distImg);
// binary again
threshold(distImg, distImg, .4, 1, THRESH_BINARY);
Mat k1 = Mat::ones(13, 13, CV_8UC1);
erode(distImg, distImg, k1, Point(-1, -1));
imshow("distance binary image", distImg);
// markers
Mat dist_8u;
distImg.convertTo(dist_8u, CV_8U);
vector<vector<Point>> contours;
findContours(dist_8u, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point(0, 0));
// create makers
Mat markers = Mat::zeros(src.size(),CV_32SC1);
cout << markers.type() << endl;
for (size_t i = 0; i < contours.size(); i++) {
drawContours(markers, contours, static_cast<int>(i), Scalar::all(static_cast<int>(i) + 1), -1);
}
circle(markers, Point(5, 5), 3, Scalar(255, 255, 255), -1);
//imshow只能对像素值处于0-255范围内的图像进行存储和显示,所以CV_32S格式需要转换成CV_8U才能进行操作
//imshow("my markers", markers * 1000);
// perform watershed
watershed(src, markers);
Mat mark = Mat::zeros(markers.size(), CV_8UC1);
markers.convertTo(mark, CV_8UC1);
bitwise_not(mark, mark, Mat());
imshow("watershed image", mark);
// generate random color
vector<Vec3b> colors;
for (size_t i = 0; i < contours.size(); i++) {
int r = theRNG().uniform(0, 255);
int g = theRNG().uniform(0, 255);
int b = theRNG().uniform(0, 255);
colors.push_back(Vec3b((uchar)b, (uchar)g, (uchar)r));
}
// fill with color and display final result
Mat dst = Mat::zeros(markers.size(), CV_8UC3);
for (int row = 0; row < markers.rows; row++) {
for (int col = 0; col < markers.cols; col++) {
int index = markers.at<int>(row, col);
if (index > 0 && index <= static_cast<int>(contours.size())) {
dst.at<Vec3b>(row, col) = colors[index - 1];
}
else {
dst.at<Vec3b>(row, col) = Vec3b(0, 0, 0);
}
}
}
imshow("Final Result", dst);
waitKey(0);
return 0;
}