精华内容
下载资源
问答
  • dtree源码解析
    2013-03-22 17:06:59

    1.先来介绍一下. dtree 的用法.(我引用了以前我收集的一篇文章.还比较详细,出处不记得啦).文章下面会附带dtree用法的例子.
             Dtree目录树的总结
                      一:函数
                               1:页面中
                                  tree.add(id,pid,name,url,title,target,icon,iconOpen,open);
                                      参数说明:
                                                  id         :节点自身的id
                                                  pid       :节点的父节点的id
                                                  name    :节点显示在页面上的名称
                                                  url        :节点的链接地址
                                                  title      :鼠标放在节点上所出现的提示信息
                                                  target   :节点链接所打开的目标frame(如框架目标mainFrame,_blank,_self 类)
                                                  icon      :节点关闭时的显示图片的路径
                                                  iconOpen:节点打开时的显示图片的路径
                                                  open    :布尔型,节点是否打开(默认为false)
                                                 注:open项:顶级节点一般采用true,即pid是-1的节点
                                2:dtree.js文件中
                                                 约87-113行是一些默认图片的路径,注意要指对。

    二:页面中的书写
              1:默认值的书写规则(从左至右,依次省略)
                              即 tree.add(id,pid,name,url);后面5个参数可以省略
              2:有间隔时的默认值(如存在第6个参数,但第5个参数想用默认值)
                             即 tree.add(id,pid,name,url,"",target);必须这样写
             3:样式表
               (1):可以将dtree.css中的样式附加到你的应用中的主css中,如a.css
               (2):也可以同时引用dtree.css与a.css两个文件,但前提条件是两个css文件中不能有重复的样式

     2、下面是我对源码的改造,主要是添加了一个双击事件,并且添加了一些动态添加节点的功能。

    首相附上源码的修改:

    /*--------------------------------------------------|

     

    | dTree 2.05 | www.destroydrop.com/javascript/tree/ |

     

    |---------------------------------------------------|

     

    | Copyright (c) 2002-2003 Geir Landr�               |

     

    |                                                   |

     

    | This script can be used freely as long as all     |

     

    | copyright messages are intact.                    |

     

    |                                                   |

     

    | Updated: 17.04.2003                               |

     

    |--------------------------------------------------*/

     

    // Node object

    function Node(id, pid, name, url, dcfun, title, target, icon, iconOpen, open)

    {

     

    this.id = id;

     

    this.pid = pid;

     

    this.name = name;

     

    this.url = url;

    // lin 增加双击事件定义

    this.dcfun = dcfun;

     

    this.title = title;

     

    this.target = target;

     

    this.icon = icon;

     

    this.iconOpen = iconOpen;

     

    this._io = open || false;

     

    this._is = false;

     

    this._ls = false;

     

    this._hc = false;

     

    this._ai = 0;

     

    this._p;

     

    };

     

    // Tree object

     

    function dTree(objName, htmlContainer)

    {

     

    this.config = {

     

    target : null,

     

    folderLinks : true,

     

    useSelection : true,

     

    useCookies : true,

     

    useLines : true,

     

    useIcons : true,

     

    useStatusText : false,

     

    closeSameLevel : false,

     

    inOrder : false

     

    }

     

    this.icon = {

     

    root : 'img/base.gif',

     

    folder : 'img/folder.gif',

     

    folderOpen : 'img/folderopen.gif',

     

    node : 'img/page.gif',

     

    empty : 'img/empty.gif',

     

    line : 'img/line.gif',

     

    join : 'img/join.gif',

     

    joinBottom : 'img/joinbottom.gif',

     

    plus : 'img/plus.gif',

     

    plusBottom : 'img/plusbottom.gif',

     

    minus : 'img/minus.gif',

     

    minusBottom : 'img/minusbottom.gif',

     

    nlPlus : 'img/nolines_plus.gif',

     

    nlMinus : 'img/nolines_minus.gif'

     

    };

     

    this.obj = objName;

     

    this.aNodes = [];

     

    this.aIndent = [];

     

    this.root = new Node(-1);

     

    this.selectedNode = null;

     

    this.selectedFound = false;

     

    this.completed = false;

     

    this.aNodesData = [];

    this.container = htmlContainer || 'dtree'; // 上层容器

     

    };

     

    // Adds a new node to the node array

     

    dTree.prototype.add = function(id, pid, name, url, dcfun, title, target, icon,

    iconOpen, open)

    {

    //alert("open"+open);

    this.aNodesData[this.aNodesData.length] = new Node(id, pid, name, url,

    dcfun, title, target, icon, iconOpen, open);

    };

    // lin

    dTree.prototype.add11 = function(id, pid, name, url, dcfun, title, target,

    icon, iconOpen, open)

    {

    var idsID = this.queryID(id);

    if (idsID > 0)

    {

    this.remove(id, idsID);

    }

    this.aNodesData[this.aNodesData.length] = new Node(id, pid, name, url,

    dcfun, title, target, icon, iconOpen, open);

     

    };

     

    // Open/close all nodes

     

    dTree.prototype.openAll = function()

    {

     

    this.oAll(true);

     

    };

     

    dTree.prototype.closeAll = function()

    {

     

    this.oAll(false);

     

    };

     

    // Outputs the tree to the page

     

    dTree.prototype.toString = function()

    {

     

    var str = '<div class="dtree">\n';

     

    if (document.getElementById)

    {

     

    if (this.config.useCookies)

    this.selectedNode = this.getSelected();

     

    str += this.addNode(this.root);

     

    }

    else

    str += 'Browser not supported.';

     

    str += '</div>';

     

    if (!this.selectedFound)

    this.selectedNode = null;

     

    this.completed = true;

     

    return str;

     

    };

     

    // Creates the tree structure

     

    dTree.prototype.addNode = function(pNode)

    {

     

    var str = '';

     

    var n = 0;

    if (this.config.inOrder)

    n = pNode._ai;

     

    for (n; n < this.aNodes.length; n++)

    {

     

    if (this.aNodes[n].pid == pNode.id)

    {

     

    var cn = this.aNodes[n];

    /*//状态open值依旧传不过来,暂时修改源码显示两层

    if(this.aNodes[n].pid==-1||this.aNodes[n].pid=="o_1")

    {

    this.aNodes[n]._io=true;

    }*/

    cn._p = pNode;

     

    cn._ai = n;

     

    this.setCS(cn);

     

    if (!cn.target && this.config.target)

    cn.target = this.config.target;

     

    if (cn._hc && !cn._io && this.config.useCookies)

    cn._io = this.isOpen(cn.id);

     

    if (!this.config.folderLinks && cn._hc)

    cn.url = null;

     

    if (this.config.useSelection && cn.id == this.selectedNode

    && !this.selectedFound)

    {

     

    cn._is = true;

     

    this.selectedNode = n;

     

    this.selectedFound = true;

     

    }

     

    str += this.node(cn, n);

     

    if (cn._ls)

    break;

     

    }

     

    }

     

    return str;

     

    };

     

    // Creates the node icon, url and text

     

    dTree.prototype.node = function(node, nodeId)

    {

    var str = '<div class="dTreeNode">' + this.indent(node, nodeId);

    if (this.config.useIcons)

    {

     

    if (!node.icon)

    node.icon = (this.root.id == node.pid) ? this.icon.root

    : ((node._hc) ? this.icon.folder : this.icon.node);

     

    if (!node.iconOpen)

    node.iconOpen = (node._hc) ? this.icon.folderOpen : this.icon.node;

     

    if (this.root.id == node.pid)

    {

     

    node.icon = this.icon.root;

     

    node.iconOpen = this.icon.root;

     

    }

    var sign = node.iconOpen;

    var reg = /^.*?\.(jpg|jpeg|png|bmp|gif)$/;

    if (sign.toLowerCase().match(reg) === null)

    {

    str += '<img id="i' + this.obj + nodeId + '" src="'

    + ((node._io) ? node.iconOpen : node.icon)

    + '" alt="" style="display:none;" />';

    }

    else

    {

    str += '<img id="i' + this.obj + nodeId + '" src="'

    + ((node._io) ? node.iconOpen : node.icon) + '" alt="" />';

    }

    }

     

    if (node.url)

    {

     

    str += '<a id="s'

    + this.obj

    + nodeId

    + '" class="'

    + ((this.config.useSelection) ? ((node._is ? 'nodeSel' : 'node'))

    : 'node') + '" href="' + node.url + '"';

     

    if (node.title)

    str += ' title="' + node.title + '"';

     

    if (node.target)

    str += ' target="' + node.target + '"';

     

    if (this.config.useStatusText)

    str += ' οnmοuseοver="window.status=\'' + node.name + '\';return true;" οnmοuseοut="window.status=\'\';return true;" ';

     

    if (this.config.useSelection

    && ((node._hc && this.config.folderLinks) || !node._hc))

     

    str += ' οnclick="javascript: ' + this.obj + '.s(' + nodeId + ');"';

    // alert("node.dcfun="+node.dcfun+"---"+node.name+"---"+node.url);

    if (node.dcfun)

    {

    // alert("dcfun="+node.dcfun);

    str += ' οndblclick="' + node.dcfun + '" ';

    }

    str += '>';

     

    }

     

    else if ((!this.config.folderLinks || !node.url) && node._hc

    && node.pid != this.root.id)

     

    str += '<a href="javascript: ' + this.obj + '.o(' + nodeId

    + ');" class="node">';

     

    str += node.name;

     

    if (node.url || ((!this.config.folderLinks || !node.url) && node._hc))

    str += '</a>';

     

    str += '</div>';

     

    if (node._hc)

    {

     

    str += '<div id="d' + this.obj + nodeId

    + '" class="clip" style="display:'

    + ((this.root.id == node.pid || node._io) ? 'block' : 'none')

    + ';">';

     

    str += this.addNode(node);

     

    str += '</div>';

     

    }

     

    this.aIndent.pop();

    return str;

     

    };

     

    // Adds the empty and line icons

     

    dTree.prototype.indent = function(node, nodeId)

    {

     

    var str = '';

     

    if (this.root.id != node.pid)

    {

     

    for ( var n = 0; n < this.aIndent.length; n++)

     

    str += '<img src="' + ((this.aIndent[n] == 1 && this.config.useLines) ? this.icon.line

    : this.icon.empty) + '" alt="" />';

     

    (node._ls) ? this.aIndent.push(0) : this.aIndent.push(1);

     

    if (node._hc)

    {

     

    str += '<a href="javascript: ' + this.obj + '.o(' + nodeId

    + ');"><img id="j' + this.obj + nodeId + '" src="';

     

    if (!this.config.useLines)

    str += (node._io) ? this.icon.nlMinus : this.icon.nlPlus;

     

    else

    str += ((node._io) ? ((node._ls && this.config.useLines) ? this.icon.minusBottom

    : this.icon.minus)

    : ((node._ls && this.config.useLines) ? this.icon.plusBottom

    : this.icon.plus));

     

    str += '" alt="" /></a>';

     

    }

    else

    str += '<img src="' + ((this.config.useLines) ? ((node._ls) ? this.icon.joinBottom

    : this.icon.join)

    : this.icon.empty) + '" alt="" />';

     

    }

     

    return str;

     

    };

     

    // Checks if a node has any children and if it is the last sibling

     

    dTree.prototype.setCS = function(node)

    {

     

    var lastId;

     

    for ( var n = 0; n < this.aNodes.length; n++)

    {

     

    if (this.aNodes[n].pid == node.id)

    node._hc = true;

     

    if (this.aNodes[n].pid == node.pid)

    lastId = this.aNodes[n].id;

     

    }

     

    if (lastId == node.id)

    node._ls = true;

     

    };

     

    // Returns the selected node

     

    dTree.prototype.getSelected = function()

    {

     

    var sn = this.getCookie('cs' + this.obj);

     

    return (sn) ? sn : null;

     

    };

     

    // Highlights the selected node

     

    dTree.prototype.s = function(id)

    {

    // lin 增加选中事件

    if (this.aNodes[id] == null)

    return;

    this.nodeSelected(this.aNodes[id].id);

    if (!this.config.useSelection)

    return;

    var cn = this.aNodes[id];

    // alert(document.getElementById("s" + this.obj + id).className);

    if (cn._hc && !this.config.folderLinks)

    return;

    if (this.selectedNode != id)

    {

     

    if (this.selectedNode || this.selectedNode == 0)

    {

     

    eOld = document.getElementById("s" + this.obj + this.selectedNode);

    if (eOld != null)

    eOld.className = "node";

     

    }

     

    eNew = document.getElementById("s" + this.obj + id);

    if (eNew != null)

    eNew.className = "nodeSel";

     

    this.selectedNode = id;

     

    if (this.config.useCookies)

    this.setCookie('cs' + this.obj, cn.id);

     

    }

     

    };

    dTree.prototype.h = function(id)

    {

    var index = this.queryID(id);

    document.getElementById("s" + this.obj + index).className = "nodeSel";

    };

    // Toggle Open or close

     

    dTree.prototype.o = function(id)

    {

     

    var cn = this.aNodes[id];

    this.nodeStatus(!cn._io, id, cn._ls);

     

    cn._io = !cn._io;

     

    if (this.config.closeSameLevel)

    this.closeLevel(cn);

     

    if (this.config.useCookies)

    this.updateCookie();

     

    };

     

    // Open or close all nodes

     

    dTree.prototype.oAll = function(status)

    {

     

    for ( var n = 0; n < this.aNodes.length; n++)

    {

     

    if (this.aNodes[n]._hc && this.aNodes[n].pid != this.root.id)

    {

     

    this.nodeStatus(status, n, this.aNodes[n]._ls)

     

    this.aNodes[n]._io = status;

     

    }

     

    }

     

    if (this.config.useCookies)

    this.updateCookie();

     

    };

     

    // Opens the tree to a specific node

     

    dTree.prototype.openTo = function(nId, bSelect, bFirst)

    {

     

    if (!bFirst)

    {

     

    for ( var n = 0; n < this.aNodes.length; n++)

    {

     

    if (this.aNodes[n].id == nId)

    {

     

    nId = n;

     

    break;

     

    }

     

    }

     

    }

     

    var cn = this.aNodes[nId];

     

    if (cn.pid == this.root.id || !cn._p)

    return;

     

    cn._io = true;

     

    cn._is = bSelect;

     

    if (this.completed && cn._hc)

    this.nodeStatus(true, cn._ai, cn._ls);

     

    if (this.completed && bSelect)

    this.s(cn._ai);

     

    else if (bSelect)

    this._sn = cn._ai;

     

    this.openTo(cn._p._ai, false, true);

     

    };

     

    // Closes all nodes on the same level as certain node

     

    dTree.prototype.closeLevel = function(node)

    {

     

    for ( var n = 0; n < this.aNodes.length; n++)

    {

     

    if (this.aNodes[n].pid == node.pid && this.aNodes[n].id != node.id

    && this.aNodes[n]._hc)

    {

     

    this.nodeStatus(false, n, this.aNodes[n]._ls);

     

    this.aNodes[n]._io = false;

     

    this.closeAllChildren(this.aNodes[n]);

     

    }

     

    }

     

    }

     

    // Closes all children of a node

     

    dTree.prototype.closeAllChildren = function(node)

    {

     

    for ( var n = 0; n < this.aNodes.length; n++)

    {

     

    if (this.aNodes[n].pid == node.id && this.aNodes[n]._hc)

    {

     

    if (this.aNodes[n]._io)

    this.nodeStatus(false, n, this.aNodes[n]._ls);

     

    this.aNodes[n]._io = false;

     

    this.closeAllChildren(this.aNodes[n]);

     

    }

     

    }

     

    }

     

    // Change the status of a node(open or closed)

     

    dTree.prototype.nodeStatus = function(status, id, bottom)

    {

     

    eDiv = document.getElementById('d' + this.obj + id);

     

    eJoin = document.getElementById('j' + this.obj + id);

     

    if (this.config.useIcons)

    {

     

    eIcon = document.getElementById('i' + this.obj + id);

     

    eIcon.src = (status) ? this.aNodes[id].iconOpen : this.aNodes[id].icon;

     

    }

     

    eJoin.src = (this.config.useLines) ?

     

    ((status) ? ((bottom) ? this.icon.minusBottom : this.icon.minus)

    : ((bottom) ? this.icon.plusBottom : this.icon.plus)) :

     

    ((status) ? this.icon.nlMinus : this.icon.nlPlus);

     

    eDiv.style.display = (status) ? 'block' : 'none';

     

    };

     

    // [Cookie] Clears a cookie

     

    dTree.prototype.clearCookie = function()

    {

     

    var now = new Date();

     

    var yesterday = new Date(now.getTime() - 1000 * 60 * 60 * 24);

     

    this.setCookie('co' + this.obj, 'cookieValue', yesterday);

     

    this.setCookie('cs' + this.obj, 'cookieValue', yesterday);

     

    };

     

    // [Cookie] Sets value in a cookie

     

    dTree.prototype.setCookie = function(cookieName, cookieValue, expires, path,

    domain, secure)

    {

     

    document.cookie =

     

    escape(cookieName) + '=' + escape(cookieValue)

     

    + (expires ? '; expires=' + expires.toGMTString() : '')

     

    + (path ? '; path=' + path : '')

     

    + (domain ? '; domain=' + domain : '')

     

    + (secure ? '; secure' : '');

     

    };

     

    // [Cookie] Gets a value from a cookie

     

    dTree.prototype.getCookie = function(cookieName)

    {

     

    var cookieValue = '';

     

    var posName = document.cookie.indexOf(escape(cookieName) + '=');

     

    if (posName != -1)

    {

     

    var posValue = posName + (escape(cookieName) + '=').length;

     

    var endPos = document.cookie.indexOf(';', posValue);

     

    if (endPos != -1)

    cookieValue = unescape(document.cookie.substring(posValue, endPos));

     

    else

    cookieValue = unescape(document.cookie.substring(posValue));

     

    }

     

    return (cookieValue);

     

    };

     

    // [Cookie] Returns ids of open nodes as a string

     

    dTree.prototype.updateCookie = function()

    {

     

    var str = '';

     

    for ( var n = 0; n < this.aNodes.length; n++)

    {

     

    if (this.aNodes[n]._io && this.aNodes[n].pid != this.root.id)

    {

     

    if (str)

    str += '.';

     

    str += this.aNodes[n].id;

     

    }

     

    }

     

    this.setCookie('co' + this.obj, str);

     

    };

     

    // [Cookie] Checks if a node id is in a cookie

     

    dTree.prototype.isOpen = function(id)

    {

     

    var aOpen = this.getCookie('co' + this.obj).split('.');

     

    for ( var n = 0; n < aOpen.length; n++)

     

    if (aOpen[n] == id)

    return true;

     

    return false;

     

    };

     

    // If Push and pop is not implemented by the browser

     

    if (!Array.prototype.push)

    {

     

    Array.prototype.push = function array_push()

    {

     

    for ( var i = 0; i < arguments.length; i++)

     

    this[this.length] = arguments[i];

     

    return this.length;

     

    }

     

    };

     

    if (!Array.prototype.pop)

    {

     

    Array.prototype.pop = function array_pop()

    {

     

    lastElement = this[this.length - 1];

     

    this.length = Math.max(this.length - 1, 0);

     

    return lastElement;

     

    }

     

    };

    // show the tree

    dTree.prototype.draw = function()

    {

    // renew the two array to save original data.

    this.aNodes = new Array();

    this.aIndent = new Array();

    for ( var i = 0; i < this.aNodesData.length; i++)

    {

     

    var oneNode = this.aNodesData[i];

    this.aNodes[i] = new Node(oneNode.id, oneNode.pid, oneNode.name,

    oneNode.url, oneNode.dcfun, oneNode.title, oneNode.target,

    oneNode.icon, oneNode.iconOpen, oneNode._io);

    }

    this.rewriteHTML();

    }

     

    // outputs the tree to the page , callled by show()

    dTree.prototype.rewriteHTML = function()

    {

    var str = '';

    var container;

    // alert("container=" + this.container);

    container = document.getElementById(this.container);

    if (!container)

    {

    alert('dTree can\'t find your specified container to show your tree.\n\n Please check your code!');

    return;

    }

    if (this.config.useCookies)

    this.selectedNode = this.getSelected();

    str += this.addNode(this.root);

    if (!this.selectedFound)

    this.selectedNode = null;

    this.completed = true;

    //alert(str);

    container.innerHTML = str;

    };

     

    // Checks if a node has children

    dTree.prototype.hasChildren = function(id)

    {

    // alert(this.aNodesData.length);

    for ( var i = 0; i < this.aNodesData.length; i++)

    {

    var oneNode = this.aNodesData[i];

    if (oneNode.pid == id)

    return true;

    }

    return false;

    };

    // 递归获取节点下所有字节点的数组,不包含当前节点

    dTree.prototype.getChildNodeIdArray = function(id)

    {

    var array = new Array();

    this.getChildArray(array, id);

    return array;

    };

    // 递归获取方法内容

    dTree.prototype.getChildArray = function(array, id)

    {

    for ( var i = 0; i < this.aNodesData.length; i++)

    {

    var oneNode = this.aNodesData[i];

    if (oneNode.pid == id)

    {

    array.push(oneNode.id);

    this.getChildArray(array, oneNode.id);

    }

    }

    };

    // remove a node 删除当前节点,以及所有子节点,可递归

    dTree.prototype.remove = function(id)

    {

    for ( var i = 0; i < this.aNodesData.length; i++)

    {

    var oneNode = this.aNodesData[i];

    if (oneNode.pid == id)

    {

    // 递归删除子节点

    this.remove(oneNode.id);

    i--;

    }

    }

     

    for ( var i = 0; i < this.aNodesData.length; i++)

    {

    if (this.aNodesData[i].id == id)

    {

    this.aNodesData.remove(i);

    }

    }

    };

     

    dTree.prototype.removeChild = function(id)

    {

    for ( var i = 0; i < this.aNodesData.length; i++)

    {

    var oneNode = this.aNodesData[i];

    if (oneNode.pid == id)

    this.aNodesData.remove(i);

    ;

    }

    };

    // define a remove method for Array

    Array.prototype.remove = function(dx)

    {

    if (isNaN(dx) || dx > this.length)

    {

    return false;

    }

    for ( var i = 0, n = 0; i < this.length; i++)

    {

    if (this[i] != this[dx])

    {

    this[n++] = this[i];

    }

    }

    this.length -= 1;

    };

    dTree.prototype.queryID = function(id)

    {

    // alert("query");

    for ( var i = 0; i < this.aNodesData.length; i++)

    {

    var oneNode = this.aNodesData[i];

    if (oneNode.id == id)

    return i;

    }

    return -1;

    };

    dTree.prototype.updateNodeTitle = function(id, title)

    {

    for ( var i = 0; i < this.aNodesData.length; i++)

    {

    var oneNode = this.aNodesData[i];

    if (oneNode.id == id)

    {

    oneNode.name = title;

    this.draw();

    }

    }

    };

    dTree.prototype.selectNode = function(id)

    {

    var index = this.queryID(id);

    this.s(index);

    };

    dTree.prototype.getNodeSelected = function()

    {

    var s = "";

    if (this.selectedNode != "" && this.selectedNode != null)

    s = this.aNodesData[this.selectedNode];

    return s;

    };

    dTree.prototype.getNodeById = function(id)

    {

    var s = "";

    for ( var i = 0; i < this.aNodesData.length; i++)

    {

    var oneNode = this.aNodesData[i];

    if (oneNode.id == id)

    s = oneNode;

    }

    return s;

    };

    dTree.prototype.nodeSelected = function(id)

    {

    };

    dTree.prototype.nodeOpened = function(id)

    {

    var idx = this.queryID(id);

    // alert("id="+id+",idx="+idx);

    this.nodeStatus(true, idx, this.aNodes[idx]._ls);

    this.s(idx);

    };

    dTree.prototype.openNode = function(id)

    {

    var idx = this.queryID(id);

    this.o(idx);

    };

    更多相关内容
  • DTree源码注释及使用示例及性能建议
  • 机器学习C++源码解析-DTree算法-源码+数据
  • JSP树型菜单 DTree.zip jsp生产管理系统.rar jsp高校科研项目管理系统.rar msn聊天程序Java仿真代码.rar Notebook源码,Java记事本.rar P2P--多用户在线聊天室(Java源码).rar P2P源码 Azureus 2.5.0.2(JAVA).rar ...
  • 假设某个stage(也就是一个强分类器)中包含有num个弱分类器(也就是num个DTreeNode),按照下面的过程计算stage对某个采样图像im的结果。 1) 初始化sum = 0 2) for i = 1:num 计算 if f > ...

    1.  概述

    CascadeClassifier为OpenCV中cv namespace下用来做目标检测的级联分类器的一个类。该类中封装的目标检测机制,简而言之是滑动窗口机制+级联分类器的方式。OpenCV的早期版本中仅支持haar特征的目标检测,分别在2.2和2.4.0(包含)之后开始支持LBP和HOG特征的目标检测。

    2.  支持的特征

    对于Haar、LBP和HOG,CascadeClassifier都有自己想对他们说的话:

    1)  Haar:因为之前从OpenCV1.0以来,一直都是只有用haar特征的级联分类器训练和检测(当时的检测函数称为cvHaarDetectObjects,训练得到的也是特征和node放在一起的xml),所以在之后当CascadeClassifier出现并统一三种特征到同一种机制和数据结构下时,没有放弃原来的C代码编写的haar检测,仍保留了原来的检测部分。另外,Haar在检测中无论是特征计算环节还是判断环节都是三种特征中最简洁的,但是笔者的经验中他的训练环节却往往是耗时最长的。

    2)  LBP:LBP在2.2中作为人脸检测的一种方法和Haar并列出现,他的单个点的检测方法(将在下面看到具体讨论)是三者中较为复杂的一个,所以当检测的点数相同时,如果不考虑特征计算时间,仅计算判断环节,他的时间是最长的。

    3)  HOG:在2.4.0中才开始出现在该类中的HOG检测,其实并不是OpenCV的新生力量,因为在较早的版本中HOG特征已经开始作为单独的行人检测模块出现。比较起来,虽然HOG在行人检测和这里的检测中同样是滑窗机制,但是一个是级联adaboost,另一个是SVM;而且HOG特征为了加入CascadeClassifier支持的特征行列改变了自身的特征计算方式:不再有相邻cell之间的影响,并且采用在Haar和LBP上都可行的积分图计算,放弃了曾经的HOGCache方式,虽然后者的加速性能远高于前者,而简单的HOG特征也使得他的分类效果有所下降(如果用SVM分类器对相同样本产生的两种HOG特征做分类,没有了相邻cell影响的计算方式下的HOG特征不那么容易完成分类)。这些是HOG为了加入CascadeClassifier而做出的牺牲,不过你肯定也想得到OpenCV保留了原有的HOG计算和检测机制。另外,HOG在特征计算环节是最耗时的,但他的判断环节和Haar一样的简洁。

    关于三种特征在三个环节的耗时有下表:

     

     

    Train

    Detect

     

     

    Feature Extract

    Judge

    Haar

    H

    L

    L

    LBP

    L

    L

    H

    HOG

    L

    H

    L

     

    3.  内部结构

    从CascadeClassifier开始,以功能和结构来整体介绍内部的组织和功能划分。

    CascadeClassifer中的数据结构包括Data和FeatureEvaluator两个主要部分。Data中存储的是从训练获得的xml文件中载入的分类器数据;而FeatureEvaluator中是关于特征的载入、存储和计算。在此之外还有检测框架的逻辑部分,是在Data和Featureevaluator之上的一层逻辑。

    3.1             Data结构

    先来看Data的结构,如下图:

     

     

    首先,在Data中存储着一系列的DTreeNode,该结构体中记录的是一个弱分类器。其中,feature ID表明它计算的是怎样的一个特征,threshold1是它的阈值,据此判断某个特征值应当属于left还是right,后面的left leafvalue和right leaf value是左右叶节点的值。这里需要结合Stage的判断环节来理解:

    假设某个stage(也就是一个强分类器)中包含有num个弱分类器(也就是num个DTreeNode),按照下面的过程计算stage对某个采样图像im的结果。

    1)  初始化sum = 0

    2)  for i = 1:num

    计算

    if f > threshold1

      sum = sum + leftVal

    else

      sum = sum + rightVal

                         end

    3)  if sum > threshold2

      output = 1

    else

      output = 0

          其中,ID、threshold1、leftVal和rightVal是第i个弱分类器中的变量,featureExtract表示对im提取第ID个特征值,是该强分类器中的阈值,当结果为正时,输出output=1,否则为0.

    另外,可以从上图看到stage结构中仅仅保存了第一个弱分类器的下标first、弱分类器数量num和自身的阈值threshold2,所有弱分类器或者说所有节点都是连续存储在一个vector内的。

    3.2             FeatureEvaluator

    如果Data结构主要是在载入时保存分类器内部的数据,FeatureEvaluator则是负责特征计算环节。这是一个基类,在此之上衍生了HaarEvaluator、LBPEvaluator和HOGEvaluator三种特征各自的特征计算结构。每个Evaluator中都保存了一个vector<Feature>,这是在read环节中从分类器中载入的特征池(feature pool),前面提到的feature ID对应的就是在这个vector内的下标。三种Evaluator中的Feature定义有所不同,因为计算特征所需的信息不同,具体如下:

    Haar——Feature中保存的是3个包含权重的rect,如果要计算下图的特征,

     

    对应的rect为[(R2,-3),(R1,1)]和[(R1,1),(R2,-1)]。这里的R1对应于上图中的红色矩形,R2对应绿色矩形,圆括号内的第二个值为对应的权重。所有Haar特征的描述只需要至多3个加权矩形即可描述,所以HaarEvaluator的Feature中保存的是三个加权矩形;

    LBP——Feature中仅保存一个rect,这里需要指出的是,LBP特征计算的不是一个3x3大小的区域中每个点与中心点的大小关系,而是一个3x3个相同大小的矩形区域之间的对比关系,这也是为什么LBP特征计算过程也用到积分图方法的原因。如下图所示,

     

    Feature中保存的就是红色的矩形位置,而我们要先提取上图中9个矩形内的所有像素点的和,然后比较外围8个矩形内的值和中间矩形内的值的关系,从而得到LBP特征。

    HOG——与LBP中类似,Feature中同样仅一个rect,HOG特征是在2x2个rect大小的范围内提取出的,也就是说给出的rect是HOG计算过程中4个block里的左上角的block。

    除此之外,Evaluator中还有另外一个很重要的数据结构——数据指针。这个结构在三种Evaluator中同样不同,但他们所指向的都是积分图中的一个值。在Haar和LBP中是先计算一个整图的积分图,而HOG中则是计算梯度方向和梯度幅值,然后按照梯度方向划分的区间将梯度幅值图映射成n个积分图。每个特征的计算过程中要维护一系列指向积分图中的指针,通过访问积分图快速计算某个矩形内的像素值的和,从而加速特征计算环节。这里暂不详细展开。

    3.3             检测框架逻辑

    这里的检测框架简而言之就是一个多尺度缩放+滑动窗口遍历搜索的框架。在CascadeClassifier中包含detectMultiScale和detectSingleScale成员函数,分别对应多尺度和单尺度检测,其中多尺度检测中会调用单尺度的方法。

    分类器仅能够对某一固定size的采样图像做判断,给出当前的采样图像是否为真实目标的“非正即负”的结果(size是由训练数据决定的)。要找到某个图像中的目标位置,就要以size大小的采样窗口对图像逐行逐列地扫描,然后对每个采样图像判断是否为正,将结果以矩形位置保存下来就获得了目标的位置。但是这仅仅是单尺度检测,也就是说,一个以40x40大小训练数据训练获得的分类器只能检测当前图像里40x40大小的目标,要检测80x80大小的目标该如何做呢?可以把原图像缩放到原来的1/2,这样原图中80x80大小的目标就变成40x40了,再做一次上面的扫描检测过程,并且将得到的矩形换算到原图中对应的位置,从而检测到了80x80大小的目标。实际上,我们每次对原图进行固定步长的缩放,形成一个图像金字塔,对图像金字塔的每一层都扫描检测,这就是多尺度检测的框架。

    4.  模块功能

    CascadeClassifier的使用中只要调用两个外部接口,一个是read,另一个是detectMultiScale。

    4.1             CascadeClassifier::read

    4.1.1       分类器的XML形式

    read的过程就是对类的成员变量进行初始化的过程,经过这一步,Data结构按照之前已经讨论的逻辑被填充。

    先来看一下一个分类器的xml文件是怎样组织的。


    整体上它包括stageType、featureType、height、width、stageParams、featureParams、stages、features几个节点。

    这里的参数内容就不展开了,主要来看一下stage结构和feature在xml里是怎样保存的,这样训练结束后你可以自己打开这个文件看一下就明白训练了一个什么分类器出来了。

    下面是一个stage的内部结构,maxWeakCount是stage包含的弱分类器个数,stageThreshold是该stage的阈值,也就是上面我们提到过的。接下来就是5个弱分类器了,每个弱分类器中包括internalNodes和leafValues两个节点。前者分别是left和right标记、feature ID和threshold1。

    这里可以解释一下featureID到底是指在哪里的ID了。下图是分类器中的features节点中保存的该分类器使用到的各种特征值,feature ID就是在这些中的ID,就是在这些之中的顺序位置。图中的特征是一个HOG特征,rect节点中的前四个数字代表我们提到的矩形,而最后的1表示要提取的特征值是block中提取的36维向量中的哪一个。当然,Haar和LBP特征的feature节点与此不同,不过也是类似的结构。

    4.1.2       读取的过程

    清楚了分类器的xml形式之后,就要从文件中读取内容至cascadeClassifier中了。可以把这部分分为Data的读取和features的读取两部分。

    bool CascadeClassifier::read(constFileNoderoot)

    {

       if( !data.read(root) )//Data的读取

           return false;

     

       featureEvaluator = FeatureEvaluator::create(data.featureType);

       FileNode fnroot[CC_FEATURES];

       iffn.empty() )

           return false;

     

       return featureEvaluator->read(fn);//features的读取

    }

    4.1.2.1  Data的读取

    先来看看Data的读取,这里以HOG特征的分类器为例,并且跳过stage的参数读取部分,直接来看如何在Data中建立stage结构的。

    // load stages

        fn = root[CC_STAGES];

        iffn.empty() )

            return false;

     

        stages.reserve(fn.size());//先给vector<Stage>分配空间出来

        classifiers.clear();

        nodes.clear();

     

        FileNodeIteratorit =fn.begin(),it_endfn.end();

     

        forint si = 0; it != it_endsi++, ++it )//遍历stages

    {

                       //进入单个stage

            FileNodefns = *it;

            Stagestage;//stage结构中包含thresholdntreesfirst三个变量

            stage.threshold = (float)fns[CC_STAGE_THRESHOLD]-THRESHOLD_EPS;

            fnsfns[CC_WEAK_CLASSIFIERS];

            if(fns.empty())

                returnfalse;

            stage.ntrees = (int)fns.size();

            stage.first = (int)classifiers.size();//ntreesfirst指出该stage中包含的树的数目和起始位置

            stages.push_back(stage);//stage被保存在stagevector(也就是stages)中

            classifiers.reserve(stages[si].first +stages[si].ntrees);//相应地扩展classifiers的空间,它存储的是这些stage中的weak classifiers,也就是weak trees

     

            FileNodeIteratorit1 =fns.begin(),it1_endfns.end();//遍历weak classifier

            for( ; it1 != it1_end;++it1 )// weaktrees

            {

                FileNodefnw = *it1;

                FileNodeinternalNodes =fnw[CC_INTERNAL_NODES];

                FileNodeleafValues =fnw[CC_LEAF_VALUES];

                if(internalNodes.empty()||leafValues.empty())

                    returnfalse;

     

                DTreetree;

                tree.nodeCount = (int)internalNodes.size()/nodeStep;//一个节点包含nodeStep个值,计算得到当前的弱分类器中包含几个节点,无论在哪种特征的分类器中这个值其实都可以默认为1

                classifiers.push_back(tree);//一个弱分类器或者说一个weak tree中只包含一个int变量,用它在classifiers中的位置和自身来指出它所包含的node个数

     

                nodes.reserve(nodes.size() +tree.nodeCount);

                leaves.reserve(leaves.size() +leafValues.size());//扩展存储nodeleavesvector结构空间

                if(subsetSize > 0 )//关于subsetSize的内容都是只在LBP分类器中使用

                    subsets.reserve(subsets.size() +tree.nodeCount*subsetSize);

     

                FileNodeIteratorinternalNodesIter =internalNodes.begin(),internalNodesEndinternalNodes.end();

    //开始访问节点内部

                for(; internalNodesIter != internalNodesEnd; )//nodes

                {

                    DTreeNodenode;//一个node中包含leftrightthresholdfeatureIdx四个变量。其中leftright是其对应的代号,left=0right=-1featureIdx指的是整个分类器中使用的特征池中某个特征的ID,比如共有108个特征,那么featureIdx就在0~107之间threshold是上面提到的。同时可以看到这里HOG分类器中每个弱分类器仅包含一个node,也就是仅对某一个特征做判断,而不是多个特征的集合

                    node.left = (int)*internalNodesIter; ++internalNodesIter;

                    node.right = (int)*internalNodesIter; ++internalNodesIter;

                    node.featureIdx = (int)*internalNodesIter; ++internalNodesIter;

                    if(subsetSize > 0 )

                    {

                        for(intj = 0; j < subsetSize;j++, ++internalNodesIter)

                            subsets.push_back((int)*internalNodesIter);

                        node.threshold = 0.f;

                    }

                    else

                    {

                        node.threshold = (float)*internalNodesIter; ++internalNodesIter;

                    }

                    nodes.push_back(node);//得到的node将保存在它的vector结构nodes

                }

     

                internalNodesIter=leafValues.begin(),internalNodesEnd =leafValues.end();

     

                for(; internalNodesIter != internalNodesEnd; ++internalNodesIter)// leaves

                    leaves.push_back((float)*internalNodesIter);//leaves中保存相应每个nodeleft leafright leaf的值,因为每个weak tree只有一个node也就分别只有一个left leafright leaf,这些将保存在leaves

            }

        }

    通过stage树的建立可以看出最终是获取stages、classifiers、nodes和leaves四个vector变量。其中的nodes和leaves共同组成一系列有序节点,而classifiers中的变量则是在这些节点中查询来构成一个由弱分类器组,它仅仅是把这些弱分类器组合在一起,最后stages中每一个stage也就是一个强分类器,它在classifiers中查询得到自己所属的弱分类器都有哪些,从而构成一个强分类器的基础。

    4.1.2.2   features的读取

    特征的读取最终将保留在featureEvaluator中的vector<Feature>中。所以先来看一下Feature的定义,仍旧以HOG特征为例:

    struct Feature

        {

            Feature();

            float calcint offset )const;

            void updatePtrsconst vector<Mat>&_hist,constMat &_normSum);

            bool readconst FileNode&node); 

     

            enum { CELL_NUM = 4, BIN_NUM= 9 };

     

            Rectrect[CELL_NUM];

            int featComponent//componentindex from 0 to 35

            const floatpF[4]; //for feature calculation

            const floatpN[4]; //for normalization calculation

    };

    其中的CELL_NUM和BIN_NUM分别是HOG特征提取的过程中block内cell个数和梯度方向划分的区间个数。也就是说,在一个block内将提取出CELL_NUM*BIN_NUM维度的HOG特征向量。rect[CELL_NUM]保存的是block的四个矩形位置,featComponent表明该特征是36维HOG特征中的哪一个值。而之后的pF与pN是重点:首先我们假设featComponent=10,那就是说要提取的特征值是该rect描述的block内提取的HOG特征的第10个值,而第一个cell中会产生9个值,那么第10个值就是第二个cell中的第一个值。通过原图计算梯度和按照区间划分的梯度积分图之后,共产生9个积分图,那么pF应当指向第1个积分图内rect描述的block内的第二个cell矩形位置的四个点。

             在featureEvaluator的read中,将对所有features遍历填充到vector<Feature>中。

    在下面的代码中只是读取了参数,并没有更新pF和pN指针,因为我们还没有获得梯度积分图。

    bool HOGEvaluator::Feature :: read(const FileNode&node )

    {

        FileNodernode =node[CC_RECT];//rect节点下包括一个矩形和一个特征类型号featComponent

        FileNodeIteratorit =rnode.begin();

        it>> rect[0].x>> rect[0].y>> rect[0].width>> rect[0].height>> featComponent;//featComponent范围在[0,35]36类特征中的一个

        rect[1].x =rect[0].x +rect[0].width;

        rect[1].y =rect[0].y;

        rect[2].x =rect[0].x;

        rect[2].y =rect[0].y +rect[0].height;

        rect[3].x =rect[0].x +rect[0].width;

        rect[3].y =rect[0].y +rect[0].height;

        rect[1].width =rect[2].width =rect[3].width =rect[0].width;

    rect[1].height =rect[2].height =rect[3].height =rect[0].height;

    //xml中的rect存储的矩形信息与4个矩形之间的关系如下图4所示

     

        return true;

    }

    4.2             CascadeClassifier::detectMultiScale

    这里的代码的伪码可以简单写成如下:

    vector<Rect> results;

    fordoublefactor = 1; ;factor*= scaleFactor )

    {

                                MatscaledImage(scaledImageSize,CV_8U,imageBuffer.data);

        resizegrayImage,scaledImage,scaledImageSize,0, 0, CV_INTER_LINEAR );

                detectSingleScalescaledImage,results );

    }

    groupRectangles( results );

    简单来说,多尺度检测只是尺度缩放形成图像金字塔然后在每个尺度上检测之后将结果进行合并的过程。

    在detectSingleScale中,使用OpenCV中的并行计算机制,以CascadeClassifierInvoker类对整图扫描检测。detectSingleScale的检测过程仍以伪码表达如下:

    // detectSingleScale

    featureEvaluator->setImage(image,data.origWinSize )

    //CascadeClassifierInvoker

    forint y = 0y <heighty +=yStep )

       for(int x = 0x <widthx +=xStep )

       {

                                doublegypWeight;

        int result=classifier->runAt(evaluator,Point(xy), gypWeight);

                results.push_back(R(x,y,W,H,scale));// R(x,y,W,H,scale)表示在scale尺度下检测到的矩形(x,y,W,H)映射到原图上时的矩形

    }

    可以看到上面的代码中最重要的两部分分别是setImagerunAt

    4.2.1       setImage

    前面提到过,featuresread部分仅仅把特征的参数读取进入vector<Feature>中,并没有对指针们初始化,这正是setImage要做的工作。仍以HOG为例,setImage的伪码如下:

     

    vector<Mat> hist;

    Mat                 norm;

    integralHistogram( image,hist, norm );

    forfeatIdx= 0;featIdx < featCount;featIdx++ )

    {

        featuresPtr[featIdx].updatePtrs( hist, norm );

    }

    integralHistogram的过程如下:首先计算image每个像素点的梯度幅值和梯度方向,梯度方向的区间为0~360°,划分为9个区间,按照梯度方向所属区间统计每个区间内image的梯度幅值的积分图。也就是说,对于hist中的第一个Mat来说,先把所有梯度方向在0~40°之外的像素点的幅值置为0,然后计算梯度幅值图的积分图,保存为hist[0];第二个Mat对应40~80°的区间……这样,得到一个包含9Mathist,而norm则是9Mat对应像素点的和。

    接下来就是要根据histnorm来更新每个Feature中的指针了,因为我们已经知道自己要计算的是一个在什么位置上的矩形、在那个区间上的特征,所以只要把指针更新到hist中的那个Mat上即可。注意,这里并没有涉及到滑动窗口机制。

    这样在计算某个HOG特征值时,我们只要计算下面的式子即可:

    HOG(i) = (pF[0]+pF[3]-pF[1]-pF[2] )/( pN[0]+pN[3]-pN[1]-pN[2] )

    4.2.2       runAt

    runAt函数调用了其他方法,但它的伪码可以如下:

             setWindow( hist, cvPoint(x,y) );

    for(intstageIdx= 0; stageIdx <nstages;stageIdx++ )

        {

            stagecascadeStages[stageIdx];//当前stage

            sum= 0.0;

            int ntrees = stage.ntrees;

            forint i = 0; i < ntreesi++, nodeOfs++,leafOfs+= 2 )

            {

                nodecascadeNodes[nodeOfs];//当前node

                doublevalue =featureEvaluator(node.featureIdx);//计算vector<Feature>中的第featureIdx个特征的值

                sum+= cascadeLeavesvaluenode.thresholdleafOfs : leafOfs+ 1 ];//根据node中的threshold得到左叶子或者右叶子的值,加到该stage中的总和

            }

     

            ifsum < stage.threshold )//如果总和大于stagethreshold则通过,小于则退出,并返回当前stage的相反数

                return-stageIdx;

    }

    setWindow是根据当前的位置(x,y)计算Feature中的指针应当在积分图上的偏移量,可以看到这里才是滑动窗口机制实现的真正部分,而不是在setImage中,setImage只是给出各个特征对应的指针相对位置,而不是真实位置。

    后面在stage和node中的遍历和检测,正是体现弱分类器、强分类器和级联分类器的概念。当stage中有一个不满足时,立即退出不再进入下一级,这是级联分类器的概念;弱分类器的判定仅仅给出一个分数,若干个弱分类器的分数的和作为强分类器的判定依据,这是强弱分类器的概念。

    4.3             LBP的判定

    这里的HOG的例子,与Haar很相似,只是特征计算环节有所不同,在判定环节都是根据某个阈值来判断,但是LBP除了在特征计算环节不同以外,在判定环节也大不相同。训练获得的LBP分类器的node中包含8个数存储在subset中,与node的存储很类似。然后在判定阶段按照下式

    t = subset[c>>5]& (1 << (c & 31))

    其中c是提取得到的LBP特征值。当t0时,结果为左叶,为1时,结果为右叶。

    4.4             groupRectangles

    最终的结果由于在多尺度上获得,因而矩形之间难免有重合、重叠和包含的关系,由于缩放尺度可能相对于目标大小比较小,导致同一个目标在多个尺度上被检测出来,所以有必要进行合并。OpenCV的合并规则中有按照权重合并的,也有以MeanShift方法合并的,最简单的一种是直接按照位置和大小关系合并。首先将所有矩形按照大小位置合并成不同的类别,然后将同一类别中的矩形合并成同一个矩形,当不满足给出的阈值条件时,该矩形不会被保存下来。这一部分不是检测的核心,不做详细讨论。

    5.  保留的Haar支持

    之前已经说过,老版本的OpenCV中只支持Haar特征的分类器训练和检测,所以有大量性能表现优秀的老版本Haar分类器已经训练获得,如果新版本的CascadeClassifier不能支持这些分类器,那是非常遗憾的事。所以CascadeClassifier中在做新版本分类器载入之后,如果失败将会按照老版本的分类器再做一次载入,并且保存到odlCascade指针中。在做检测时,如果oldCascade不为空,则按照老版本的Haar分类器做检测。这个过程完全是以C风格的代码完成,是对OpenCV2.2之前版本的继承。

    展开全文
  • Applet钢琴模拟程序java源码 2个目标文件,提供基本的音乐编辑功能。编辑音乐软件的朋友,这款实例会对你有所帮助。 Calendar万年历 1个目标文件 EJB 模拟银行ATM流程及操作源代码 6个目标文件,EJB来模拟银行ATM机...
  • // load内部调用read解析XML中的内容,也可以自己创建节点然后调用Read即可,但是该函数只能读取新格式的分类器,不能读取旧格式的分类器  // 多尺度检测函数  CV_WRAP virtual void ...
    级联分类器检测类CascadeClassifier,在2.4.5版本中使用Adaboost的方法+LBP、HOG、HAAR进行目标检测,加载的是使用traincascade进行训练的分类器

    class CV_EXPORTS_W CascadeClassifier
    {
    public:
        CV_WRAP CascadeClassifier(); // 无参数构造函数,new自动调用该函数分配初试内存
        CV_WRAP CascadeClassifier( const string& filename ); // 带参数构造函数,参数为XML的绝对名称
        virtual ~CascadeClassifier(); // 析构函数,无需关心

        CV_WRAP virtual bool empty() const; // 是否导入参数,只创建了该对象而没有加载或者加载失败时都是空的
        CV_WRAP bool load( const string& filename ); // 加载分类器,参数为XML的绝对名称,函数内部调用read读取新格式的分类器,读取成功后直接返回,读取失败后调用cvLoad读取旧格式的分类器,读取成功返回true,否则返回false
        virtual bool read( const FileNode& node );   // load内部调用read解析XML中的内容,也可以自己创建节点然后调用Read即可,但是该函数只能读取新格式的分类器,不能读取旧格式的分类器

        // 多尺度检测函数
        CV_WRAP virtual void detectMultiScale( const Mat& image,        // 图像,cvarrtoMat实现IplImage转换为Mat,必须为8位,内部可自行转换为灰度图像
                                       CV_OUT vector<Rect>& objects,    // 输出矩形,注意vector不是线程安全的
                                       double scaleFactor=1.1,          // 缩放比例,必须大于1
                                       int minNeighbors=3,              // 合并窗口时最小neighbor,每个候选矩阵至少包含的附近元素个数
                                       int flags=0,                     // 检测标记,只对旧格式的分类器有效,与 cvHaarDetectObjects的参数flags相同, 默认为0,可能的取值为CV_HAAR_DO_CANNY_PRUNING(CANNY边缘检测)、CV_HAAR_SCALE_IMAGE(缩放图像)、CV_HAAR_FIND_BIGGEST_OBJECT(寻找最大的目标)、 CV_HAAR_DO_ROUGH_SEARCH(做粗略搜索);如果寻找最大的目标就不能缩放图像,也不能CANNY边缘检测
                                       Size minSize=Size(),             // 最小检测目标
                                       Size maxSize=Size() );           // 最大检测目标
        // 最好不要在这里设置最大最小,可能会影响合并的效果,因此可以在检测完毕后自行判断结果是否满足要求
        CV_WRAP virtual void detectMultiScale( const Mat& image,
                                       CV_OUT vector<Rect>& objects,
                                       vector<int>& rejectLevels,
                                       vector<double>& levelWeights,
                                       double scaleFactor=1.1,
                                       int minNeighbors=3, int flags=0,
                                       Size minSize=Size(),
                                       Size maxSize=Size(),
                                       bool outputRejectLevels=false );
    // 上述参数多了rejectLevels和levelWeights以及 outputRejectLevels参数,只有在 outputRejectLevels为true的时候才可能输出前两个参数
    // 还有就是在使用旧分类器的时候必须设置flags为 CV_HAAR_SCALE_IMAGE,可以通过haarcascade_frontalface_alt.xml检测人脸尝试


        bool isOldFormatCascade() const;        // 是否是旧格式的分类器
        virtual Size getOriginalWindowSize() const;    // 初始检测窗口大小,也就是训练的窗口
        int getFeatureType() const; // 获取特征类型
        bool setImage( const Mat& );    // 设置图像,计算图像的积分图
        virtual int runAt( Ptr<FeatureEvaluator>& feval, Point pt, double& weight ); // 计算某检测窗口是否为目标
        // 保存强分类器数据
        class Data
        {
        public:
            struct CV_EXPORTS DTreeNode // 节点
            {
                int featureIdx; // 对应的特征编号
                float threshold; // for ordered features only 节点阈值
                int left; // 左子树
                int right; // 右子树
            };

            struct CV_EXPORTS DTree // 弱分类器
            {
                int nodeCount; // 弱分类器中节点个数
            };

            struct CV_EXPORTS Stage // 强分类器
            {
                int first; // 在classifier中的起始位置
                int ntrees; // 该强分类器中的弱分类器数
                float threshold; // 强分类器阈值
            };

            bool read(const FileNode &node); // 读取强分类器

            bool isStumpBased;    // 是否只有树桩

            int stageType;      // BOOST,boostType:GAB、RAB等
            int featureType;    // HAAR、HOG、LBP
            int ncategories;    // maxCatCount,LBP为256,其余为0
            Size origWinSize;

            vector<Stage> stages;
            vector<DTree> classifiers;
            vector<DTreeNode> nodes;
            vector<float> leaves;
            vector<int> subsets;
        };

        Data data;
        Ptr<FeatureEvaluator> featureEvaluator;
        Ptr<CvHaarClassifierCascade> oldCascade;
    // 关于mask这块参考《OpenCV目标检测之MaskGenerator》
    public:
        class CV_EXPORTS MaskGenerator
        {
        public:
            virtual ~MaskGenerator() {}
            virtual cv::Mat generateMask(const cv::Mat& src)=0;
            virtual void initializeMask(const cv::Mat& /*src*/) {};
        };
        void setMaskGenerator(Ptr<MaskGenerator> maskGenerator);
        Ptr<MaskGenerator> getMaskGenerator();

        void setFaceDetectionMaskGenerator();

    protected:
        Ptr<MaskGenerator> maskGenerator;
    }

    注意:当在不同的分类器之间切换的时候,需要手动释放,因为read内部没有释放上一次读取的分类器数据!

    关于新旧格式的分类器参考《OpenCV存储解读之Adaboost分类器》



    使用CascadeClassifier检测目标的过程

    1) load分类器并调用empty函数检测是否load成功
    // 读取stages
    bool CascadeClassifier::Data::read(const FileNode &root)
    {
        static const float THRESHOLD_EPS = 1e-5f;

        // load stage params
        string stageTypeStr = (string)root[CC_STAGE_TYPE];
        if( stageTypeStr == CC_BOOST )
            stageType = BOOST;
        else
            return false;
        printf("stageType: %s\n", stageTypeStr.c_str());

        string featureTypeStr = (string)root[CC_FEATURE_TYPE];
        if( featureTypeStr == CC_HAAR )
            featureType = FeatureEvaluator::HAAR;
        else if( featureTypeStr == CC_LBP )
            featureType = FeatureEvaluator::LBP;
        else if( featureTypeStr == CC_HOG )
            featureType = FeatureEvaluator::HOG;

        else
            return false;
        printf("featureType: %s\n", featureTypeStr.c_str());

        origWinSize.width = (int)root[CC_WIDTH];
        origWinSize.height = (int)root[CC_HEIGHT];
        CV_Assert( origWinSize.height > 0 && origWinSize.width > 0 );

        isStumpBased = (int)(root[CC_STAGE_PARAMS][CC_MAX_DEPTH]) == 1 ? true : false;
        printf("stumpBased: %d\n", isStumpBased);

        // load feature params
        FileNode fn = root[CC_FEATURE_PARAMS];
        if( fn.empty() )
            return false;
        // LBP的maxCatCount=256,其余特征都等于0
        ncategories = fn[CC_MAX_CAT_COUNT]; // ncategories=256/0
        int subsetSize = (ncategories + 31)/32,// subsetSize=8/0 // 强制类型转换取整,不是四舍五入
            nodeStep = 3 + ( ncategories>0 ? subsetSize : 1 ); //每组数值个数,nodeStep=11/4
        printf("subsetSize: %d, nodeStep: %d\n", subsetSize, nodeStep);
        // load stages
        fn = root[CC_STAGES];
        if( fn.empty() )
            return false;

        stages.reserve(fn.size());
        classifiers.clear();
        nodes.clear();

        FileNodeIterator it = fn.begin(), it_end = fn.end();

        for( int si = 0; it != it_end; si++, ++it )
        {
            FileNode fns = *it;
            Stage stage;
            stage.threshold = (float)fns[CC_STAGE_THRESHOLD] - THRESHOLD_EPS;
            fns = fns[CC_WEAK_CLASSIFIERS];
            if(fns.empty())
                return false;
            stage.ntrees = (int)fns.size();
            stage.first = (int)classifiers.size();
            printf("stage %d: ntrees: %d, first: %d\n", si, stage.ntrees, stage.first);
            stages.push_back(stage);
            classifiers.reserve(stages[si].first + stages[si].ntrees);

            FileNodeIterator it1 = fns.begin(), it1_end = fns.end();
            for( ; it1 != it1_end; ++it1 ) // weak trees
            {
                FileNode fnw = *it1;
                FileNode internalNodes = fnw[CC_INTERNAL_NODES];
                FileNode leafValues = fnw[CC_LEAF_VALUES];
                if( internalNodes.empty() || leafValues.empty() )
                    return false;

                // 弱分类器中的节点
                DTree tree;
                tree.nodeCount = (int)internalNodes.size()/nodeStep;
                classifiers.push_back(tree);

                nodes.reserve(nodes.size() + tree.nodeCount);
                leaves.reserve(leaves.size() + leafValues.size());
                if( subsetSize > 0 ) // 针对LBP
                    subsets.reserve(subsets.size() + tree.nodeCount*subsetSize);

                FileNodeIterator internalNodesIter = internalNodes.begin(), internalNodesEnd = internalNodes.end();
                // 保存每一个node
                for( ; internalNodesIter != internalNodesEnd; ) // nodes
                {
                    DTreeNode node;
                    node.left = (int)*internalNodesIter; ++internalNodesIter;
                    node.right = (int)*internalNodesIter; ++internalNodesIter;
                    node.featureIdx = (int)*internalNodesIter; ++internalNodesIter;
                    // 针对LBP,获取8个数值
                    if( subsetSize > 0 )
                    {
                        for( int j = 0; j < subsetSize; j++, ++internalNodesIter )
                            subsets.push_back((int)*internalNodesIter);
                        node.threshold = 0.f;
                    }
                    else
                    {
                        node.threshold = (float)*internalNodesIter; ++internalNodesIter;
                    }
                    nodes.push_back(node);
                }
                // 保存叶子节点
                internalNodesIter = leafValues.begin(), internalNodesEnd = leafValues.end();

                for( ; internalNodesIter != internalNodesEnd; ++internalNodesIter ) // leaves
                    leaves.push_back((float)*internalNodesIter);
            }
        }

        return true;
    }
    // 读取stages与features
    bool CascadeClassifier::read(const FileNode& root)
    {
        // load stages
        if( !data.read(root) )
            return false;

        // load features,参考 《图像特征->XXX特征之OpenCV-估计》
        featureEvaluator = FeatureEvaluator::create(data.featureType);
        FileNode fn = root[CC_FEATURES];
        if( fn.empty() )
            return false;

        return featureEvaluator->read(fn);
    }
    // 外部调用的函数
    bool CascadeClassifier::load(const string& filename)
    {
        oldCascade.release();
        data = Data();
        featureEvaluator.release();
        // 读取新格式的分类器
        FileStorage fs(filename, FileStorage::READ);
        if( !fs.isOpened() )
            return false;

        if( read(fs.getFirstTopLevelNode()) )
            return true;

        fs.release();
        // 读取新格式失败则读取旧格式的分类器
        oldCascade = Ptr<CvHaarClassifierCascade>((CvHaarClassifierCascade*)cvLoad(filename.c_str(), 0, 0, 0));
        return !oldCascade.empty();
    }
    2) 调用detectMultiScale函数进行多尺度检测,该函数可以使用老分类器进行检测也可以使用新分类器进行检测

    2.1 如果load的为旧格式的分类器则使用cvHaarDetectObjectsForROC进行检测,flags参数只对旧格式的分类器有效,参考《OpenCV函数解读之cvHaarDetectObjects》
        if( isOldFormatCascade() )
        {
            MemStorage storage(cvCreateMemStorage(0));
            CvMat _image = image;
            CvSeq* _objects = cvHaarDetectObjectsForROC( &_image, oldCascade, storage, rejectLevels, levelWeights, scaleFactor,
                                                  minNeighbors, flags, minObjectSize, maxObjectSize, outputRejectLevels );
            vector<CvAvgComp> vecAvgComp;
            Seq<CvAvgComp>(_objects).copyTo(vecAvgComp);
            objects.resize(vecAvgComp.size());
            std::transform(vecAvgComp.begin(), vecAvgComp.end(), objects.begin(), getRect());
            return;
        }
    2.2 新格式分类器多尺度检测
         for( double factor = 1; ; factor *= scaleFactor )
         {
            Size originalWindowSize = getOriginalWindowSize();

            Size windowSize( cvRound(originalWindowSize.width*factor), cvRound(originalWindowSize.height*factor) );
            Size scaledImageSize( cvRound( grayImage.cols/factor ), cvRound( grayImage.rows/factor ) );
            Size processingRectSize( scaledImageSize.width-originalWindowSize.width + 1, scaledImageSize.height-originalWindowSize.height + 1 );

            if( processingRectSize.width <= 0 || processingRectSize.height <= 0 )
                break;
            if( windowSize.width > maxObjectSize.width || windowSize.height > maxObjectSize.height )
                break;
            if( windowSize.width < minObjectSize.width || windowSize.height < minObjectSize.height )
                continue;
            // 缩放图像
            Mat scaledImage( scaledImageSize, CV_8U, imageBuffer.data );
            resize( grayImage, scaledImage, scaledImageSize, 0, 0, CV_INTER_LINEAR );
            // 计算步长
            int yStep;
            if( getFeatureType() == cv::FeatureEvaluator::HOG )
            {
                yStep = 4;
            }
            else
            {
                yStep = factor > 2. ? 1 : 2;
            }
            // 并行个数以及大小,按照列进行并行处理
            int stripCount, stripSize;
            // 是否采用TBB进行优化
        #ifdef HAVE_TBB
            const int PTS_PER_THREAD = 1000;
            stripCount = ((processingRectSize.width/yStep)*(processingRectSize.height + yStep-1)/yStep + PTS_PER_THREAD/2)/PTS_PER_THREAD;
            stripCount = std::min(std::max(stripCount, 1), 100);
            stripSize = (((processingRectSize.height + stripCount - 1)/stripCount + yStep-1)/yStep)*yStep;
        #else
            stripCount = 1;
            stripSize = processingRectSize.height;
        #endif
            // 调用单尺度检测函数进行检测
            if( !detectSingleScale( scaledImage, stripCount, processingRectSize, stripSize, yStep, factor, candidates,
                rejectLevels, levelWeights, outputRejectLevels ) )
                break;
        }

    2.3 合并检测结果
        objects.resize(candidates.size());
        std::copy(candidates.begin(), candidates.end(), objects.begin());

        if( outputRejectLevels )
        {
            groupRectangles( objects, rejectLevels, levelWeights, minNeighbors, GROUP_EPS );
        }
        else
        {
            groupRectangles( objects, minNeighbors, GROUP_EPS );
        }


    单尺度检测函数流程
    2.2.1 根据所载入的特征计算积分图、积分直方图等
        // 计算当前图像的积分图,参考《图像特征->XXX特征之OpenCV-估计》
        if( !featureEvaluator->setImage( image, data.origWinSize ) )
            return false;
    2.2.2 根据是否输出检测级数并行目标检测
        vector<Rect> candidatesVector;
        vector<int> rejectLevels;
        vector<double> levelWeights;
        Mutex mtx;
        if( outputRejectLevels )
        {
            parallel_for_(Range(0, stripCount), CascadeClassifierInvoker( *this, processingRectSize, stripSize, yStep, factor,
                candidatesVector, rejectLevels, levelWeights, true, currentMask, &mtx));
            levels.insert( levels.end(), rejectLevels.begin(), rejectLevels.end() );
            weights.insert( weights.end(), levelWeights.begin(), levelWeights.end() );
        }
        else
        {
             parallel_for_(Range(0, stripCount), CascadeClassifierInvoker( *this, processingRectSize, stripSize, yStep, factor,
                candidatesVector, rejectLevels, levelWeights, false, currentMask, &mtx));
        }
        candidates.insert( candidates.end(), candidatesVector.begin(), candidatesVector.end() );

    CascadeClassifierInvoker函数的operator()实现具体的检测过程
        // 对于没有并行时range.start=0,range.end=1
        void operator()(const Range& range) const
        {
            Ptr<FeatureEvaluator> evaluator = classifier->featureEvaluator->clone();

            Size winSize(cvRound(classifier->data.origWinSize.width * scalingFactor), 
                         cvRound(classifier->data.origWinSize.height * scalingFactor));
            // strip=processingRectSize.height
            int y1 = range.start * stripSize; // 0
            int y2 = min(range.end * stripSize, processingRectSize.height); // processSizeRect.height也就是可以处理的高度,已经减去窗口高度
            for( int y = y1; y < y2; y += yStep )
            {
                for( int x = 0; x < processingRectSize.width; x += yStep )
                {
                    if ( (!mask.empty()) && (mask.at<uchar>(Point(x,y))==0)) {
                        continue;
                    }
                    // result=1表示通过了所有的分类器 <=0表示失败的级数
                    // gypWeight表示返回的阈值
                    double gypWeight;
                    int result = classifier->runAt(evaluator, Point(x, y), gypWeight);
    // 输出LOG
    #if defined (LOG_CASCADE_STATISTIC)
                    logger.setPoint(Point(x, y), result);
    #endif 
                    // 当返回级数的时候可以最后三个分类器不通过
                    if( rejectLevels )
                    {
                        if( result == 1 )
                            result = -(int)classifier->data.stages.size();
                        // 可以最后三个分类器不通过
                        if( classifier->data.stages.size() + result < 4 )
                        {
                            mtx->lock();
                            rectangles->push_back(Rect(cvRound(x*scalingFactor), cvRound(y*scalingFactor), winSize.width, winSize.height));
                            rejectLevels->push_back(-result);
                            levelWeights->push_back(gypWeight);
                            mtx->unlock();
                        }
                    }
                    // 不返回级数的时候通过所有的分类器才保存起来
                    else if( result > 0 )
                    {
                        mtx->lock();
                        rectangles->push_back(Rect(cvRound(x*scalingFactor), cvRound(y*scalingFactor),
                                                   winSize.width, winSize.height));
                        mtx->unlock();
                    }
                    // 如果一级都没有通过那么加大搜索步长
                    if( result == 0 )
                        x += yStep;
                }
            }
        }

    runAt函数实现某一检测窗口的检测
    int CascadeClassifier::runAt( Ptr<FeatureEvaluator>& evaluator, Point pt, double& weight )
    {
        CV_Assert( oldCascade.empty() );

        assert( data.featureType == FeatureEvaluator::HAAR ||
                data.featureType == FeatureEvaluator::LBP ||
                data.featureType == FeatureEvaluator::HOG );
        // 设置某一点处的特征,参考 《图像特征->XXX特征之OpenCV-估计》
        if( !evaluator->setWindow(pt) )
            return -1;
        // 如果为树桩,没有树枝
        if( data.isStumpBased )
        {
            if( data.featureType == FeatureEvaluator::HAAR )
                return predictOrderedStump<HaarEvaluator>( *this, evaluator, weight );
            else if( data.featureType == FeatureEvaluator::LBP )
                return predictCategoricalStump<LBPEvaluator>( *this, evaluator, weight );
            else if( data.featureType == FeatureEvaluator::HOG )
                return predictOrderedStump<HOGEvaluator>( *this, evaluator, weight );
            else
                return -2;
        }
        // 每个弱分类器不止一个node
        else
        {
            if( data.featureType == FeatureEvaluator::HAAR )
                return predictOrdered<HaarEvaluator>( *this, evaluator, weight );
            else if( data.featureType == FeatureEvaluator::LBP )
                return predictCategorical<LBPEvaluator>( *this, evaluator, weight );
            else if( data.featureType == FeatureEvaluator::HOG )
                return predictOrdered<HOGEvaluator>( *this, evaluator, weight );
            else
                return -2;
        }
    }

    predictOrdered*函数实现判断当前检测窗口的判断
    // HAAR与HOG特征的多node检测
    template<class FEval>
    inline int predictOrdered( CascadeClassifier& cascade, Ptr<FeatureEvaluator> &_featureEvaluator, double& sum )
    {
        int nstages = (int)cascade.data.stages.size();
        int nodeOfs = 0, leafOfs = 0;
        FEval& featureEvaluator = (FEval&)*_featureEvaluator;
        float* cascadeLeaves = &cascade.data.leaves[0];
        CascadeClassifier::Data::DTreeNode* cascadeNodes = &cascade.data.nodes[0];
        CascadeClassifier::Data::DTree* cascadeWeaks = &cascade.data.classifiers[0];
        CascadeClassifier::Data::Stage* cascadeStages = &cascade.data.stages[0];

        // 遍历每个强分类器
        for( int si = 0; si < nstages; si++ )
        {
            CascadeClassifier::Data::Stage& stage = cascadeStages[si];
            int wi, ntrees = stage.ntrees;
            sum = 0;
            // 遍历每个弱分类器
            for( wi = 0; wi < ntrees; wi++ )
            {
                CascadeClassifier::Data::DTree& weak = cascadeWeaks[stage.first + wi];
                int idx = 0, root = nodeOfs;
                // 遍历每个节点
                do
                {
                    // 选择一个node:root和idx初始化为0,即第一个node
                    CascadeClassifier::Data::DTreeNode& node = cascadeNodes[root + idx];
                    // 计算当前node特征池编号下的特征值
                    double val = featureEvaluator(node.featureIdx);
                    // 如果val小于node阈值则选择左子树,否则选择右子树
                    idx = val < node.threshold ? node.left : node.right;
                }  while( idx > 0 );
                // 累加最终的叶子节点
                sum += cascadeLeaves[leafOfs - idx];
                nodeOfs += weak.nodeCount;
                leafOfs += weak.nodeCount + 1;
            }
            // 判断所有叶子节点累加和是否小于强分类器阈值,小于强分类器阈值则失败
            if( sum < stage.threshold )
                return -si;
        }
        // 通过了所有的强分类器返回1,否则返回失败的分类器
        return 1;
    }

    // LBP特征的多node检测
    template<class FEval>
    inline int predictCategorical( CascadeClassifier& cascade, Ptr<FeatureEvaluator> &_featureEvaluator, double& sum )
    {
        int nstages = (int)cascade.data.stages.size();
        int nodeOfs = 0, leafOfs = 0;
        FEval& featureEvaluator = (FEval&)*_featureEvaluator;
        size_t subsetSize = (cascade.data.ncategories + 31)/32;
        int* cascadeSubsets = &cascade.data.subsets[0];
        float* cascadeLeaves = &cascade.data.leaves[0];
        CascadeClassifier::Data::DTreeNode* cascadeNodes = &cascade.data.nodes[0];
        CascadeClassifier::Data::DTree* cascadeWeaks = &cascade.data.classifiers[0];
        CascadeClassifier::Data::Stage* cascadeStages = &cascade.data.stages[0];

        for(int si = 0; si < nstages; si++ )
        {
            CascadeClassifier::Data::Stage& stage = cascadeStages[si];
            int wi, ntrees = stage.ntrees;
            sum = 0;

            for( wi = 0; wi < ntrees; wi++ )
            {
                CascadeClassifier::Data::DTree& weak = cascadeWeaks[stage.first + wi];
                int idx = 0, root = nodeOfs;
                do
                {
                    CascadeClassifier::Data::DTreeNode& node = cascadeNodes[root + idx];
                    // c为0-255之间的数
                    int c = featureEvaluator(node.featureIdx);
                    // 获取当前node的subset头位置
                    const int* subset = &cascadeSubsets[(root + idx)*subsetSize]; // LBP:subsetSize=8
                    // 判断选择左子树还是右子树
                    idx = (subset[c>>5] & (1 << (c & 31))) ? node.left : node.right;
                    // c>>5表示将c右移5位,选择高3位,0-7之间
                    // c&31表示低5位,1<<(c&31)选择低5位后左移1位
                    // 将上面的数按位与,如果最后结果不为0表示选择左子树,否则选择右子树
                }
                while( idx > 0 );
                sum += cascadeLeaves[leafOfs - idx];
                nodeOfs += weak.nodeCount;
                leafOfs += weak.nodeCount + 1;
            }
            if( sum < stage.threshold )
                return -si;
        }
        return 1;
    }

    // HAAR与HOG特征的单node检测
    template<class FEval>
    inline int predictOrderedStump( CascadeClassifier& cascade, Ptr<FeatureEvaluator> &_featureEvaluator, double& sum )
    {
        int nodeOfs = 0, leafOfs = 0;
        FEval& featureEvaluator = (FEval&)*_featureEvaluator;
        float* cascadeLeaves = &cascade.data.leaves[0];
        CascadeClassifier::Data::DTreeNode* cascadeNodes = &cascade.data.nodes[0];
        CascadeClassifier::Data::Stage* cascadeStages = &cascade.data.stages[0];

        int nstages = (int)cascade.data.stages.size();
        // 遍历每个强分类器
        for( int stageIdx = 0; stageIdx < nstages; stageIdx++ )
        {
            CascadeClassifier::Data::Stage& stage = cascadeStages[stageIdx];
            sum = 0.0;

            int ntrees = stage.ntrees;
            // 遍历每个弱分类器
            for( int i = 0; i < ntrees; i++, nodeOfs++, leafOfs+= 2 )
            {
                CascadeClassifier::Data::DTreeNode& node = cascadeNodes[nodeOfs];
                double value = featureEvaluator(node.featureIdx);
                sum += cascadeLeaves[ value < node.threshold ? leafOfs : leafOfs + 1 ];
            }

            if( sum < stage.threshold )
                return -stageIdx;
        }

        return 1;
    }
    // LBP特征的单node检测
    template<class FEval>
    inline int predictCategoricalStump( CascadeClassifier& cascade, Ptr<FeatureEvaluator> &_featureEvaluator, double& sum )
    {
        int nstages = (int)cascade.data.stages.size();
        int nodeOfs = 0, leafOfs = 0;
        FEval& featureEvaluator = (FEval&)*_featureEvaluator;
        size_t subsetSize = (cascade.data.ncategories + 31)/32;
        int* cascadeSubsets = &cascade.data.subsets[0];
        float* cascadeLeaves = &cascade.data.leaves[0];
        CascadeClassifier::Data::DTreeNode* cascadeNodes = &cascade.data.nodes[0];
        CascadeClassifier::Data::Stage* cascadeStages = &cascade.data.stages[0];

    #ifdef HAVE_TEGRA_OPTIMIZATION
        float tmp = 0; // float accumulator -- float operations are quicker
    #endif
        for( int si = 0; si < nstages; si++ )
        {
            CascadeClassifier::Data::Stage& stage = cascadeStages[si];
            int wi, ntrees = stage.ntrees;
    #ifdef HAVE_TEGRA_OPTIMIZATION
            tmp = 0;
    #else
            sum = 0;
    #endif

            for( wi = 0; wi < ntrees; wi++ )
            {
                CascadeClassifier::Data::DTreeNode& node = cascadeNodes[nodeOfs];
                int c = featureEvaluator(node.featureIdx);
                const int* subset = &cascadeSubsets[nodeOfs*subsetSize];
    #ifdef HAVE_TEGRA_OPTIMIZATION
                tmp += cascadeLeaves[ subset[c>>5] & (1 << (c & 31)) ? leafOfs : leafOfs+1];
    #else
                sum += cascadeLeaves[ subset[c>>5] & (1 << (c & 31)) ? leafOfs : leafOfs+1];
    #endif
                nodeOfs++;
                leafOfs += 2;
            }
    #ifdef HAVE_TEGRA_OPTIMIZATION
            if( tmp < stage.threshold ) {
                sum = (double)tmp;
                return -si;
            }
    #else
            if( sum < stage.threshold )
                return -si;
    #endif
        }

    #ifdef HAVE_TEGRA_OPTIMIZATION
        sum = (double)tmp;
    #endif

        return 1;
    }
    }

    展开全文
  • java源码包---java 源码 大量 实例

    千次下载 热门讨论 2013-04-18 23:15:26
    Applet钢琴模拟程序java源码 2个目标文件,提供基本的音乐编辑功能。编辑音乐软件的朋友,这款实例会对你有所帮助。 Calendar万年历 1个目标文件 EJB 模拟银行ATM流程及操作源代码 6个目标文件,EJB来模拟银行ATM...
  • Applet钢琴模拟程序java源码 2个目标文件,提供基本的音乐编辑功能。编辑音乐软件的朋友,这款实例会对你有所帮助。 Calendar万年历 1个目标文件 EJB 模拟银行ATM流程及操作源代码 6个目标文件,EJB来模拟银行ATM机...
  • Java 源码包 Applet钢琴模拟程序java源码 2个目标文件,提供基本的音乐编辑功能。编辑音乐软件的朋友,这款实例会对你有所帮助。 Calendar万年历 1个目标文件 EJB 模拟银行ATM流程及操作源代码 6个目标文件,EJB来...
  • JAVA上百实例源码以及开源项目

    千次下载 热门讨论 2016-01-03 17:37:40
    笔者当初为了学习JAVA,收集了很多经典源码源码难易程度分为初级、中级、高级等,详情看源码列表,需要的可以直接下载! 这些源码反映了那时那景笔者对未来的盲目,对代码的热情、执着,对IT的憧憬、向往!此时此...
  • 一切准备就绪,下面我们来看强分类器是如何训练的,该过程在CvCascadeBoost::train函数中完成,代码如下: bool CvCascadeBoost::train( const CvFeatureEvaluator* _featureEvaluator,  int _numSamples, ...

           下面的内容很长,倒杯水(有茶或者咖啡更好),带上耳机,准备就绪再往下看。下面我们来看强分类器是如何训练的,该过程在CvCascadeBoost::train函数中完成,代码如下:

    bool CvCascadeBoost::train( const CvFeatureEvaluator* _featureEvaluator,
                               int _numSamples,
                               int _precalcValBufSize, int _precalcIdxBufSize,
                               const CvCascadeBoostParams& _params )
    {
        bool isTrained = false;
        CV_Assert( !data );
        clear();
    	// 样本的数据都存在 _featureEvaluator 里面,这里把训练相关的数据都
    	// 用CvCascadeBoostTrainData类封装,内部创建了运行时需要的一些内存
    	// 方便后面使用
        data = new CvCascadeBoostTrainData( _featureEvaluator, _numSamples,
                                            _precalcValBufSize, _precalcIdxBufSize, _params );
        CvMemStorage *storage = cvCreateMemStorage();
    	// 创建一个 CvSeq 序列,存放一个强分类器的所有弱分类器
        weak = cvCreateSeq( 0, sizeof(CvSeq), sizeof(CvBoostTree*), storage );
        storage = 0;
    
    
        set_params( _params );
        if ( (_params.boost_type == LOGIT) || (_params.boost_type == GENTLE) )
    	{
    		// 从_featureEvaluator->cls 中拷贝样本的类别信息到 data->responses
    		// 因为这两种boost方法计算式把类别从0/1该为-1/+1使用
    		data->do_responses_copy();
    	}
    	// 设置所有样本初始权值为1/n
        update_weights( 0 );
    
    
        cout << "+----+---------+---------+" << endl;
        cout << "|  N |    HR   |    FA   |" << endl;
        cout << "+----+---------+---------+" << endl;
    
    
        do
        {
    		// 训练一个弱分类器,弱分类器是棵CART树
            CvCascadeBoostTree* tree = new CvCascadeBoostTree;
            if( !tree->train( data, subsample_mask, this ) )
            {
                delete tree;
                break;
            }
    		// 得到弱分类器加入序列
            cvSeqPush( weak, &tree );
    		// 根据boost公式更新样本数据的权值
            update_weights( tree );
    		// 根据用户输入参数,把一定比例的(0.05)权值最小的样本去掉
            trim_weights();
    		// subsample_mask 保存每个样本是否参数训练的标记(值为0/1)
    		// 没有可用样本了,退出训练
            if( cvCountNonZero(subsample_mask) == 0 )
                break;
        } // 如果当前强分类器达到了设置的虚警率要求或弱分类数目达到上限停止
        while( !isErrDesired() && (weak->total < params.weak_count) );
    
    
        if(weak->total > 0)
        {
            data->is_classifier = true;
            data->free_train_data();
            isTrained = true;
        }
        else
            clear();
    
    
        return isTrained;
    }
    


            代码中首先把训练相关的数据用CvCascadeBoostTrainData封装,一遍后面传递给其它函数,将每个样本的权值设置为1/N,N为总样本数。此后便开始进入弱分类器训练循环。我们接着来看弱分类器的训练,代码位于CvCascadeBoostTree::train中。
    bool
    CvBoostTree::train( CvDTreeTrainData* _train_data,
                        const CvMat* _subsample_idx, CvBoost* _ensemble )
    {
        clear();
        ensemble = _ensemble;
        data = _train_data;
        data->shared = true;
        return do_train( _subsample_idx );
    }

            注意这里的参数_ensemble实际是CvCascadeBoost类型的指针,转入调用CvBoostTree::do_train函数,传入的参数为参与训练的样本的索引数组,具体代码如下:
    bool CvDTree::do_train( const CvMat* _subsample_idx )
    {
        bool result = false;
    
    
        CV_FUNCNAME( "CvDTree::do_train" );
    
    
        __BEGIN__;
    	// 创建CART树根节点,设置根节点是数据为输入数据集
        root = data->subsample_data( _subsample_idx );
    	// 开始分割节点,向树上增加子节点,构成CART树。如果设置弱分类器
        CV_CALL( try_split_node(root));
    
    
        if( root->split )
        {
            CV_Assert( root->left );
            CV_Assert( root->right );
    
    
            if( data->params.cv_folds > 0 )
                CV_CALL( prune_cv() );
    
    
            if( !data->shared )
                data->free_train_data();
    
    
            result = true;
        }
    
    
        __END__;
    
    
        return result;
    }
    

            创建一个root节点后,对root节点进行分割,调用try_split_node函数实现,代码如下:
    void CvDTree::try_split_node( CvDTreeNode* node )
    {
        CvDTreeSplit* best_split = 0;
        int i, n = node->sample_count, vi;
        bool can_split = true;
        double quality_scale;
    	// 计算当前节点的 value,节点的风险 node_risk
        calc_node_value( node );
    	// 节点样本数目过少样本数(默认为10) 或者树深度达到设置值(默认为1),也就是一个分割节点
        if( node->sample_count <= data->params.min_sample_count ||
            node->depth >= data->params.max_depth )
            can_split = false;
    	// is_classifer:false
        if( can_split && data->is_classifier )
        {
            // check if we have a "pure" node,
            // we assume that cls_count is filled by calc_node_value()
            int* cls_count = data->counts->data.i;
            int nz = 0, m = data->get_num_classes();
            for( i = 0; i < m; i++ )
                nz += cls_count[i] != 0;
            if( nz == 1 ) // there is only one class
                can_split = false;
        }
        else if( can_split )
        {
    		// 平均error值很小了,说明已经分得很好,没必要继续下去 regression_accuracy (0.01)
            if( sqrt(node->node_risk)/n < data->params.regression_accuracy )
                can_split = false;
        }
    
    
        if( can_split )
        {
    		// 调用函数找到最优分割,弱分类器训练的重头戏
            best_split = find_best_split(node);
            // TODO: check the split quality ...
            node->split = best_split;
        }
        if( !can_split || !best_split )
        {
            data->free_node_data(node);
            return;
        }
    	// ignore this
        quality_scale = calc_node_dir( node );
    	// 级联参数 use_surrogates = use_1se_rule = truncate_pruned_tree = false;
        if( data->params.use_surrogates )
        {
            // find all the surrogate splits
            // and sort them by their similarity to the primary one
            for( vi = 0; vi < data->var_count; vi++ )
            {
                CvDTreeSplit* split;
                int ci = data->get_var_type(vi);
    
    
                if( vi == best_split->var_idx )
                    continue;
    
    
                if( ci >= 0 )
                    split = find_surrogate_split_cat( node, vi );
                else
                    split = find_surrogate_split_ord( node, vi );
    
    
                if( split )
                {
                    // insert the split
                    CvDTreeSplit* prev_split = node->split;
                    split->quality = (float)(split->quality*quality_scale);
    
    
                    while( prev_split->next &&
                           prev_split->next->quality > split->quality )
                        prev_split = prev_split->next;
                    split->next = prev_split->next;
                    prev_split->next = split;
                }
            }
        }
    	// 创建左右子节点,把node的节点的数据分给left,right子节点
    split_node_data( node );
    // 递归实现子节点划分,分割左右子节点
        try_split_node( node->left );
        try_split_node( node->right );
    }

            创建一个新的分割节点最为关键的就是要找到一个特征和阈值的组合,该分割能够把数据划分得最好(在误差的意义上),在find_best_split(node)中实现,我们看代码:
    CvDTreeSplit* CvDTree::find_best_split( CvDTreeNode* node )
    {
        DTreeBestSplitFinder finder( this, node );
    	// 在开启TBB情况下,多核并行处理
        cv::parallel_reduce(cv::BlockedRange(0, data->var_count), finder);
    	// 保存最优分割
        CvDTreeSplit *bestSplit = 0;
        if( finder.bestSplit->quality > 0 )
        {
            bestSplit = data->new_split_cat( 0, -1.0f );
            memcpy( bestSplit, finder.bestSplit, finder.splitSize );
        }
    
    
        return bestSplit;
    }

            关键代码位于的DTreeBestSplitFinder::operator()函数中,代码遍历特征序号为range.begin()--range.end()之间的特征,调用tree->find_split_ord_reg函数对特征vi找到最优的阈值。

    void DTreeBestSplitFinder::operator()(const BlockedRange& range)
    {
        int vi, vi1 = range.begin(), vi2 = range.end();
        int n = node->sample_count;
        CvDTreeTrainData* data = tree->get_data();
        AutoBuffer<uchar> inn_buf(2*n*(sizeof(int) + sizeof(float)));
    	// 遍历特征 vi
        for( vi = vi1; vi < vi2; vi++ )
        {
            CvDTreeSplit *res;
    		// 取特征 数值特征为 -(vi+1) < 0,编码类型特征 vi >= 0,仅用作标识
            int ci = data->get_var_type(vi);
            if( node->get_num_valid(vi) <= 1 )
                continue;
    
    
            if( data->is_classifier )
            {
                if( ci >= 0 )
                    res = tree->find_split_cat_class( node, vi, bestSplit->quality, split, (uchar*)inn_buf );
                else
                    res = tree->find_split_ord_class( node, vi, bestSplit->quality, split, (uchar*)inn_buf );
            }
            else
            {
                if( ci >= 0 )
                    res = tree->find_split_cat_reg( node, vi, bestSplit->quality, split, (uchar*)inn_buf );
                else // 找到特征vi对应的最优分割,也就是求取最优阈值 
                    res = tree->find_split_ord_reg( node, vi, bestSplit->quality, split, (uchar*)inn_buf );
            }
    // 更新bestSplit为quality最高的分割
            if( res && bestSplit->quality < split->quality )
                    memcpy( (CvDTreeSplit*)bestSplit, (CvDTreeSplit*)split, splitSize );
        }
    }
    DTreeBestSplitFinder::join函数的作用是在使用TBB库并行计算时,把不同线程的运行结果进行合并,保存最优结果,这里保留quality更好的分割节点。
    void DTreeBestSplitFinder::join( DTreeBestSplitFinder& rhs )
    {
        if( bestSplit->quality < rhs.bestSplit->quality )
            memcpy( (CvDTreeSplit*)bestSplit, (CvDTreeSplit*)rhs.bestSplit, splitSize );
    }
    

            Haar特征的值两个(或三个)矩形区域均值之差,是一个浮点数值。对所有样本求取特征值得到一个数组,一个分割就是找到一个最有的阈值把数据分成左右两部分,使得两边的总体误差最小。回顾一下原理部分讲过的例子,红色为正样本,绿色为负样本,阈值就是找一条垂直x轴的分割线。

    Haar特征值数据

    取阈值为1.5

    取阈值为5.5

            代码首先调用get_ord_var_data计算当期节点上所有样本的第vi个haar特征值,然后按照从小到大排序返回排序后的值数组values,然后根据values找到最优分割阈值(也就是移动图中的蓝色分割线)。代码依次测试最优阈值为values[i]和values[i+1]的中值,找到最小化总体误差的一个,返回得到的split。
    CvDTreeSplit*
    CvBoostTree::find_split_ord_reg( CvDTreeNode* node, int vi, float init_quality, CvDTreeSplit* _split, uchar* _ext_buf )
    {
        const float epsilon = FLT_EPSILON*2;
        const double* weights = ensemble->get_subtree_weights()->data.db;
        int n = node->sample_count;
        int n1 = node->get_num_valid(vi);
    
    
        cv::AutoBuffer<uchar> inn_buf;
        if( !_ext_buf )
            inn_buf.allocate(2*n*(sizeof(int)+sizeof(float)));
        uchar* ext_buf = _ext_buf ? _ext_buf : (uchar*)inn_buf;
    
    
        float* values_buf = (float*)ext_buf;
        int* indices_buf = (int*)(values_buf + n);
        int* sample_indices_buf = indices_buf + n;
        const float* values = 0;
        const int* indices = 0;
    	// 计算所有样本的第vi个haar特征值,values为特征值数组,已经从小到大排序
        data->get_ord_var_data( node, vi, values_buf, indices_buf, &values, &indices, sample_indices_buf );
        float* responses_buf = (float*)(indices_buf + n);
    	// 取所有样本的真是响应值(+1/-1)
        const float* responses = data->get_ord_responses( node, responses_buf, sample_indices_buf );
    
    
        int i, best_i = -1;
        double L = 0, R = weights[n]; // R 为总权值和
        double best_val = init_quality, lsum = 0, rsum = node->value*R;
    
    
        // compensate for missing values
        for( i = n1; i < n; i++ )
        {
            int idx = indices[i];
            double w = weights[idx];
            rsum -= responses[idx]*w;
            R -= w;
        }
    
    
        // find the optimal split
        for( i = 0; i < n1 - 1; i++ )
        {
            int idx = indices[i];
            double w = weights[idx];
            double t = responses[idx]*w;
            L += w; R -= w;       // L为左边权值和,R为右边权值和
            lsum += t; rsum -= t; // lsum左边正样本权值和-负样本权值和
    		                      // rsum右边正样本权值和-负样本权值和
            if( values[i] + epsilon < values[i+1] )
            {
    			// 计算当前分割下的error
                double val = (lsum*lsum*R + rsum*rsum*L)/(L*R);
                if( best_val < val )
                {
                    best_val = val;
                    best_i = i;
                }
            }
        }
    
    
        CvDTreeSplit* split = 0;
        if( best_i >= 0 )
        {
            split = _split ? _split : data->new_split_ord( 0, 0.0f, 0, 0, 0.0f );
            split->var_idx = vi;
    		// 最优阈值取前后两个特征值的中间值
            split->ord.c = (values[best_i] + values[best_i+1])*0.5f;
            split->ord.split_point = best_i; // 最优特征值序号
            split->inversed = 0;
            split->quality = (float)best_val;// 最小error
        }
        return split;
    }

            我们再来看看如何求取haar特征的,get_ord_var_data函数代码较长,没有注释的部分大可直接略过,直接看注释部分,调用(*featureEvaluator)( vi, sampleIndices[i])计算第smapleIndices[i]个样本的第vi个特征值。计算完成后,调用icvSortIntAux按照从小到大排序。
    void CvCascadeBoostTrainData::get_ord_var_data( CvDTreeNode* n, int vi, float* ordValuesBuf, int* sortedIndicesBuf,
            const float** ordValues, const int** sortedIndices, int* sampleIndicesBuf )
    {
        int nodeSampleCount = n->sample_count;
        const int* sampleIndices = get_sample_indices(n, sampleIndicesBuf);
    
    
        if ( vi < numPrecalcIdx )
        {
            if( !is_buf_16u )
                *sortedIndices = buf->data.i + n->buf_idx*get_length_subbuf() + vi*sample_count + n->offset;
            else
            {
                const unsigned short* shortIndices = (const unsigned short*)(buf->data.s + n->buf_idx*get_length_subbuf() +
                                                        vi*sample_count + n->offset );
                for( int i = 0; i < nodeSampleCount; i++ )
                    sortedIndicesBuf[i] = shortIndices[i];
    
    
                *sortedIndices = sortedIndicesBuf;
            }
    
    
            if( vi < numPrecalcVal )
            {
                for( int i = 0; i < nodeSampleCount; i++ )
                {
                    int idx = (*sortedIndices)[i];
                    idx = sampleIndices[idx];
                    ordValuesBuf[i] =  valCache.at<float>( vi, idx);
                }
            }
            else
            {
                for( int i = 0; i < nodeSampleCount; i++ )
                {
                    int idx = (*sortedIndices)[i];
                    idx = sampleIndices[idx];
                    ordValuesBuf[i] = (*featureEvaluator)( vi, idx);
                }
            }
        }
        else // vi >= numPrecalcIdx 特征没有计算 valCache
        {
            cv::AutoBuffer<float> abuf(nodeSampleCount);
            float* sampleValues = &abuf[0];
    
    
            if ( vi < numPrecalcVal )
            {
                for( int i = 0; i < nodeSampleCount; i++ )
                {
                    sortedIndicesBuf[i] = i;
                    sampleValues[i] = valCache.at<float>( vi, sampleIndices[i] );
                }
            }
            else
            {	// 计算节点样本的特征值,通过featureEvaluator.operator()得到
    			// 样本索引存在sampleIndices里面
                for( int i = 0; i < nodeSampleCount; i++ )
                {
                    sortedIndicesBuf[i] = i;
    				//  调用featureEvaluator.operator()计算第vi个haar特征值
                    sampleValues[i] = (*featureEvaluator)( vi, sampleIndices[i]);
                }
            }
    		// 对索引进行排序,特征值从小到大,sampleValues 里面放的是特征值
    		// icvSortIntAux 根据sampleValues的值排序
            icvSortIntAux( sortedIndicesBuf, nodeSampleCount, &sampleValues[0] );
            for( int i = 0; i < nodeSampleCount; i++ )
                ordValuesBuf[i] = (&sampleValues[0])[sortedIndicesBuf[i]];
            *sortedIndices = sortedIndicesBuf;
        }
    
    
    	// *sortedIndices  样本排序后的索引数组,指示特征值在排序中的位置
    	// *ordValues      特征值排序结果,用户计算最优分割
        *ordValues = ordValuesBuf;
    }


    计算特征值
            回顾一下haar特征,两个或三个矩形加权求和。权值的作用是为了平衡区域的面积,例如A和B两个特征,黑白面积一样,权值都为1:1,而C特征,实际是用包含黑色的大矩形去减中间的黑色矩形,因此权值为1:3,也就是黑色矩形权值为3。

            前面提到过使用积分图来加速求取一个矩形内的灰度和。积分图中每个点(x,y)保存的是从(0,0)开始的矩形的灰度和。

            在积分图上求一个矩形内灰度和在积分图上只需要四个点进行运算,例如矩形D的和为v[4]+v[1]-v[2]-v[3]。其中v[]表示积分图对应的值。

            现在来看代码的实现,调用calc函数计算矩形区域的加权和,为了加快计算,矩形不是使用起点和宽高表示,创建了fastRect结构来保存上图中1234四个点相对于图像原点的偏离量,记作p0,p1,p2,p3。
            得到加权和之后,在operator()函数中还除以了normfactor归一化系数,这一值就是图像的标准差,去除因不同对比度造成的差异。

    inline float CvHaarEvaluator::operator()(int featureIdx, int sampleIdx) const
    {
        float nf = normfactor.at<float>(0, sampleIdx);
        return !nf ? 0.0f : (features[featureIdx].calc( sum, tilted, sampleIdx)/nf);
    }
    
    
    inline float CvHaarEvaluator::Feature::calc( const cv::Mat &_sum, const cv::Mat &_tilted, size_t y) const
    {
        const int* img = tilted ? _tilted.ptr<int>((int)y) : _sum.ptr<int>((int)y);
        float ret = rect[0].weight * (img[fastRect[0].p0] - img[fastRect[0].p1] - img[fastRect[0].p2] + img[fastRect[0].p3] ) +
            rect[1].weight * (img[fastRect[1].p0] - img[fastRect[1].p1] - img[fastRect[1].p2] + img[fastRect[1].p3] );
        if( rect[2].weight != 0.0f )
            ret += rect[2].weight * (img[fastRect[2].p0] - img[fastRect[2].p1] - img[fastRect[2].p2] + img[fastRect[2].p3] );
        return ret;
    }

    代码走到这里,一个弱分类器的流程就全部走完了。我们得到了一个弱分类器。我们继续回到强分类器训练流程中看看接下来做了什么工作。
    bool CvCascadeBoost::train( const CvFeatureEvaluator* _featureEvaluator,
                               int _numSamples,
                               int _precalcValBufSize, int _precalcIdxBufSize,
                               const CvCascadeBoostParams& _params )
    {
        bool isTrained = false;
        // ...此前代码略过
    	// 设置所有样本初始权值为1/n
        update_weights( 0 );
    
    
        cout << "+----+---------+---------+" << endl;
        cout << "|  N |    HR   |    FA   |" << endl;
        cout << "+----+---------+---------+" << endl;
    
    
        do
        {
    		// 训练一个弱分类器,弱分类器是棵CART树
            CvCascadeBoostTree* tree = new CvCascadeBoostTree;
            if( !tree->train( data, subsample_mask, this ) )
            {
                delete tree;
                break;
            }
    		// 得到弱分类器加入序列
            cvSeqPush( weak, &tree );
    		// 根据boost公式更新样本数据的权值
            update_weights( tree );
    		// 根据用户输入参数,把一定比例的(0.05)权值最小的样本去掉
            trim_weights();
    		// subsample_mask 保存每个样本是否参数训练的标记(值为0/1)
    		// 没有可用样本了,退出训练
            if( cvCountNonZero(subsample_mask) == 0 )
                break;
        } // 如果当前强分类器达到了设置的虚警率要求或弱分类数目达到上限停止
        while( !isErrDesired() && (weak->total < params.weak_count) );
    
    
        if(weak->total > 0)
        {
            data->is_classifier = true;
            data->free_train_data();
            isTrained = true;
        }
        else
            clear();
    
    
        return isTrained;
    }


            首先将得到的弱分类器加入序列weak,然后根据Adaboost公式(我们采用的Gentle adaboost)更新所有样本的权值,为下一轮弱分类器训练作准备。更新权值调用trim_weights函数,根据的设置,将一定比例的权值很小的样本从训练集中去除(能够提高训练的速度,对性能有一定的影响)。
    我们来看权值更新是如何进行的。当传入tree为0时,设置每个样本权值为1/n。得到一个弱分类器后,传入上一个弱分类器tree,样本i更新后的为:w_i *= exp(-y_i*f(x_i)),其中y_i为样本的真实响应(+1/-1值)f(x_i)表示输入的弱分类器tree对样本i的响应值。最后将调整后的样本总权值调整为1。

    void CvCascadeBoost::update_weights( CvBoostTree* tree )
    {
        int n = data->sample_count;
        double sumW = 0.;
        int step = 0;
        float* fdata = 0;
        int *sampleIdxBuf;
        const int* sampleIdx = 0;
        int inn_buf_size = ((params.boost_type == LOGIT) || (params.boost_type == GENTLE) ? n*sizeof(int) : 0) +
                           ( !tree ? n*sizeof(int) : 0 );
        cv::AutoBuffer<uchar> inn_buf(inn_buf_size);
        uchar* cur_inn_buf_pos = (uchar*)inn_buf;
        if ( (params.boost_type == LOGIT) || (params.boost_type == GENTLE) )
        {
            step = CV_IS_MAT_CONT(data->responses_copy->type) ?
                1 : data->responses_copy->step / CV_ELEM_SIZE(data->responses_copy->type);
    		// data->responses_copy = data->responses 为样本的真实相应(正样本+1,负样本-1)
            fdata = data->responses_copy->data.fl;
            sampleIdxBuf = (int*)cur_inn_buf_pos; cur_inn_buf_pos = (uchar*)(sampleIdxBuf + n);
            sampleIdx = data->get_sample_indices( data->data_root, sampleIdxBuf );
        }
        CvMat* buf = data->buf;
        size_t length_buf_row = data->get_length_subbuf();
        if( !tree ) // before training the first tree, initialize weights and other parameters
        {
            int* classLabelsBuf = (int*)cur_inn_buf_pos; cur_inn_buf_pos = (uchar*)(classLabelsBuf + n);
            // 实际上也是取data->responses数据指针,此时正样本为1,负样本为0
    		const int* classLabels = data->get_class_labels(data->data_root, classLabelsBuf);
            // in case of logitboost and gentle adaboost each weak tree is a regression tree,
            // so we need to convert class labels to floating-point values
            double w0 = 1./n;
            double p[2] = { 1, 1 };
    
    
            cvReleaseMat( &orig_response );
            cvReleaseMat( &sum_response );
            cvReleaseMat( &weak_eval );
            cvReleaseMat( &subsample_mask );
            cvReleaseMat( &weights );
    
    
            orig_response = cvCreateMat( 1, n, CV_32S );
            weak_eval = cvCreateMat( 1, n, CV_64F );
            subsample_mask = cvCreateMat( 1, n, CV_8U );
            weights = cvCreateMat( 1, n, CV_64F );
            subtree_weights = cvCreateMat( 1, n + 2, CV_64F );
    
    
            if (data->is_buf_16u)
            {
                unsigned short* labels = (unsigned short*)(buf->data.s + data->data_root->buf_idx*length_buf_row +
                    data->data_root->offset + (data->work_var_count-1)*data->sample_count);
                for( int i = 0; i < n; i++ )
                {
                    // save original categorical responses {0,1}, convert them to {-1,1}
    				// 将样本标签{0,1}转到{-1,+1}
                    orig_response->data.i[i] = classLabels[i]*2 - 1;
                    // make all the samples active at start.
                    // later, in trim_weights() deactivate/reactive again some, if need
    				// subsample_mask标识每个样本是否使用,为1表示参与训练
                    subsample_mask->data.ptr[i] = (uchar)1;
                    // make all the initial weights the same.
    				// 设置样本的初始权值为1/n,每个样本权值一样
                    weights->data.db[i] = w0*p[classLabels[i]];
                    // set the labels to find (from within weak tree learning proc)
                    // the particular sample weight, and where to store the response.
                    labels[i] = (unsigned short)i;
                }
            }
            else
            {
                int* labels = buf->data.i + data->data_root->buf_idx*length_buf_row +
                    data->data_root->offset + (data->work_var_count-1)*data->sample_count;
    
    
                for( int i = 0; i < n; i++ )
                {
                    // save original categorical responses {0,1}, convert them to {-1,1}
                    orig_response->data.i[i] = classLabels[i]*2 - 1;
                    subsample_mask->data.ptr[i] = (uchar)1;
                    weights->data.db[i] = w0*p[classLabels[i]];
                    labels[i] = i;
                }
            }
    
    
            if( params.boost_type == LOGIT )
            {
                sum_response = cvCreateMat( 1, n, CV_64F );
    
    
                for( int i = 0; i < n; i++ )
                {
                    sum_response->data.db[i] = 0;
                    fdata[sampleIdx[i]*step] = orig_response->data.i[i] > 0 ? 2.f : -2.f;
                }
    
    
                // in case of logitboost each weak tree is a regression tree.
                // the target function values are recalculated for each of the trees
                data->is_classifier = false;
            }
            else if( params.boost_type == GENTLE )
            {
    			// 设置 data->reponse 为{-1,+1}
                for( int i = 0; i < n; i++ )
                    fdata[sampleIdx[i]*step] = (float)orig_response->data.i[i];
    
    
                data->is_classifier = false;
            }
        }
        else
        {
            // at this moment, for all the samples that participated in the training of the most
            // recent weak classifier we know the responses. For other samples we need to compute them
            if( have_subsample )
            {
                // invert the subsample mask
                cvXorS( subsample_mask, cvScalar(1.), subsample_mask );
    
    
                // run tree through all the non-processed samples
                for( int i = 0; i < n; i++ )
                    if( subsample_mask->data.ptr[i] )
                    {
                        weak_eval->data.db[i] = ((CvCascadeBoostTree*)tree)->predict( i )->value;
                    }
            }
    
    
            // ... 其他boost方式处理,忽略
            {
                // Gentle AdaBoost:
                //   weak_eval[i] = f(x_i) in [-1,1]
                //   w_i *= exp(-y_i*f(x_i))
                assert( params.boost_type == GENTLE );
    
    
                for( int i = 0; i < n; i++ )
                    weak_eval->data.db[i] *= -orig_response->data.i[i];
    
    
                cvExp( weak_eval, weak_eval );
    
    
                for( int i = 0; i < n; i++ )
                {
                    double w = weights->data.db[i] * weak_eval->data.db[i];
                    weights->data.db[i] = w;
                    sumW += w;
                }
            }
        }
    
    
        // renormalize weights
        if( sumW > FLT_EPSILON )
        {
            sumW = 1./sumW;
            for( int i = 0; i < n; ++i )
                weights->data.db[i] *= sumW;
        }
    }

            没增加一个弱分类器都是对当前强分类器的增强,我们需要检查当前的强分类器是否已经足够强,也就是它能否满足设置的性能指标,命中率(也就是recall)和虚警率达到要求。在isErrorDisired函数中进行,我们来看个究竟。
    bool CvCascadeBoost::isErrDesired()
    {
        int sCount = data->sample_count,
            numPos = 0, numNeg = 0, numFalse = 0, numPosTrue = 0;
        vector<float> eval(sCount);
    	// 计算每个正样本的弱分类器输出之和,predict函数中完成
        for( int i = 0; i < sCount; i++ )
            if( ((CvCascadeBoostTrainData*)data)->featureEvaluator->getCls( i ) == 1.0F )
                eval[numPos++] = predict( i, true );
    	// 所有正样本的该值进行从小到大排序
        icvSortFlt( &eval[0], numPos, 0 );
    	// 因为我们要求正样本通过强分类器的比例为minHitRate
    	// 因此阈值应该取从小到大排序数组中的(1.0F - minHitRate)处的值
        int thresholdIdx = (int)((1.0F - minHitRate) * numPos);
        threshold = eval[ thresholdIdx ];
        numPosTrue = numPos - thresholdIdx;
        for( int i = thresholdIdx - 1; i >= 0; i--)
            if ( abs( eval[i] - threshold) < FLT_EPSILON )
                numPosTrue++;
        float hitRate = ((float) numPosTrue) / ((float) numPos);
    	// 确定强分类器的阈值threshold之后,还需要计算虚警率,也就是负样本通过该
    	// 阈值的比例。同样的调用predict
        for( int i = 0; i < sCount; i++ )
        {
            if( ((CvCascadeBoostTrainData*)data)->featureEvaluator->getCls( i ) == 0.0F )
            {
                numNeg++;
    			// 返回1表示通过,也就是弱分类器和大于设置阈值,此处表示负样本通过强分类器
                if( predict( i ) )
                    numFalse++;
            }
        }
    	// 虚警率 = 通过的负样本/总负样本数
        float falseAlarm = ((float) numFalse) / ((float) numNeg);
    
    
        cout << "|"; cout.width(4); cout << right << weak->total;
        cout << "|"; cout.width(9); cout << right << hitRate;
        cout << "|"; cout.width(9); cout << right << falseAlarm;
        cout << "|" << endl;
        cout << "+----+---------+---------+" << endl;
    
    
        return falseAlarm <= maxFalseAlarm;
    }

            来看一下强分类器是如何预测的,在predict函数中,依次调用每个弱分类器的predict来给出第当前样本的输出,统计输出值sum。如果设置返回是否通过,则将sum与强分类器阈值threshold比较的结果。否则直接返回sum值。
    float CvCascadeBoost::predict( int sampleIdx, bool returnSum ) const
    {
        CV_Assert( weak );
        double sum = 0;
        CvSeqReader reader;
        cvStartReadSeq( weak, &reader );
        cvSetSeqReaderPos( &reader, 0 );
    	// 遍历当前所有的弱分类器
        for( int i = 0; i < weak->total; i++ )
        {
            CvBoostTree* wtree;
            CV_READ_SEQ_ELEM( wtree, reader );
    		// 累加第i个弱分类器的输出
            sum += ((CvCascadeBoostTree*)wtree)->predict(sampleIdx)->value;
        }
    	// 如果设置不返回和sum,返回sum是否通过强分类器的阈值threshold
        if( !returnSum )
            sum = sum < threshold - CV_THRESHOLD_EPS ? 0.0 : 1.0;
        return (float)sum;
    }

            最后我们来看上面的弱分类器是如何预测的,predict函数中实现,已经做了注释。
    CvDTreeNode* CvCascadeBoostTree::predict( int sampleIdx ) const
    {
        CvDTreeNode* node = root;
        if( !node )
            CV_Error( CV_StsError, "The tree has not been trained yet" );
    
    
        if ( ((CvCascadeBoostTrainData*)data)->featureEvaluator->getMaxCatCount() == 0 ) // ordered
        {
    		// 我们说过一个弱分类是一个CART树,结构上是一个二叉树,一个节点最多有左右孩子两个子节点
    		// CART树上的分割节点总是有两个孩子,而叶子节点没有孩子。因此此处就是判断node为split节点
    		// 也就是要走到叶子节点才会退出,结束树的遍历
            while( node->left )
            {
                CvDTreeSplit* split = node->split;
    			// 取split节点的特征序号var_idx,计算第sampleIndex个样本的第var_idx个特征值
                float val = ((CvCascadeBoostTrainData*)data)->getVarValue( split->var_idx, sampleIdx );
    			// 与split节点的阈值ord.c比较,如果小于,转向左孩子,否则转右孩子
                node = val <= split->ord.c ? node->left : node->right;
            }
        }
        else // categorical
        {
            while( node->left )
            {
                CvDTreeSplit* split = node->split;
                int c = (int)((CvCascadeBoostTrainData*)data)->getVarValue( split->var_idx, sampleIdx );
                node = CV_DTREE_CAT_DIR(c, split->subset) < 0 ? node->left : node->right;
            }
        }
    	// 返回样本落入的叶节点
        return node;
    }


    展开全文
  • java源码包2

    千次下载 热门讨论 2013-04-20 11:28:17
    Applet钢琴模拟程序java源码 2个目标文件,提供基本的音乐编辑功能。编辑音乐软件的朋友,这款实例会对你有所帮助。 Calendar万年历 1个目标文件 EJB 模拟银行ATM流程及操作源代码 6个目标文件,EJB来模拟银行...
  • java源码包3

    千次下载 热门讨论 2013-04-20 11:30:13
    Applet钢琴模拟程序java源码 2个目标文件,提供基本的音乐编辑功能。编辑音乐软件的朋友,这款实例会对你有所帮助。 Calendar万年历 1个目标文件 EJB 模拟银行ATM流程及操作源代码 6个目标文件,EJB来模拟银行...
  • java源码包4

    千次下载 热门讨论 2013-04-20 11:31:44
    Applet钢琴模拟程序java源码 2个目标文件,提供基本的音乐编辑功能。编辑音乐软件的朋友,这款实例会对你有所帮助。 Calendar万年历 1个目标文件 EJB 模拟银行ATM流程及操作源代码 6个目标文件,EJB来模拟银行...
  • 在网上找了一个js树形脚本——dTreedTree是一个易于使用的JavaScript树形菜单控件。支持无限分级,可以在同一个页面中放置多个dTree,可以为每个节点指定不同的图标。  不打算使用特殊的控件进行树型显示,也...
  • 一、原理   决策树是一种非参数的监督学习方法,它主要用于分类和回归。决策树的目的是构造一种模型,使之能够从样本数据的特征属性中,通过学习简单的决策规则——IF THEN规则,从而预测目标变量的值。...
  • 在上一边博客《Web版RSS阅读器(一)——dom4j读取xml(opml)文件》中已经讲过如何读取rss订阅文件了。...网上找了一个js树形脚本——dTreedTree是一个易于使用的JavaScript树形 菜单控件。支持无限分级,可以在
  • 如上图源码所示: url参数指定rewriteBatchedStatements参数置为true, mysql驱动才会批量执行SQL (如:#jdbc.url=jdbc:mysql://localhost:3306/dtree?useUnicode=true&characterEncoding=utf8&...
  • 人脸检测(九)--检测器源码分析

    千次阅读 2016-05-31 17:33:26
    // load内部调用read解析XML中的内容,也可以自己创建节点然后调用Read即可,但是该函数只能读取新格式的分类器,不能读取旧格式的分类器    // 多尺度检测函数  CV_WRAP virtual void ...

空空如也

空空如也

1 2 3 4 5
收藏数 99
精华内容 39
关键字:

dtree源码解析