|
1681 | 1681 | */ |
1682 | 1682 | tracking.ColorTracker.knownColors_ = {}; |
1683 | 1683 |
|
| 1684 | + /** |
| 1685 | + * Caches coordinates values of the neighbours surrounding a pixel. |
| 1686 | + * @type {Object.<number, Int32Array>} |
| 1687 | + * @private |
| 1688 | + * @static |
| 1689 | + */ |
| 1690 | + tracking.ColorTracker.neighbours_ = {}; |
| 1691 | + |
1684 | 1692 | /** |
1685 | 1693 | * Registers a color as known color. |
1686 | 1694 | * @param {string} name The color name. |
|
1720 | 1728 | * the blog extracted from the cloud points. |
1721 | 1729 | * @private |
1722 | 1730 | */ |
1723 | | - tracking.ColorTracker.prototype.calculateCentralCoordinate_ = function(cloud, total) { |
1724 | | - var dx = 0; |
1725 | | - var dy = 0; |
| 1731 | + tracking.ColorTracker.prototype.calculateDimensions_ = function(cloud, total) { |
1726 | 1732 | var maxx = -1; |
1727 | 1733 | var maxy = -1; |
1728 | 1734 | var minx = Infinity; |
1729 | 1735 | var miny = Infinity; |
1730 | | - var totalInliers = 0; |
1731 | 1736 |
|
1732 | 1737 | for (var c = 0; c < total; c += 2) { |
1733 | 1738 | var x = cloud[c]; |
1734 | 1739 | var y = cloud[c + 1]; |
1735 | 1740 |
|
1736 | | - if (x > -1 && y > -1) { |
1737 | | - dx += x; |
1738 | | - dy += y; |
1739 | | - totalInliers++; |
1740 | | - |
1741 | | - if (x < minx) { |
1742 | | - minx = x; |
1743 | | - } |
1744 | | - if (x > maxx) { |
1745 | | - maxx = x; |
1746 | | - } |
1747 | | - if (y < miny) { |
1748 | | - miny = y; |
1749 | | - } |
1750 | | - if (y > maxy) { |
1751 | | - maxy = y; |
1752 | | - } |
| 1741 | + if (x < minx) { |
| 1742 | + minx = x; |
| 1743 | + } |
| 1744 | + if (x > maxx) { |
| 1745 | + maxx = x; |
| 1746 | + } |
| 1747 | + if (y < miny) { |
| 1748 | + miny = y; |
| 1749 | + } |
| 1750 | + if (y > maxy) { |
| 1751 | + maxy = y; |
1753 | 1752 | } |
1754 | 1753 | } |
1755 | | - if (totalInliers === 0) { |
1756 | | - return null; |
1757 | | - } |
| 1754 | + |
1758 | 1755 | return { |
1759 | | - x: dx / totalInliers, |
1760 | | - y: dy / totalInliers, |
1761 | | - z: 60 - ((maxx - minx) + (maxy - miny)) / 2 |
| 1756 | + maxx: maxx, |
| 1757 | + maxy: maxy, |
| 1758 | + minx: minx, |
| 1759 | + miny: miny |
1762 | 1760 | }; |
1763 | 1761 | }; |
1764 | 1762 |
|
1765 | 1763 | /** |
1766 | | - * Flags the cloud points with -1 when the pixel of the desired color is far |
1767 | | - * by `tracking.ColorTracker.MIN_PIXELS` of the area with a bigger density |
1768 | | - * of pixels of the desired color. This helps to reduce outliers from the |
1769 | | - * tracking. |
1770 | | - * @param {Array.<number>} cloud Major row order array containing all the |
1771 | | - * points from the desired color, e.g. [x1, y1, c2, y2, ...]. |
1772 | | - * @param {number} total Total numbers of pixels of the desired color. |
1773 | | - * @private |
| 1764 | + * Gets the colors being tracked by the `ColorTracker` instance. |
| 1765 | + * @return {Array.<string>} |
1774 | 1766 | */ |
1775 | | - tracking.ColorTracker.prototype.flagOutliers_ = function(cloud, total) { |
1776 | | - for (var m = 0; m < total; m += 2) { |
1777 | | - var dist = 0; |
1778 | | - for (var n = 2; n < total; n += 2) { |
1779 | | - dist += tracking.Math.distance(cloud[m], cloud[m + 1], cloud[n], cloud[n + 1]); |
1780 | | - } |
1781 | | - if (dist/total >= tracking.ColorTracker.MIN_PIXELS) { |
1782 | | - cloud[m] = -1; |
1783 | | - cloud[m + 1] = -1; |
1784 | | - total[m]--; |
1785 | | - } |
| 1767 | + tracking.ColorTracker.prototype.getColors = function() { |
| 1768 | + return this.colors; |
| 1769 | + }; |
| 1770 | + |
| 1771 | + /** |
| 1772 | + * Gets the eight offset values of the neighbours surrounding a pixel. |
| 1773 | + * @param {number} width The image width |
| 1774 | + * @return {array} Array with the eight offset values of the neighbours |
| 1775 | + * surrounding a pixel. |
| 1776 | + */ |
| 1777 | + tracking.ColorTracker.prototype.getNeighboursForWidth_ = function(width) { |
| 1778 | + if (tracking.ColorTracker.neighbours_[width]) { |
| 1779 | + return tracking.ColorTracker.neighbours_[width]; |
1786 | 1780 | } |
| 1781 | + |
| 1782 | + var neighbours = new Int32Array(8); |
| 1783 | + |
| 1784 | + neighbours[0] = -width * 4; |
| 1785 | + neighbours[1] = -width * 4 + 4; |
| 1786 | + neighbours[2] = 4; |
| 1787 | + neighbours[3] = width * 4 + 4; |
| 1788 | + neighbours[4] = width * 4; |
| 1789 | + neighbours[5] = width * 4 - 4; |
| 1790 | + neighbours[6] = -4; |
| 1791 | + neighbours[7] = -width * 4 - 4; |
| 1792 | + |
| 1793 | + tracking.ColorTracker.neighbours_ = neighbours; |
| 1794 | + |
| 1795 | + return neighbours; |
1787 | 1796 | }; |
1788 | 1797 |
|
1789 | 1798 | /** |
1790 | | - * Gets the colors being tracked by the `ColorTracker` instance. |
1791 | | - * @return {Array.<string>} |
| 1799 | + * Unites groups whose bounding box intersect with each other. |
| 1800 | + * @param {Array.<Object>} results |
1792 | 1801 | */ |
1793 | | - tracking.ColorTracker.prototype.getColors = function() { |
1794 | | - return this.colors; |
| 1802 | + tracking.ColorTracker.prototype.regroupResults_ = function(results) { |
| 1803 | + var newResults = []; |
| 1804 | + var going; |
| 1805 | + |
| 1806 | + for (var r = 0; r < results.length; r++) { |
| 1807 | + going = true; |
| 1808 | + |
| 1809 | + for (var s = r + 1; s < results.length; s++) { |
| 1810 | + if ((results[r].minx >= results[s].minx && results[r].minx <= results[s].maxx) || |
| 1811 | + (results[r].maxx >= results[s].minx && results[r].maxx <= results[s].maxx)) { |
| 1812 | + if ((results[r].miny >= results[s].miny && results[r].miny <= results[s].maxy) || |
| 1813 | + (results[r].maxy >= results[s].miny && results[r].maxy <= results[s].maxy)) { |
| 1814 | + |
| 1815 | + going = false; |
| 1816 | + |
| 1817 | + results[s].minx = Math.min(results[r].minx, results[s].minx); |
| 1818 | + results[s].maxx = Math.max(results[r].maxx, results[s].maxx); |
| 1819 | + results[s].miny = Math.min(results[r].miny, results[s].miny); |
| 1820 | + results[s].maxy = Math.max(results[r].maxy, results[s].maxy); |
| 1821 | + results[s].z = 60 - ((results[s].maxx - results[s].minx) + (results[s].maxy - results[s].miny)) / 2; |
| 1822 | + |
| 1823 | + break; |
| 1824 | + } |
| 1825 | + } |
| 1826 | + } |
| 1827 | + |
| 1828 | + if (going) { |
| 1829 | + newResults.push({ |
| 1830 | + color: results[r].color, |
| 1831 | + height: results[r].maxy - results[r].miny, |
| 1832 | + width: results[r].maxx - results[r].minx, |
| 1833 | + x: results[r].minx, |
| 1834 | + y: results[r].miny |
| 1835 | + }); |
| 1836 | + } |
| 1837 | + } |
| 1838 | + |
| 1839 | + return newResults; |
1795 | 1840 | }; |
1796 | 1841 |
|
1797 | 1842 | /** |
|
1810 | 1855 | * @param {number} height The pixels canvas height. |
1811 | 1856 | */ |
1812 | 1857 | tracking.ColorTracker.prototype.track = function(pixels, width, height) { |
1813 | | - var instance = this, |
1814 | | - color, |
1815 | | - colorFn, |
1816 | | - colorIndex, |
1817 | | - colors = this.getColors(), |
1818 | | - cloud = [], |
1819 | | - payload = [], |
1820 | | - total = []; |
1821 | | - |
1822 | | - tracking.Matrix.forEach(pixels, width, height, function(r, g, b, a, w, i, j) { |
1823 | | - for (colorIndex = -1; color = colors[++colorIndex];) { |
1824 | | - if (!cloud[colorIndex]) { |
1825 | | - total[colorIndex] = 0; |
1826 | | - cloud[colorIndex] = []; |
1827 | | - } |
1828 | | - |
1829 | | - colorFn = tracking.ColorTracker.knownColors_[color]; |
1830 | | - |
1831 | | - if (colorFn && colorFn.call(instance, r, g, b, a, w, i, j)) { |
1832 | | - total[colorIndex] += 2; |
1833 | | - cloud[colorIndex].push(j, i); |
1834 | | - } |
1835 | | - } |
1836 | | - }); |
| 1858 | + var colors = this.getColors(); |
| 1859 | + var payload = []; |
1837 | 1860 |
|
1838 | | - for (colorIndex = -1; color = colors[++colorIndex];) { |
1839 | | - if (total[colorIndex] < tracking.ColorTracker.MIN_PIXELS) { |
1840 | | - continue; |
1841 | | - } |
1842 | | - |
1843 | | - instance.flagOutliers_(cloud[colorIndex], total[colorIndex]); |
1844 | | - |
1845 | | - var data = instance.calculateCentralCoordinate_(cloud[colorIndex], total[colorIndex]); |
1846 | | - if (data) { |
1847 | | - data.color = colors[colorIndex]; |
1848 | | - data.pixels = cloud[colorIndex]; |
1849 | | - payload.push(data); |
1850 | | - } |
| 1861 | + for (var colorIndex = 0; colorIndex < colors.length; colorIndex++) { |
| 1862 | + payload.push(this.trackColor_(pixels, width, height, colors[colorIndex])); |
1851 | 1863 | } |
1852 | 1864 |
|
1853 | 1865 | if (payload.length) { |
1854 | | - if (instance.onFound) { |
1855 | | - instance.onFound.call(instance, payload); |
| 1866 | + if (this.onFound) { |
| 1867 | + this.onFound.call(this, payload); |
1856 | 1868 | } |
1857 | 1869 | } |
1858 | 1870 | else { |
1859 | | - if (instance.onNotFound) { |
1860 | | - instance.onNotFound.call(instance, payload); |
| 1871 | + if (this.onNotFound) { |
| 1872 | + this.onNotFound.call(this, payload); |
1861 | 1873 | } |
1862 | 1874 | } |
1863 | 1875 | }; |
1864 | 1876 |
|
| 1877 | + /** |
| 1878 | + * Find the given color in the given matrix of pixels. |
| 1879 | + * @param {Uint8ClampedArray} pixels The pixels data to track. |
| 1880 | + * @param {number} width The pixels canvas width. |
| 1881 | + * @param {number} height The pixels canvas height. |
| 1882 | + * @param {string} color The color to be found |
| 1883 | + */ |
| 1884 | + tracking.ColorTracker.prototype.trackColor_ = function(pixels, width, height, color) { |
| 1885 | + var colorFn = tracking.ColorTracker.knownColors_[color]; |
| 1886 | + var currGroup = new Int32Array(pixels.length >> 2); |
| 1887 | + var currGroupSize; |
| 1888 | + var currI; |
| 1889 | + var currJ; |
| 1890 | + var currW; |
| 1891 | + var marked = new Int8Array(pixels.length); |
| 1892 | + var minPixels = tracking.ColorTracker.MIN_PIXELS; |
| 1893 | + var neighboursW = this.getNeighboursForWidth_(width); |
| 1894 | + var queue = new Int32Array(pixels.length); |
| 1895 | + var queuePosition; |
| 1896 | + var results = []; |
| 1897 | + var w = -4; |
| 1898 | + |
| 1899 | + if (!colorFn) { |
| 1900 | + return results; |
| 1901 | + } |
| 1902 | + |
| 1903 | + for (var i = 0; i < height; i++) { |
| 1904 | + for (var j = 0; j < width; j++) { |
| 1905 | + w += 4; |
| 1906 | + |
| 1907 | + if (marked[w]) { |
| 1908 | + continue; |
| 1909 | + } |
| 1910 | + |
| 1911 | + currGroupSize = 0; |
| 1912 | + |
| 1913 | + queuePosition = -1; |
| 1914 | + queue[++queuePosition] = w; |
| 1915 | + queue[++queuePosition] = i; |
| 1916 | + queue[++queuePosition] = j; |
| 1917 | + |
| 1918 | + marked[w] = 1; |
| 1919 | + |
| 1920 | + while (queuePosition >= 0) { |
| 1921 | + currJ = queue[queuePosition--]; |
| 1922 | + currI = queue[queuePosition--]; |
| 1923 | + currW = queue[queuePosition--]; |
| 1924 | + |
| 1925 | + if (colorFn.call(this, pixels[currW], pixels[currW + 1], pixels[currW + 2], pixels[currW + 3], currW, currI, currJ)) { |
| 1926 | + currGroup[currGroupSize++] = currJ; |
| 1927 | + currGroup[currGroupSize++] = currI; |
| 1928 | + |
| 1929 | + for (var k = 0; k < neighboursW.length; k++) { |
| 1930 | + var otherW = currW + neighboursW[k]; |
| 1931 | + var otherI = currI + neighboursI[k]; |
| 1932 | + var otherJ = currJ + neighboursJ[k]; |
| 1933 | + if (!marked[otherW] && otherI >= 0 && otherI < height && otherJ >= 0 && otherJ < width) { |
| 1934 | + queue[++queuePosition] = otherW; |
| 1935 | + queue[++queuePosition] = otherI; |
| 1936 | + queue[++queuePosition] = otherJ; |
| 1937 | + |
| 1938 | + marked[otherW] = 1; |
| 1939 | + } |
| 1940 | + } |
| 1941 | + } |
| 1942 | + } |
| 1943 | + |
| 1944 | + if (currGroupSize >= minPixels) { |
| 1945 | + var data = this.calculateDimensions_(currGroup, currGroupSize); |
| 1946 | + if (data) { |
| 1947 | + data.color = color; |
| 1948 | + results.push(data); |
| 1949 | + } |
| 1950 | + } |
| 1951 | + } |
| 1952 | + } |
| 1953 | + |
| 1954 | + return this.regroupResults_(results); |
| 1955 | + } |
| 1956 | + |
1865 | 1957 | // Default colors |
1866 | 1958 | //=================== |
1867 | 1959 |
|
|
1875 | 1967 | if ((g - r) >= thresholdGreen && (b - r) >= thresholdBlue) { |
1876 | 1968 | return true; |
1877 | 1969 | } |
1878 | | - return Math.sqrt(dx * dx + dy * dy + dz * dz) < 80; |
| 1970 | + return dx * dx + dy * dy + dz * dz < 6400; |
1879 | 1971 | }); |
1880 | 1972 |
|
1881 | 1973 | tracking.ColorTracker.registerColor('magenta', function(r, g, b) { |
|
1887 | 1979 | if ((r - g) >= threshold && (b - g) >= threshold) { |
1888 | 1980 | return true; |
1889 | 1981 | } |
1890 | | - return Math.sqrt(dx * dx + dy * dy + dz * dz) < 140; |
| 1982 | + return dx * dx + dy * dy + dz * dz < 19600; |
1891 | 1983 | }); |
1892 | 1984 |
|
1893 | 1985 | tracking.ColorTracker.registerColor('yellow', function(r, g, b) { |
|
1896 | 1988 | dy = g - 255, |
1897 | 1989 | dz = b - 0; |
1898 | 1990 |
|
1899 | | - if ((r - g) >= threshold && (b - g) >= threshold) { |
| 1991 | + if ((r - b) >= threshold && (g - b) >= threshold) { |
1900 | 1992 | return true; |
1901 | 1993 | } |
1902 | | - return Math.sqrt(dx * dx + dy * dy + dz * dz) < 100; |
| 1994 | + return dx * dx + dy * dy + dz * dz < 10000; |
1903 | 1995 | }); |
1904 | 1996 |
|
| 1997 | + |
| 1998 | + // Caching neighbour i/j offset values. |
| 1999 | + //=================== |
| 2000 | + |
| 2001 | + var neighboursI = new Int32Array(8); |
| 2002 | + var neighboursJ = new Int32Array(8); |
| 2003 | + |
| 2004 | + neighboursI[0] = -1; |
| 2005 | + neighboursI[1] = -1; |
| 2006 | + neighboursI[2] = 0; |
| 2007 | + neighboursI[3] = 1; |
| 2008 | + neighboursI[4] = 1; |
| 2009 | + neighboursI[5] = 1; |
| 2010 | + neighboursI[6] = 0; |
| 2011 | + neighboursI[7] = -1; |
| 2012 | + |
| 2013 | + neighboursJ[0] = 0; |
| 2014 | + neighboursJ[1] = 1; |
| 2015 | + neighboursJ[2] = 1; |
| 2016 | + neighboursJ[3] = 1; |
| 2017 | + neighboursJ[4] = 0; |
| 2018 | + neighboursJ[5] = -1; |
| 2019 | + neighboursJ[6] = -1; |
| 2020 | + neighboursJ[7] = -1; |
1905 | 2021 | }()); |
1906 | 2022 |
|
1907 | 2023 | (function() { |
|
0 commit comments