哈希表初始化:
struct uint64_zarray_entry **clustermap = calloc(nclustermap, sizeof(struct uint64_zarray_entry*));
clustermap 是一个二维指针。哈希表可以理解为数组+链表,这里的数组大小便是nclustermap,每个数组存储一个链表的起始地址,链表中的每个元素类型是uint64_zarray_entry*。
struct uint64_zarray_entry
{
uint64_t id; // 待存储的边界点ID
zarray_t *cluster;// 该像素点的聚类信息
struct uint64_zarray_entry *next;// 下一个链表节点地址
};
然后对图像遍历每行每列,分别检索对比当前点与"下方、右侧、右上方、右下方"的像素值梯度,按梯度进行聚类并进行哈希存储,具体步骤见下:
if (v0 + v1 == 255) {
if (unionfind_get_set_size(uf, rep1) > 24) {
轮廓检验时的条件是:1)两个邻接像素值相加为255;2)邻接像素所处的连接区域(由联合查找算法确定)像素点数量大于24。
然后计算待存储的哈希表键值:clusterid,也是聚类ID,满足上述条件的邻接像素点ID决定。
uint64_t clusterid; \
/*将两个点的ID存储于64位clusterid量中,大的位于前面*/ \
if (rep0 < rep1) \
clusterid = (rep1 << 32) + rep0; \
else \
clusterid = (rep0 << 32) + rep1; \
然后,基于该哈希表的键值,也是聚类点ID,计算数组索引:
uint32_t clustermap_bucket = u64hash_2(clusterid) % nclustermap; /*哈希表的数组的编号*/
clustermap_bucket 便为当前哈希表的一维索引位置。每个像素点聚类后存储的位置由clustermap_bucket 决定,也就是取模后值相等那些像素点都会存储在一个链表里,该链表的起始地址为索引号为clustermap_bucket的数组。clustermap[clustermap_bucket] 。
新建一个哈希表入口变量entry,新建链表节点地址便是对应桶/数组中上一个链表节点的地址:
struct uint64_zarray_entry *entry = clustermap[clustermap_bucket];/*新建链表节点地址便是上一个对应桶中链表节点的地址*/
每次开始前会判断当前的边界聚类ID是不是和当前对应数组/桶里存储的链表节点里存储的ID是否一致,也就是检索当前的ID值以前是否存储过,在程序里会逐个和之前的clusterid对比,若不等于,则entry继续指向该数组下的上一个链表地址,直到找到对应的聚类ID(此时entry指向上一个节点,下面会进行地址递增)或者entry指向空(表示找完了都没有),此时才会进行下一步:
while (entry && entry->id != clusterid) { \
entry = entry->next;
}
最后,对于每个不为空的链表节点,首先更新当前的链表节点地址,也就是叠加链表元素大小的地址偏移:
if (mem_pool_loc == mem_chunk_size) { \
mem_pool_loc = 0; \
mem_pool_idx++; \
mem_pools[mem_pool_idx] = calloc(mem_chunk_size, sizeof(struct uint64_zarray_entry)); \
} \
/*链表节点地址递增*/ \
entry = mem_pools[mem_pool_idx] + mem_pool_loc; \
mem_pool_loc++;
将当前的“聚类ID,聚类点信息,当前链表节点的起始地址(数组索引)”添加到当前链表节点中:
entry->id = clusterid; /*将边界聚类id赋值,也就是哈希表的键值*/ \
entry->cluster = zarray_create(sizeof(struct pt));/*当前聚类信息*/ \
entry->next = clustermap[clustermap_bucket]; /*哈希表的下一个存储桶或者数组*/ \
然后,哈希表的当前索引位置数组clustermap[clustermap_bucket] 指向entry:
clustermap[clustermap_bucket] = entry; /*当前哈希表桶的入口地址存储当前聚类的地址*/
同时,不管当前的链表节点是否为空,都更新当前的聚类像素点坐标和梯度值,坐标按照2倍关系存储,梯度按照邻接像素的位置存储:
struct pt p = { .x = 2*x + dx, .y = 2*y + dy, .gx = dx*((int) v1-v0), .gy = dy*((int) v1-v0)}; \
zarray_add(entry->cluster, &p); \
此时的entry和clustermap[clustermap_bucket]指向的是同一个地址。所以增加的像素点聚类信息也就是增加到了clustermap[clustermap_bucket]指向的链表节点中。
最后运行结束后,将clustermap存储的信息,按顺序存储在clusters中,并返回。clusters便是聚类后的结果,并完全按照哈希表的方式存储。
for (int i = 0; i < nclustermap; i++) {// 按桶遍历哈希表,也就是遍历每个聚类
int start = zarray_size(clusters);
// 遍历哈希表中每个桶(每个聚类)里的聚类点信息,并存储进clusters
for (struct uint64_zarray_entry *entry = clustermap[i]; entry; entry = entry->next) {
struct cluster_hash* cluster_hash = malloc(sizeof(struct cluster_hash));
cluster_hash->hash = u64hash_2(entry->id) % nclustermap;// 哈希表赋值
cluster_hash->id = entry->id;
cluster_hash->data = entry->cluster;
zarray_add(clusters, &cluster_hash);
}
int end = zarray_size(clusters);
// Do a quick bubblesort on the secondary key.
// 对每个桶里的ID按照冒泡排序
int n = end - start;
for (int j = 0; j < n - 1; j++) {
for (int k = 0; k < n - j - 1; k++) {
struct cluster_hash* hash1;
struct cluster_hash* hash2;
zarray_get(clusters, start + k, &hash1);// 将聚类里第 start + k个检索值赋值给hash1
zarray_get(clusters, start + k + 1, &hash2);
if (hash1->id > hash2->id) { // 若ID大于
struct cluster_hash tmp = *hash2; // 将两个哈希数据位置交换
*hash2 = *hash1;
*hash1 = tmp;
}
}
}
}
// 释放内存
for (int i = 0; i <= mem_pool_idx; i++) {
free(mem_pools[i]);
}
free(clustermap);
// 返回聚类结果
return clusters;