初衷
最近有个图像处理模块,流程简单,实现冗杂;在同事指导下,开始接触多线程处理。
先前有做过线程,也稍微接触GPU处理,但是这种整齐划一的多线程处理,还是最近才拉开帷幕,并且:一发不可收。
笔者接触的项目对实时性要求很高,同时,处理图像尺寸比较大,怎么说呢,
尺寸(640*22)*512,就是拼接全景图尺寸大概级别。
这种大数据量的处理,即使简单的访问像素,都需要7208960次,虽然计算机具有良好的高频处理特性,但是倘或能够将处理时间打折,那应该是件很了不起的事情。
处理案例
最近接触一个算法,名为:基于时空结构张量的运动检测,具体原理和使用场景自行百度。
主要处理流程包括:
- 1、获取原始灰度图的空间梯度,即水平方向和垂直方向梯度图;
- 2、获取时间梯度图,即帧差图;
- 3、三张图对应像素计算,涉及相乘、平方、相加,在原文中,为求矩阵的迹;
- 4、邻域卷积,获得小范围内的迹,通过和阈值比较,从而判定为运动前景还是稳定背景,实现孤立区域、运动区域的分割。
思路
在笔者接触的图像中,宽度远大于高度,因此在做图像拆分小的单元块时,选择保持高度不变,而在宽度方向拆分;由于并不清楚以后运行是四核还是八核,因此统一开启四个线程。
所以基本思路如下:
- 1、确定每个线程块处理的列项起始和终止坐标点;
- 2、编写核心处理部分,处理部分除了ID,差异不会太大;
- 3、开启线程,调用线程处理模块。
实现代码
这里分享一部分代码:
线程处理核心部分如下,该部分主要做卷积:
/**
* @brief 时空结构张量的计算写进线程模块
* @brief 部分参数写进类成员,在这边就不做分享具体含义了
* @param *idThread - 入参,线程处理的id,据此计算当前索引起始点
* @param *radius - 入参,邻域半径
* @return
*/
int CTENSOR::tensorImageThread(int idThread,int radius,int thresh)
{
int indexStart = idThread*m_iWidthThread;
int jStart = 0;
int jEnd = 0;
if (idThread == 0)
{
jStart = indexStart + radius;
jEnd = indexStart + m_iWidthThread;
}
else if (idThread == 3)
{
jStart = indexStart;
jEnd = indexStart + m_iWidthThread - radius;
}
else
{
jStart = indexStart;
jEnd = indexStart + m_iWidthThread;
}
int indexRow = 0;
int indexCol = 0;
for (int i = radius; i < m_iRow - radius; i++)
{
for (int j = jStart; j < jEnd; j++)
{
float kernelSum = 0;
for (int kernelRow = -radius; kernelRow < radius; kernelRow++)
{
for (int kernelCol = -radius; kernelCol < radius; kernelCol++)
{
indexRow = i + kernelRow;
indexCol = j + kernelCol;
kernelSum = kernelSum + m_mulSumImage.at<float>(indexRow, indexCol);
}
}
if (kernelSum >= thresh)
{
tensoredImage.at<uchar>(i, j) = 255;
}
}
}
return 0;
}
在以上代码中,特别愿意分享的是,
由于卷积操作,在边界处索引和内部空间会有差别;稍不留神就会引发内存访问越界的风险;同时考虑空间连续,也不能做内部数据的舍弃
所以可以参考我所采用的,提前设置列向开始和结束的id。
线程调用部分如下:
auto F1 = std::async(&CTENSOR::tensorImageThread, this, 0, 3, thresh);
auto F2 = std::async(&CTENSOR::tensorImageThread, this, 1, 3, thresh);
auto F3 = std::async(&CTENSOR::tensorImageThread, this, 2, 3, thresh);
auto F4 = std::async(&CTENSOR::tensorImageThread, this, 3, 3, thresh);
auto result1 = F1.get();
auto result2 = F2.get();
auto result3 = F3.get();
auto result4 = F4.get();
至于处理速度,怎么说呢?
没加线程之前0.6秒;开启该部分线程后,0.2秒。
当然其他部分也做了些许优化;
但就图像处理这个小模块而言,速度理论提升四倍。
路漫漫其修远矣~