zoukankan      html  css  js  c++  java
  • OpenCV2计算机编程手册(二)基于类的图像处理

    1. 在算法设计中使用策略(Strategy)模式

    策略设计模式的目标是将算法封装在类中。因此,可以更容易地替换一个现有的算法,或者组合使用多个算法以拥有更复杂的处理逻辑。此外,该模式将算法的复杂度隐藏在易用的编程接口背后,降低了算法的部署难度。

    准备工作

    比方说,我们需要构建一个简单的算法,它可以鉴别出图像中含有给定颜色的所有像素。该算法输入的是图像以及颜色,并返回表示含有指定颜色的像素的二值图像,该算法还需要指定另外一个参数,即对颜色偏差的容忍度。

    实现方法

    让我们写一个主函数,然后看看我们颜色检测算法的运行结果是什么样的:

    int main()
    {
        //1. 创建图像处理对象
        ColorDector cdetect;
        //2. 读取输入图像
        cv::Mat image = cv::imread("boldt.jpg");
        cv::namedWindow("original", 0);
        cv::imshow("original", image);
        //3. 设置输入参数
        cv::Mat result;
        cdetect.setTargetColor(130, 190, 230);//蓝天的颜色
        //处理并显示结果
        cv::namedWindow("result", 0);
        cv::imshow("result", cdetect.process(image, result));
        cv::waitKey(0);
    
        return 0;
    }

    作用原理

    算法的核心部分非常简单,,包含一个遍历每个像素的简单循环,将像素的颜色与目标颜色比较。 

    cv::Mat_<uchar> process(cv::Mat &image)
        {
            cv::Mat result;
            result.create(image.rows, image.cols, CV_8U);
            //得到迭代器
            cv::Mat_<cv::Vec3b>::const_iterator it = image.begin<cv::Vec3b>();
            cv::Mat_<cv::Vec3b>::const_iterator itend = image.end<cv::Vec3b>();
            cv::Mat_<uchar>::iterator itout = result.begin<uchar>();
    
            while (it != itend)
            {
                if (getDistance(*it) < minDist)
                {
                    *itout = 255;
                }
                else
                {
                    *itout = 0;
                }
                it++;//更新输入迭代器
                itout++;;//更新输出迭代器
            }
            return result;
        }

    cv::Mat 类型的变量 image 表示输入图像,而 result 表示的是二值输出图像。因此,第一步包括初始化迭代器,之后循环遍历很容易实现。每个迭代器检测当前像素的颜色与目标颜色的差距,判断是否在 minDist 所定义的容忍度之内。如果判断为真,那么输出图像中的当前像素赋值为255(白色),否则赋值为0(黑色)。 gitDistance 方法用于计算两个颜色之间的距离。本例中,简单使用各通道像素差绝对值之和来计算颜色距离:

    int getDistance(const cv::Vec3b &color)    const
        {
            return abs(color[0] - target[0]) +
                abs(color[1] - target[1]) +
                abs(color[2] - target[2]);
        }

    这里process 方法执行时,会检查 输出图像是否需要重新分配大小, 也就是creat 方法是否会执行。

    下面我们来把其他方法(包括变量set和get方法)或变量补全。

    class ColorDector{
    private:
        //最小可接受距离
        int minDist;
        //目标色
        cv::Vec3b target;
        //结果图像
        cv::Mat result;
    public:
        //构造函数
        ColorDector() : minDist(100)
        {
            //初始化默认参数
            target[0] = target[1] = target[2] = 0;
        }
        //设置彩色距离阈值,阈值须为非负数
        void setColorDistanceThreshold(int distance)
        {
            if (distance < 0)
                distance = 0;
            minDist = distance;
        }
        //获取彩色距离阈值
        int getColorDistanceThreshold() const
        {
            return minDist;
        }
        //设置需检测的颜色
        void setTargetColor(unsigned char red, unsigned char green, unsigned char blue)
        {
            target[2] = red;
            target[1] = green;
            target[0] = blue;
        }
        //设置需检测的颜色
        void setTargetColor(cv::Vec3b color)
        {
            target = color;
        }
        //获取需检测的颜色
        cv::Vec3b getTargetColor()    const
        {
            return target;
        }
        //二值化处理函数
        cv::Mat_<uchar> process(cv::Mat &image, cv::Mat & result)
        {
            result.create(image.rows, image.cols, CV_8U);
            //得到迭代器
            cv::Mat_<cv::Vec3b>::const_iterator it = image.begin<cv::Vec3b>();
            cv::Mat_<cv::Vec3b>::const_iterator itend = image.end<cv::Vec3b>();
            cv::Mat_<uchar>::iterator itout = result.begin<uchar>();
    
            while (it != itend)
            {
                if (getDistance(*it) < minDist)
                {
                    *itout = 255;
                }
                else
                {
                    *itout = 0;
                }
                it++;//更新输入迭代器
                itout++;;//更新输出迭代器
            }
            return result;
        }
        //计算颜色距离
        int getDistance(const cv::Vec3b &color)    const
        {
            return abs(color[0] - target[0]) +
                abs(color[1] - target[1]) +
                abs(color[2] - target[2]);
        }
    };

    这里,我们提供用户两种 setTargetColor 方法。前一种的三个参数分别是三个颜色分量,而后一种使用 cv::Vec3b 来保存颜色值。

    整个设计很简单,当算法愈发复杂是,策略模式才能够发挥真正的威力。

    扩展阅读

    为了计算两个颜色向量之间的距离,我们之前使用了简单的像素差绝对值之和。OpenCV内置了一个 norm 函数用于计算向量的欧斯距离:

    return static_cast<int>(
        cv::norm<int,3>(cv::Vec3i(color[0]-target[0],
                                  color[1]-target[1],
                                  color[2]-target[2])));        

    这里我们使用的是 cv::Vec3i(包含三个整数的向量)作为norm函数的输入参数。

    OpenCV提供了矩阵和向量结构的一些基本算术操作。所以我们可能会这样写:

    return static_cast<int>(
        cv::norm<uchar,3>(color-target)); // wrong!

    实际上,这是错误的,因为这些算术操作符都包含了对 static_cast 的调用。那么当target的值大于color时,返回的不是负数值,而是0。

     正确的形式应该是:

    cv::Vec3b dist;
    cv::absdiff(color, target, dist);
    return cv::sum(dist)[0];

    2. 使用控制器(Controller)实现模块间通信

    当构建更复杂的应用程序时,你将需要创建多个算法,组合使用它们可以完成一些高级的任务。

    准备工作

    创建两个按钮的简单对话框应用,其中之一用于选择图像,另一个用于开始处理图像,如图所示:

    实现方法

     1. 定义colordetector.h

    #include<iostream>
    #include "opencv2/opencv.hpp"
    
    using namespace std;
    
    class ColorDetector{
    private:
        //最小可接受距离
        int minDist;
        //目标色
        cv::Vec3b target;
        //结果图像
        cv::Mat result;
    public:
        //构造函数
        ColorDetector() : minDist(100)
        {
            //初始化默认参数
            target[0] = target[1] = target[2] = 0;
        }
        //设置彩色距离阈值,阈值须为非负数
        void setColorDistabceThreshold(int distance)
        {
            if (distance < 0)
                distance = 0;
            minDist = distance;
        }
        //获取彩色距离阈值
        int getColorDistanceThreshold() const
        {
            return minDist;
        }
        //设置需检测的颜色
        void setTargetColor(unsigned char red, unsigned char green, unsigned char blue)
        {
            target[2] = red;
            target[1] = green;
            target[0] = blue;
        }
        //设置需检测的颜色
        void setTargetColor(cv::Vec3b color)
        {
            target = color;
        }
        //获取需检测的颜色
        cv::Vec3b getTargetColor()    const
        {
            return target;
        }
        //二值化处理函数
        cv::Mat_<uchar> process(cv::Mat &image)
        {
            result.create(image.rows, image.cols, CV_8U);
            //得到迭代器
            cv::Mat_<cv::Vec3b>::const_iterator it = image.begin<cv::Vec3b>();
            cv::Mat_<cv::Vec3b>::const_iterator itend = image.end<cv::Vec3b>();
            cv::Mat_<uchar>::iterator itout = result.begin<uchar>();
    
            while (it != itend)
            {
                if (getDistance(*it) < minDist)
                {
                    *itout = 255;
                }
                else
                {
                    *itout = 0;
                }
                it++;//更新输入迭代器
                itout++;;//更新输出迭代器
            }
            return result;
        }
        //计算颜色距离
        int getDistance(const cv::Vec3b &color)    const
        {
            cv::Vec3b dist;
            cv::absdiff(color, target, dist);
            return cv::sum(dist)[0];
        }
    };
    
    class ColorDetectController
    {
    private:
        ColorDetector* cdetect;//算法类
        cv::Mat image;//待处理的图像
        cv::Mat result;//结果
    public:
        ColorDetectController()
        {
            cdetect = new ColorDetector();
        }
        //设置色彩距离阈值
        void setColorDistanceThreshold(int distance)
        {
            cdetect->setColorDistabceThreshold(distance);
        }
        //获取色彩距离阈值
        int getColorDistancethreshold()    const
        {
            return cdetect->getColorDistanceThreshold();
        }
        //设置要检测的颜色
        void setTargetColor(unsigned char red,
            unsigned char green, unsigned char blue)
        {
            cdetect->setTargetColor(red, green, blue);
        }
        //获取要检测的颜色
        void getTargetColor(unsigned char& red, unsigned char& green, unsigned char& blue)    const
        {
            cv::Vec3b color = cdetect->getTargetColor();
            red = color[2];
            green = color[1];
            blue = color[0];
        }
        //设置输入图像,通过文件读取
        bool setInputImage(string filename)
        {
            image = cv::imread(filename);
            if (!image.data)
                return false;
            else
                return true;
        }
        //返回当前的输入图像
        const cv::Mat getInputImage()    const
        {
            return image;
        }
        //开始处理图像
        void process()
        {
            result = cdetect->process(image);
        }
        //获取最近一次处理的结果
        const cv::Mat getLastResult()    const
        {
            return result;
        }
        //删除当前控制器创建的处理对象
        ~ColorDetectController()
        {
            delete cdetect;
        }
    };
    View Code

    MFC中两个按键触发函数

      CFileDialog dlg(TRUE, _T("*.bmp"), NULL,
            OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY,
            _T("image file(*.bmp;*,jpg)|*.bmp;*.jpg|ALL Files(*.*)|*.*||"), NULL);
    
        dlg.m_ofn.lpstrTitle = _T("Open Image");
        // if a filename has been selected
        if (dlg.DoModal() == IDOK) {
            // get the path of the selected filename
            CString strMfc = dlg.GetPathName();
            std::string filename = CT2CA(strMfc.GetBuffer(0));
            // set and display the input image
            controller.setInputImage(filename);
            cv::imshow("Input Image", controller.getInputImage());
        }
      // target color is hard-coded here
        controller.setTargetColor(130, 190, 230);
        // process the input image and display result
        controller.process();
        cv::imshow("Output Result", controller.getLastResult());

    3. 使用单件(Singleton)设计模式

    单件是另外一种流行的设计模式,用于简化对一个类实例的访问,同时保证在程序执行期间只有一个实例存在。

    准备工作

    我们使用上面的 ColorDetectController 类。该类将被修改,以包含一个单件类。

    实现方法

    要做的第一件事是添加一个私有静态成员变量,他将保存对单个类实例的引用。同时,为了禁止创建额外的类实例,构造函数也是私有的:

    class ColorDetectController{
    private:
        cv::Mat image;//待处理的图像
        cv::Mat result;//结果
        //单件指针
        static ColorDetectController *singleton;
        ColorDetector *cdetect;
        //私有构造函数
        ColorDetectController()
        {
            //初始化工作
            cdetect = new ColorDetector();
        }
    ...
    }

    此外,你还可以使复制构造函数和操作符 = 私有化,以确保无法创建独一无二的单件实例的拷贝。当一个用户的类要求单件类的一个实例时,它才被创建。这通过使用一个公有静态方法实现,如果实例不存在那么创建它,然后返回一个指向该实例的指针:

        static ColorDetectController * getInstance()
        {
            if (singleton == 0)
                singleton = new ColorDetectController();
            return singleton;
        }    

    需要注意的是,单件的实现并不是线程安全的。因此,在多线程情况下不应该使用它。

    最后,因为单件实例是被动态创建,当不需要时用户必须删除它。这也是通过一个静态方法实现的:

        static void destroy()
        {
            if (singleton != 0)
                delete singleton;
            singleton = 0;
        }

    由于单件是一个静态成员变量,它必须在 .cpp 文件中定义,如下:

    ColorDetectController *ColorDetectController::singleton = 0;

    作用原理

    因为单件可以通过一个公共的静态方法获取,所有包括单件类声明的类都能访问它。这尤其适用于控制器对象,它被多个拥有复杂GUI的窗口控件类访问。其中的任何一个GUI类都不需要声明一个成员变量,这和前一节不同。对话框类的两个回调方法编写如下:

    void CMy3_3单件设计模式Dlg::OnBnClickedOpen()
    {
        // TODO:  在此添加控件通知处理程序代码
        CFileDialog dlg(TRUE, _T("*.bmp"), NULL,
            OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY,
            _T("image file(*.bmp;*,jpg)|*.bmp;*.jpg|ALL Files(*.*)|*.*||"), NULL);
    
        dlg.m_ofn.lpstrTitle = _T("Open Image");
        if (dlg.DoModal() == IDOK)
        {
            // get the path of the selected filename
            CString strMfc = dlg.GetPathName();
            std::string filename = CT2CA(strMfc.GetBuffer(0));
            // set and display the input image
            ColorDetectController::getInstance()->setInputImage(filename);
            cv::imshow("Input Image", ColorDetectController::getInstance()->getInputImage());
        }
    }
    
    
    void CMy3_3单件设计模式Dlg::OnBnClickedProcess()
    {
        // TODO:  在此添加控件通知处理程序代码
        ColorDetectController::getInstance()->setTargetColor(130, 190, 230);
        ColorDetectController::getInstance()->process();
        cv::imshow("Output result", ColorDetectController::getInstance()->getLastResult());
    }

    当应用程序关闭时,单件的实例必须被释放:

    void CMy3_3单件设计模式Dlg::OnBnClickedOk()
    {
        // TODO:  在此添加控件通知处理程序代码
        ColorDetectController::getInstance()->destroy();
        CDialog::OnOK();
    }

     如上所示,当一个控制器被封装在一个单件中,它变得更容易访问。然而,一个更好的实现需要一个更复杂的GUI。这将在下一节中实现。

    4. 使用模型-试图-控制器(Model-View-Controller)架构设计应用程序

    5. 颜色空间转换

    下面先介绍几个不同的颜色空间:
    RGB: 基于红、绿、蓝三原色的使用。

    Lab: 在感知上均匀分布的色彩空间。

    HSV: 代表色调,饱和度和值(Value)。

    HLS: 代表色调(Hue),饱和度(Saturation)和亮度(Lightness)。

    调用方式为:

    cvtColor(image_, gray, COLOR_BGR2GRAY);
    cvtColor(image_, hls, COLOR_BGR2HLS);
    cvtColor(image_, hsv, COLOR_BGR2HSV);
    cvtColor(image_, hsv, COLOR_BGR2Lab);

    作用原理

    当图像从一个颜色空间转换到另一个,线性或非线性变换将作用于每个输入像素,以产生输出像素。

    注意,三原色红、绿、蓝在RGB次序或BGR次序转换到相同颜色空间是不一样的(灰度一样),比如转到HSV空间,H会相反。

    #include <iostream>
    #include "opencv2/highgui.hpp"
    #include "opencv2/imgproc.hpp"
    
    using namespace std;
    using namespace cv;
    
    void Show_Image(Mat & input, char* name)
    {
        namedWindow(name, 0);
        imshow(name, input);
    
    }
    
    int main()
    {
        Mat image = imread("waves.jpg");
        Mat hsv;
        cvtColor(image, hsv, COLOR_BGR2HSV);
        Mat channel[3];
        split(hsv, channel);
        for (int index = 0; index < 3; index++)
        {
            char* buff[3] = { "H", "S", "V" };
    
            Show_Image(channel[index], buff[index]);
        }
        
        waitKey(0);
    
        return 0;
    }
  • 相关阅读:
    成功熬了四年还没死?一个IT屌丝创业者的深刻反思
    史氏语录
    WEB安全攻防学习内容
    从程序员的角度谈创业三年
    Windows2008 R2修改3389端口教程
    Win2008R2 zip格式mysql 安装与配置
    制作支持UEFI PC的Server2008 R2系统安装U盘
    郎科U208(主控 PS2251-50 HYNIX H27UCG8T2MYR)量产还原
    自用有线IP切换
    自动配置IP地址.bat
  • 原文地址:https://www.cnblogs.com/xuanyuyt/p/6246461.html
Copyright © 2011-2022 走看看