C++知识点
- 1. .h文件和.cpp文件并不是名字要一一对应(只是一般默认是这样写)
- 2. cv::Rect rect是个矩形
- 3. 进行操作时,cv::Mat相当于指针可以覆盖
- 4. 输入一个和输入一组是不一样的
- 5. Vec3b 获取RGB三通道的值
- 5. 补充,读取单通道和三通道的像素值
- 6. 轮廓代码
- 7. vs中打开控制台,使用cout<< X <<endl;打印参数
- 8. 开操作
- 9. 梯度幅值图
- 10. 自己写的滑动窗口【矩形在原图上滑动】
- 10.1 补充,把原图分成X*X大小的块,分区域二值化,再贴到一张同尺寸图上
- 11. release可以运行,debug不能运行时,试试
- 12. 遍历矩形时,容易越界,行列正确的代码函数
- 13. 两个矩形交集和并集的操作
- 14. 重写大津法,图片中低于设定阈值的区域不参与运算
1. .h文件和.cpp文件并不是名字要一一对应(只是一般默认是这样写)
- .h文件用来声明函数,.cpp文件用来定义函数,调用函数(图1)
- 需要注意:cpp中的函数,前面需要写**类::**,来证明这是哪个类函数下面的(图2)
- C++中,函数中不能再写一个函数(图3)
2. cv::Rect rect是个矩形
含有属性:左上角的xy点坐标和宽高属性
2.1 可以直接在图片上找到矩形框出的位置
cv::Mat src,
cv::Mat roi = src(rect)
3. 进行操作时,cv::Mat相当于指针可以覆盖
cv::cvtColor(roi, roi, COLOR_RGB2GRAY);
4. 输入一个和输入一组是不一样的
说明:比如drawContours函数,需要输入的参数是一个vector容器,直接给他一个元素是不行的,需要把元素放在容器里再传进去
5. Vec3b 获取RGB三通道的值
- Vec3b 是一个包含三个无符号字符(8位)的结构体,通常用于表示 BGR(蓝绿红)颜色空间中的像素值
result.at <Vec3b> (y, x)代表的是对 result 矩阵对象的访问操作。
at 是一个成员函数,用于访问矩阵中指定位置的元素。在这里,y 和 x 分别表示行和列的索引
---访问的是该位置的RGB三个值
cv::Mat MainWindow::px_subtract(cv::Mat src, cv::Mat temple, cv::Mat result)
{
for (int y = 0; y < src.rows; y++) // ------------3----逐像素相减(用异常图-模版图)
{
for (int x = 0; x < src.cols; x++)
{
Vec3b pixel1 = src.at<Vec3b>(y, x);
Vec3b pixel2 = temple.at<Vec3b>(y, x);
result.at<Vec3b>(y, x) = Vec3b(abs(pixel1[0] - pixel2[0]), abs(pixel1[1] - pixel2[1]), abs(pixel1[2] - pixel2[2]));
}
}
return result;
}
5. 补充,读取单通道和三通道的像素值
5.1 单通道uchar存储,at.<cv::uchar>访问
5.2 三通道cv::Vec3b存储,at.<cv::Vec3b>访问
单通道灰度图
如果是一张单通道的灰度图像,你可以使用cv::Scalar来存储像素值,并使用uchar作为像素类型,at<uchar>读取像素值。
cv::Mat image = cv::imread("gray_example.jpg", cv::IMREAD_GRAYSCALE); // 读取灰度图像
cv::Scalar pixel = image.at<uchar>(y, x); // 读取(x, y)位置的像素值
RGB三通道灰度值
cv::Vec3b 是 OpenCV 库中的一个数据类型,用于表示一个3通道的字节型像素值。在计算机视觉和图像处理中,图像通常由像素组成,每个像素可以有多个通道,例如RGB通道。cv::Vec3b 就是一个用于存储RGB三个通道值的结构。
在cv::Vec3b中:
第一个通道(b)通常表示蓝色通道的强度。
第二个通道(g)通常表示绿色通道的强度。
第三个通道(r)通常表示红色通道的强度。
每个通道的值通常是一个字节(8位),范围从0到255。
cv::Mat image = cv::imread("example.jpg"); // 读取图像
cv::Vec3b pixel = image.at<cv::Vec3b>(y, x); // 读取(x, y)位置的像素值
// 修改像素值
pixel[0] = 255; // 蓝色通道
pixel[1] = 255; // 绿色通道
pixel[2] = 255; // 红色通道
image.at<cv::Vec3b>(y, x) = pixel; // 将修改后的像素值写回图像
6. 轮廓代码
查找轮廓
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(binary, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
画轮廓的函数
cv::Mat result1 = Mat::zeros(binary.size(), binary.type());
drawContours(result1, new2_contours, -1, Scalar(255), 2);
imshow("去除外边缘10px绘制轮廓-4", result1);
找外接矩
void MainWindow::contours_Rectangle(vector<vector<cv::Point>> all_contours, vector<cv::Rect> &all_Rectangle) {
for (size_t i = 0; i < all_contours.size(); i++) {
Rect rect = boundingRect(all_contours[i]); //这个函数指的是这个轮廓的最小外接矩
all_Rectangle.push_back(rect);
}
}
画矩形的函数cv::rectangle
cv::Mat rectangle_img = cv::Mat::zeros(edge.size(), CV_8UC3); //能错在这个地方CV_8UC3,呜呜呜....
for (size_t i = 0; i < canny_rectangle.size(); i++) {
cv::rectangle(rectangle_img, canny_rectangle[i], cv::Scalar(0, 255, 0), 2); //画轮廓和画矩形的函数不一样
}
cv::imshow("Bounding Rectangles", rectangle_img);
cv::waitKey(0);
上面找到的轮廓的坐标是基于小图的,而不是基于原图的,要想在原图上显示,需要加一个偏移量
for (int i = 0; i < new4_contours.size(); i++)
{
for (int j = 0; j < new4_contours[i].size(); j++)
{
new4_contours[i][j].x = new4_contours[i][j].x + rect.x;
new4_contours[i][j].y = new4_contours[i][j].y + rect.y;
}
}
7. vs中打开控制台,使用cout<< X <<endl;打印参数
8. 开操作
//定义卷积核
cv::Size kernel1 = cv::Size(1, 1);
cv::Mat kernel_1 = cv::getStructuringElement(cv::MORPH_RECT, kernel1, cv::Point(-1, -1));
cv::Mat diff;
cv::morphologyEx(srcImage_, diff, cv::MORPH_OPEN, kernel_1); // 对检测图开操作,先腐蚀后膨胀
9. 梯度幅值图
void MainWindow::grad_1(cv::Mat src, cv::Mat &grad_map) {
if (src.empty())
{
cout << "无法读取图像" << endl;//改在日志中提示
}
// 创建输出图像
Mat grad_x, grad_y, abs_grad_x, abs_grad_y;
// 计算x方向梯度
Sobel(src, grad_x, CV_16S, 1, 0, 3, 1, 0, BORDER_DEFAULT);
// 计算y方向梯度
Sobel(src, grad_y, CV_16S, 0, 1, 3, 1, 0, BORDER_DEFAULT);
// 计算绝对值
convertScaleAbs(grad_x, abs_grad_x);
convertScaleAbs(grad_y, abs_grad_y);
// 合并梯度
addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 0, grad_map);
// 显示结果
/*mshow("原始图像", src);
imshow("梯度幅值图", grad_map);
waitKey(0);*/
//return grad_map;
}
10. 自己写的滑动窗口【矩形在原图上滑动】
void MainWindow::slide_window(cv::Mat src, cv::Rect rect, vector<cv::Mat> &vector_map)
{
int h = 0;// 制定循环的次数
int w = 0;
(src.rows % rect.height == 0) ? (h = src.rows / rect.height) : (h = src.rows / rect.height + 1);
(src.cols % rect.width == 0) ? (w = src.cols / rect.width) : (w = src.cols / rect.width + 1);
for (size_t i = 0; i < h; i++)
{
if(rect.y + rect.height > src.rows) //先判断矩形高度有没有超过
{
rect.height = src.rows - rect.y;
}
for (size_t j = 0; j < w; j++)
{
if (rect.x + rect.width > src.cols) //判断矩形宽度有没有超过
{
rect.width = src.cols - rect.x;
}
cv::Mat temp = src(rect);
vector_map.push_back(temp);
rect.x = rect.x + rect.width;
}
rect.x = 0;
rect.y = rect.y + rect.height;
}
}
10.1 补充,把原图分成X*X大小的块,分区域二值化,再贴到一张同尺寸图上
这个函数需要注意的是,dst是接收返回的二值化图像,所以引用这个函数钱,需要定义的dst是单通道的图
cv::Mat dst(src.size(), CV_8UC1); ******非常重要的知识点
void localThreshold(cv::Mat& src, cv::Mat& dst, int size)
{
if (src.empty() || src.size() != dst.size() || size == 0)
{
ClassErrorLog("local threshold fail!");
FlushLog();
return;
}
cv::Mat subThreshImg; //切片区域阈值结果
cv::Rect subRect; //切片区域范围
int dW = src.cols / size;
int dH = src.rows / size;
int resW = src.cols - (size * dW);
int resH = src.rows - (size * dH);
for (int i = 0; i < size; i++)
{
for (int j = 0; j < size; j++)
{
subRect.x = j * dW;
subRect.y = i * dH;
subRect.width = dW;
subRect.height = dH;
if (j == (size - 1))
subRect.width = dW + resW;
if (i == (size - 1))
subRect.height = dH + resH;
int threshold = AlgorithmModule::ImageProcessAlg::customOtsu(src(subRect), 10);
//这是先设置了一个低的阈值
//threshold = threshold > curPaintPeelingDetectParams_.minThresh ? threshold : 255;
cv::threshold(src(subRect), subThreshImg, threshold, 255, cv::THRESH_BINARY);
subThreshImg.copyTo(dst(subRect));
}
}
return;
}
11. release可以运行,debug不能运行时,试试
12. 遍历矩形时,容易越界,行列正确的代码函数
//rows 行数,代表高
//cols 列数,代表宽
for (size_t i = 0; i < image_gray.rows; i++)
{
for (size_t j = 0; j < image_gray.cols; j++)
{
uchar pixel = image_gray.at<uchar>(i, j);
}
}
12.1 找灰度图像素值中位数代码
cvtColor(srcImage_, image_gray, COLOR_BGR2GRAY);//------------2----检测图像转换为灰度图像
vector<uchar> pixel_value;
for (size_t i = 0; i < image_gray.rows; i++)
{
for (size_t j = 0; j < image_gray.cols; j++)
{
uchar pixel = image_gray.at<uchar>(i, j);
pixel_value.push_back(pixel);
}
}
// 对vector进行排序
std::sort(pixel_value.begin(), pixel_value.end());
//计算中位数
int n = pixel_value.size();
cout << "总数为:" << n << endl;
int median = (int)pixel_value[n / 2];
std::cout << "中位数: " << median << std::endl;
cv::Mat srcImage_ = cv::imread("./Solution/HSV/1212/foreign-5/foreign_src-S/foreign_src-S.jpg"); // 这是输入的检测图
cv::Mat templateImage_ = cv::imread("./Solution/1212/foreign-1/foreign_template.jpg"); //这是模板图
cv::Mat image_gray;
cvtColor(srcImage_, image_gray, COLOR_BGR2GRAY);
vector<uchar> pixel_value;
for (size_t i = 0; i < image_gray.rows; i++)
{
for (size_t j = 0; j < image_gray.cols; j++)
{
uchar pixel = image_gray.at<uchar>(i, j);
pixel_value.push_back(pixel);
}
}
// 对vector进行排序
std::sort(pixel_value.begin(), pixel_value.end());
//计算中位数
int n = pixel_value.size();
cout << "总数为:" << n << endl;
int median = (int)pixel_value[n / 2];
std::cout << "中位数: " << median << std::endl;
cv::Mat binary;
threshold(image_gray, binary, median+20, 255, THRESH_BINARY_INV);
imshow("binary", binary);
waitKey(0);
13. 两个矩形交集和并集的操作
cv::Rect rect1;
cv::Rect rect2;
如果两个矩形相交,怎么判断?取交集,看看面积是不是大于0
cv::Rect jiaoji = rect1 & rect2;
if jiaoji.area > 0;就代表是有相交区域;
取两个矩形的并集
cv::Rect bingji = rect1 | rect2;
14. 重写大津法,图片中低于设定阈值的区域不参与运算
大津法的核心思想是在灰度直方图中寻找一个最佳的阈值,将图像分为两个类别使得类别内的差异最小,类别间的差异最大
//bgVal是设置的阈值,低于阈值的都不统计
int ImageProcessAlg::customOtsu(cv::Mat& src, uchar bgVal)
{
int threshold = 0;
if (src.empty())
return 0;
double srcPixNum = 0; //需统计的像素数量
const int Grayscale = 256; //灰度级数
int graynum[Grayscale] = { 0 }; //各灰度级像素数量
int height = src.rows;
int width = src.cols;
if (src.isContinuous())
{
width *= height;
height = 1;
}
for (int i = 0; i < height; ++i)
{
const uchar* ptr = src.ptr<uchar>(i);
for (int j = 0; j < width; ++j)
{ //直方图统计
if (ptr[j] > bgVal) //剔除灰度(背景)
{
graynum[ptr[j]]++;
++srcPixNum;
}
}
}
double P[Grayscale] = { 0 };
double PK[Grayscale] = { 0 };
double MK[Grayscale] = { 0 };
double sumTmpPK = 0, sumTmpMK = 0;
for (int i = 0; i < Grayscale; ++i)
{
P[i] = graynum[i] / srcPixNum; //每个灰度级出现的概率
PK[i] = sumTmpPK + P[i]; //概率累计和
sumTmpPK = PK[i];
MK[i] = sumTmpMK + i*P[i]; //灰度级的累加均值
sumTmpMK = MK[i];
}
//计算类间方差
double Var = 0;
for (int k = 0; k < Grayscale; ++k)
{
double variance = (MK[Grayscale - 1] * PK[k] - MK[k])*(MK[Grayscale - 1] * PK[k] - MK[k]) / (PK[k] * (1 - PK[k]));
if (variance > Var)
{
Var = variance;
threshold = k;
}
}
return threshold;
}