opencv操作图像像素和通道

老师让我评价一下别人的一个跟踪效果,只有带跟踪框的视频,所以需要检测这个框,用了下投影,最早用matlab写的一个脚本,很简单,转到opencv里反而有些麻烦,老不用忘得很厉害,昨天搞了2个小时可以运行了,中间用到图像像素和通道的操作,顺便做个总结: 灰度图像,加的红色框,我想做的是检测到这个红色框的四个顶点的位置,比如下面这个图:

示例

原图是灰度图像,这里标记的时候使用的是红色框,所以在保存成视频的时候是扩展成彩色了的,灰色部分三个通道复制扩展。

思路

因为是红色框,所以打算用红色通道减去绿色通道(蓝色也可以),这样剪掉以后剩下的就主要是框了,然后分别沿着x和y方向做投影,投影的两个最大值就是要求坐标了,这里画的是一个像素的线,所以出来确实是这样的。如果不是一个像素的线可能还要做其他处理。 这样基本就可以了:

通道相减

投影

这里只要简单取两个最大值就可以了,就是坐标。这里画的图都是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;
}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏生信小驿站

Python数据处理从零开始----第四章(可视化)(7)(多图合并)目录正文

=========================================================

851
来自专栏一心无二用,本人只专注于基础图像算法的实现与优化。

SSE图像算法优化系列十一:使用FFT变换实现图像卷积。

  本文重点主要不在于FFT的SSE优化,而在于使用FFT实现快速卷积的相关技巧和过程。   关于FFT变换,有很多参考的代码,特别是对于长...

5389
来自专栏机器之心

教程 | 如何使用TensorFlow中的高级API:Estimator、Experiment和Dataset

选自Medium 作者:Peter Roelants 机器之心编译 参与:李泽南、黄小天 近日,背景调查公司 Onfido 研究主管 Peter Roelant...

6807
来自专栏bboysoul

1067: 成绩评估

描述:我们知道,高中会考是按等级来的。90~100为A; 80~89为B; 70~79为C; 60~69为D; 0~59为E。 编写一个程序,对输入的...

842
来自专栏程序生活

Leetcode-Easy 437. Path Sum III

101. Symmetric Tree 描述: 给定一个二叉树和一个目标和,求满足和为目标值的路径个数 ? 思路: dfs 深度优先搜索 ...

3544
来自专栏Unity Shader

Shader初学笔记:等值线

http://www.cnblogs.com/lpcoder/p/7103634.html

6055
来自专栏云霄雨霁

子字符串查找----各种算法总结

2380
来自专栏数据结构与算法

1038 一元三次方程求解

1038 一元三次方程求解 2001年NOIP全国联赛提高组 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 白银 Silver ...

2848
来自专栏Petrichor的专栏

leetcode: 85. Maximal Rectangle

From LeetCode 笔记系列 18 Maximal Rectangle [学以致用]:

2273
来自专栏bboysoul

1475: C语言实验题――一元二次方程 II

描述:求一元二次方程ax2+bx+c=0的解。a,b,c为任意实数。 输入:输入数据有一行,包括a b c的值 输出:按以下格式输出方程的根x1和x2。x1...

1213

扫码关注云+社区

领取腾讯云代金券