老师让我评价一下别人的一个跟踪效果,只有带跟踪框的视频,所以需要检测这个框,用了下投影,最早用matlab写的一个脚本,很简单,转到opencv里反而有些麻烦,老不用忘得很厉害,昨天搞了2个小时可以运行了,中间用到图像像素和通道的操作,顺便做个总结:
灰度图像,加的红色框,我想做的是检测到这个红色框的四个顶点的位置,比如下面这个图:
![](https://img.haomeiwen.com/i5252065/9a0034ce0cc34dde.png)
原图是灰度图像,这里标记的时候使用的是红色框,所以在保存成视频的时候是扩展成彩色了的,灰色部分三个通道复制扩展。
思路
因为是红色框,所以打算用红色通道减去绿色通道(蓝色也可以),这样剪掉以后剩下的就主要是框了,然后分别沿着x和y方向做投影,投影的两个最大值就是要求坐标了,这里画的是一个像素的线,所以出来确实是这样的。如果不是一个像素的线可能还要做其他处理。
这样基本就可以了:
![](https://img.haomeiwen.com/i5252065/94bac3c9e3383b78.png)
![](https://img.haomeiwen.com/i5252065/b315dc41c7826bed.png)
这里只要简单取两个最大值就可以了,就是坐标。这里画的图都是matlab里面画的,写起来也很简单,opencv的话要分离通道,投影的函数也要自己写。
opencv里操作通道。
这里主要是两个函数,一个是分离通道split,一个是合并通道merge。
split()
有两个重载函数:
void split(const Mat& src, Mat* mvbegin);
split(InputArray m, OutputArrayOfArrays mv);
第一个参数接受要分离的多通道数组,第二个参数填输出的数组或者vector容器,最新版的opencv和c++的话,建议把Mat分离到vector<Mat>里。
Mat img;
vector<Mat> channels;
split(img, channels);
cv::split(img, channels);
Mat r_y;
r_y = channels[2] - channels[1];
用起来很简单,要注意opencv里是BGR通道,其他的就没什么了。
merge()
和split对应的,刚好是相反的操作:把多个数组合并成一个多通道数组。
void merge(const Mat*mv,size_t count,OutputArray dst);
void merge(IputArrayOfArrays mv,OutputArray dst);
和前面的一样,如果要合并,可以直接这样:merge(channels,img);
还是比较简单的。
opencv里访问像素
opencv提供了三中访问像素的方法:指针访问,迭代器访问。动态地址计算。
三中访问方式速度不一样,debug模式下差异明显,指针最快,其他两个差不多,迭代器略快于动态地址计算。release模式下差异就没什么了。
以前照着浅墨的书写过,放在下面,当时还不了解迭代器,现在就能看懂了,动态地址计算是最接近直观的,和坐标也能对照起来。
下面的函数功能是减少颜色数,先整除再乘。
void ColorReduce_C(Mat img_input, Mat &img_output, int div)
//这里的img_output的引用必不可少,因为如果只做形参,就不能够对传入的这个
// 地址的变量做修改,一开始忘记写了就不对,如果要在函数里修改参数的值,必须用引用把地址传进来
{
img_output = img_input.clone(); //复制实参到临时变量
int rowNum = img_output.rows; //行数
int colNum = img_output.cols*img_output.channels(); //列*通道=每一行元素数
cout << rowNum;
cout << colNum;
for (int i = 0; i < rowNum; i++)
{
uchar*data = img_output.ptr<uchar>(i); //获得每行的首地址
for (int j = 0; j < colNum; j++)
{
data[j] = (data[j] / div)*div; //逐行处理,颜色缩减
}
}
//处理结束
}
//----------------【用迭代器操作像素】-----------------------
//这个我没怎么看懂,看了STL之后再回来看下吧-----------------
void ColorReduce_STL(Mat &img_input, Mat &img_output, int div)
{
img_output = img_input.clone();
Mat_<Vec3b>::iterator it = img_output.begin<Vec3b>();
//初始位置的迭代器
Mat_<Vec3b>::iterator itend = img_output.end<Vec3b>();
//终止位置的迭代器
for (; it != itend; ++it)
{
(*it)[0] = (*it)[0] / div*div;
(*it)[1] = (*it)[1] / div*div;
(*it)[2] = (*it)[2] / div*div;
}
}
//----------------【用“动态地址计算”操作像素】-----------------------
// 简洁明了,符合对像素的认识,通道操作更容易---------------------
void ColorReduce_AT(Mat &img_input, Mat &img_output, int div)
{
img_output = img_input.clone(); //复制实参到临时变量
int row_Num = img_output.rows;
int col_Num = img_output.cols;
//获得行列
for (int i = 0; i < row_Num; i++)
{
for (int j = 0; j < col_Num; j++)
{
img_output.at<Vec3b>(i, j)[0] = img_output.at<Vec3b>(i, j)[0] / div*div;
img_output.at<Vec3b>(i, j)[1] = img_output.at<Vec3b>(i, j)[1] / div*div;
img_output.at<Vec3b>(i, j)[2] = img_output.at<Vec3b>(i, j)[2] / div*div;
//处理三个通道
}
}
}
2018/8/17新增:
上面写的是访问uchar型的数据时是这样,实际上在写算法的时候,经常会遇到需要访问CV_32F型的数据,这个时候用uchar的话肯定就会出现错误的。
对于指针来说,应该使用uchar*data = img_output.ptr<float>(i);
对于at运算符来说,应该使用:img.at<vec3f>(i,j)[0]
这样的形式,当然有可能只是单通道,那么vec3f
这里换成float
,也有可能是双通道,这里就是vec2f
。反正就是根据自己的需求写了,比如最近在做一个去雾的算法的时候需要取两个矩阵对应位置的最大值,我就是这么做的:
cv::Mat min_BRG_32F(cv::Mat &img_32F)
{
int rows = img_32F.rows;
int cols = img_32F.cols;
Mat res = Mat::zeros(Size(cols,rows), CV_32FC1);
//cout << img_32F.size()<<endl;
//cout << res.size() << endl;
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
res.at<float>(i, j) = std::min(img_32F.at<Vec2f>(i, j)[0], img_32F.at<Vec2f>(i, j)[1]);
}
}
return res;
}
获得矩形位置的函数:
输入一个Mat图像,返回的是Rect类型的一个矩形。
写成了头文件,贴在下面,vector的排序是不带索引的,又写了一个带索引的。
#include<opencv2/core/core.hpp>
#include<opencv2\highgui\highgui.hpp>
#include<opencv2\/imgproc\imgproc.hpp>
#include<utility>
#define m_min(a,b) ((a<b)?a:b)
#define m_max(a,b) ((a>b)?a:b)
using std::vector;
using cv::Mat;
using std::pair;
vector<pair<double, int>> sort_index(vector<double> &vec);
bool sort_pair(pair<double, int> &a, pair<double, int> &b);
vector<double> Sum_row(Mat &img);
vector<double> Sum_col(Mat &img);
cv::Rect get_rec_pos(Mat &img);
cv::Rect get_rec_pos(Mat &img)
{
vector<Mat> channels;
split(img, channels);
cv::split(img, channels);
Mat r_y;
r_y = channels[2] - channels[1]; //红色通道和蓝色通道做差
auto sum_row = Sum_row(r_y);
auto sum_col = Sum_col(r_y);
vector<pair<double, int>> sort_row = sort_index(sum_row);
vector<pair<double, int>> sort_col = sort_index(sum_col);
int row_s = m_min((sort_row.end() - 1)->second, (sort_row.end() - 2)->second);
int row_l = m_max((sort_row.end() - 1)->second, (sort_row.end() - 2)->second);
int col_s = m_min((sort_col.end() - 1)->second, (sort_col.end() - 2)->second);
int col_l = m_max((sort_col.end() - 1)->second, (sort_col.end() - 2)->second);
vector<cv::Point2i> Pos;
Pos.push_back(cv::Point2i(row_s, col_s));
Pos.push_back(cv::Point2i(row_l, col_l));
//注意坐标和行列刚好是相反的
cv::Rect res(col_s, row_s, col_l - col_s, row_l - row_s);
return res;
}
vector<pair<double, int>> sort_index(vector<double> &vec)
{
vector<pair<double, int>> res;
for (int i = 0; i < vec.size(); i++)
{
res.push_back(std::make_pair(vec[i], i));
}
sort(res.begin(), res.end(), sort_pair);
return res;
}
//排序规则
bool sort_pair(pair<double, int> &a, pair<double, int> &b)
{
if (a.first <= b.first)
return true;
else
return false;
}
vector<double> Sum_row(Mat &img)
{
int col_num = img.cols;
int row_num = img.rows;
vector<double> sum_row(row_num, 0);
for (int i = 0; i < row_num; i++)
{
for (int j = 0; j < col_num; j++)
{
sum_row[i] += img.at<uchar>(i, j);
}
}
return sum_row;
}
vector<double> Sum_col(Mat &img)
{
int col_num = img.cols;
int row_num = img.rows;
vector<double> sum_col(col_num, 0);
for (int i = 0; i < col_num; i++)
{
for (int j = 0; j < row_num; j++)
{
sum_col[i] += img.at<uchar>(j, i);
}
}
return sum_col;
}
网友评论