八叉树算法是用来提取图像中主要颜色的算法,可用于图像压缩,提取颜色板(比如生成油画)
八叉树算法有个缺点,就是颜色数设定的不固定,比如你设定40个颜色,可能算出来的是36色,38色,40色。原因是因为合并的时候一次合并了8个子节点,其中可能有5个子叶节点,可能有3个子叶节点,反正就是数量不固定。
本次改进可以设定严格的颜色数,比如设定15个颜色,就是15个,不会多一个也不会少一个,而且可以设定8以内的颜色数 5个、3个都可以(可用于刻绘),这是原来的八叉树算法不具备的能力。下面先介绍一下八叉树原本的算法。
八叉树算法的步骤
1,定义节点
public class TreeNode
{
public int Counter = 0;
public int Rcount = 0;
public int Gcount = 0;
public int Bcount = 0;
public int State = 0; //0正常 1叶子节点
public TreeNode[] ChildrenNodes = null;
public string ListTag = "";
public TreeNode Farthernode = null;
}
2、循环遍历图像中的每一个像素。
3、计算像素对应的节点号。计算方法:将RGB值用二进制方式表示,然后从第0~7位分别提取RGB中的一个bit重组数字。
比如 RGB 0xFE, 0x77,0x4A,对应的二进制序列就是:
11111110
01110111
01001010
从0~7为分别取一个bit就是 100,111,110,110,101,110,111,010那么它的节点号就是4,7,6,6,5,6,7,2。对应8叉树中每一层的节点号的索引。
4、从8差数根节点开始搜索节点,接上面的例子第一层是4那么就选择第一层的第四个节点,如果改节点不存在,那么创建该节点,继续下一层。如果该节点已存在,判断该节点是否是子叶节点,如果是子叶节点,子叶节点的count加1,RGB值分别加上当前像素的RGB值(最后求平均使用)。如果已经是第8层,则该节点计入子叶节点。
5、判断子叶节点列表的个数,如果超过要求的颜色数,那么找到最深层的count最小的子叶节点合并。合并规则是,将子叶节点及其兄弟节点的count r g b值求和加到它们的父节点中,将父节点标记为子叶节点并加入子叶节点列表。
最后留下来的子叶节点列表即为选定的子叶节点,求它们的rgb平均值即可得到色板。
C#实现代码如下:
//选颜色 八叉树法
List<Color> listColor = new List<Color>();
listAllNodes.Clear();
ListEndNodes.Clear();
TreeNode root = new TreeNode();
for (int rd = 0; rd < ArrayIndexs.Length; rd++)
{
int x = rd % pixWidth;
int y = rd / pixWidth;
List<int> indexs = new List<int>();
int r = bytes[y * bitmapData.Stride + x * channel + 2];
int g = bytes[y * bitmapData.Stride + x * channel + 1];
int b = bytes[y * bitmapData.Stride + x * channel];
for (int k = 7; k >= 0; k--)
{
indexs.Add(((r & 1 << k) > 0 ? 4 : 0) + ((g & 1 << k) > 0 ? 2 : 0) + ((b & 1 << k) > 0 ? 1 : 0));
}
TreeNode currentNode = root;
for (int k = 0; k < colorDeep; k++)
{
if (currentNode.ChildrenNodes == null)//正常节点没有子节点的情况
{
currentNode.ChildrenNodes = new TreeNode[8];
while (listAllNodes.Count <= k)
{
listAllNodes.Add(new List<TreeNode>());
}
}
if (currentNode.ChildrenNodes[indexs[k]] == null)
{
currentNode.ChildrenNodes[indexs[k]] = new TreeNode();
currentNode.ChildrenNodes[indexs[k]].Farthernode = currentNode;
currentNode.ChildrenNodes[indexs[k]].ListTag = currentNode.ListTag + indexs[k].ToString();
if (k == colorDeep - 1)
{
currentNode.ChildrenNodes[indexs[k]].State = 1;
}
listAllNodes[k].Add(currentNode.ChildrenNodes[indexs[k]]);
}
currentNode = currentNode.ChildrenNodes[indexs[k]];
if (currentNode.State == 1)//当前是叶子节点
{
currentNode.Counter += 1;
currentNode.Rcount += r;
currentNode.Gcount += g;
currentNode.Bcount += b;
if (!ListEndNodes.Contains(currentNode))
{
ListEndNodes.Add(currentNode);
}
break;
}
if (currentNode.State == 3)//已经删除的叶子结点
{
currentNode = currentNode.Farthernode;
currentNode.Counter += 1;
currentNode.Rcount += r;
currentNode.Gcount += g;
currentNode.Bcount += b;
if (!ListEndNodes.Contains(currentNode))
{
ListEndNodes.Add(currentNode);
}
break;
}
}
CheckAndRemoveChild(colorCount);
}
listAllNodes.Clear();
ListEndNodes.Sort((x, y) => -x.Counter.CompareTo(y.Counter));
其中 CheckAndRemoveChild 方法 就是用来合并子叶节点。本章中使用的合并方法不是一次合并8个子节点,而是一次合并一个,父节点标记为(半子叶节点),如果合并后,父节点没有子叶节点了,那么该父节点才会被标记为真正的子叶节点。半子叶节点在遍历的时候,如果下一个子叶节点是已经合并删除的子叶节点,那么count和rgb值加入到该半子叶节点上,上面的代码中有体现。
private static void CheckAndRemoveChild(int colorCount)
{
while (ListEndNodes.Count > colorCount)
{
int maxDeep = 0;
for (int i = 0; i < listAllNodes.Count; i++)
{
if (listAllNodes[i].Count != 0)
{
if (maxDeep < i)
{
maxDeep = i;
}
}
}
TreeNode child = null;
foreach (TreeNode node in listAllNodes[maxDeep])
{
if (child == null)
{
child = node;
}
if (child.Counter > node.Counter)
{
child = node;
}
}
TreeNode combineNode = child.Farthernode;
combineNode.State = 2;//半子叶节点
if (!ListEndNodes.Contains(combineNode))
{
ListEndNodes.Add(combineNode);
}
foreach (List<TreeNode> list in listAllNodes)
{
if (list.Contains(child))
{
list.Remove(child);
}
}
ListEndNodes.Remove(child);
combineNode.Counter += child.Counter;
combineNode.Rcount += child.Rcount;
combineNode.Gcount += child.Gcount;
combineNode.Bcount += child.Bcount;
child.State = 3;
bool has = false;
foreach (TreeNode node in combineNode.ChildrenNodes)
{
if (node != null && node.State != 3)
{
has = true;
break;
}
}
if (!has)
{
//如果子叶节点全部删除
combineNode.ChildrenNodes = null;
combineNode.State = 1;
}
}
}
交流加306128847.