项目中用到相机后端处理,走了一些弯路,也遇到不少问题(解决了不少问题),特意写下本文记录下当时点点滴滴。
讲一下背景,公司自研相机,用于一些高端场合,因此对后端处理也非常讲究
采集-显示 基本方案:
- 前端模拟信号(BNC)
- PCIE采集图像
- 应用层通过PCIE采集卡(驱动)获取YUV图像
- YUV转码BGR
- BGR进行OSD叠加
- BGR显示到UI
方案很简单,也很常规一种解决办法,但仍然碰到许多问题
1. 首先采集端
最初方案是USB采集卡,网购号称USB3.0采集卡,实测发现根本达不到3.0,后来拆开来看,外面接口是USB3.0,但里面转接是2.0,因此做不到。
后来采用采集到HDMI 然后再HDMI - USB3.0采集卡,虽然可以达到,但延时、占用CPU资源大的问题很突出。
又考虑到主板是ARM方案,因此,还是PCIE的方式比较靠谱(成本高),多路也方便
为什么非要USB3.0?1080p*25的带宽,2.0明显不够(只能跑10帧)
2. YUV-转BGR编码
转码很好理解,但是考虑到性能,绝对不可以用CPU,
最佳办法使用RGA(rk的解决办法),也占资源,但好在还可以接受
由于1080不是16倍数(一开始不知道这个原因),因此也搞了好久
测试640、720都没问题,1080就不行,以为是性能问题
而且,官方虽然有demo,但可复杂了,如果仅仅是rga转码,只要一点点代码就够了
int RgaConvert::YuvToRgb(uint8_t *srcBuf, uint32_t w,uint32_t h, uint8_t *dstBuf)
{
int srcFormat = RK_FORMAT_YUYV_422;
int dstFormat = RK_FORMAT_BGR_888;
rga_info srcInfo,dstInfo;
memset(&srcInfo, 0, sizeof(rga_info_t));
srcInfo.fd = -1;
srcInfo.mmuFlag = 1;
memset(&dstInfo, 0, sizeof(rga_info_t));
dstInfo.fd = -1;
dstInfo.mmuFlag = 1;
rga_set_rect(&srcInfo.rect,0,0,w,h,w,h,srcFormat);
rga_set_rect(&dstInfo.rect,0,0,w,h,w,h,dstFormat);
//图像翻转也可以这里做,但会增加CPU
srcInfo.virAddr = (void *)srcBuf;
dstInfo.virAddr = (void *)dstBuf;
mRkRga.RkRgaBlit(&srcInfo, &dstInfo, NULL);
return 0;
}
3. OSD叠加
OSD叠加也是个大学问,简单叠加当然简单,但是但是,
考虑到图像背景,叠加颜色怎么处理也是个麻烦地方
首先,你得有图像数据传进来
其次,然后要计算图像数据的灰度(总不能取中心点,也不能去全部点,然后每帧都要计算吗?显然每秒计算一次也足够了),
。。。有了背景的灰度,此时,还要考虑临界点(比如128以上是白色,如果实际刚好是127-129来回切换,那不是每次都得切换颜色?这样效果也不好:肉眼看起来没有变化的,实际却在切换颜色);还有如果字符很长,那么背景灰度也很讨厌(一个一个字符去计算?还是,显然一个个字符去计算比较合理点)
最后,是否可以考虑描边?可以,但很丑(也非常占资源),且logo图标方式也很难支持描边
这里涉及到很多参数,算法,需要慢慢去优化,调整,也很辛酸
/*计算灰度*//*斜线方向*/
uint32_t Osd::CalculateGray(const uint8_t *rgb,const QRect &rect,int base,bool w2b)
{
uint32_t countGray = 0;
/*y轴检测16像素*/
int yStep = (rect.height() > 16) ? (rect.height() / 16) : 1;
int xStep = (rect.width() > 16) ? (rect.width() / 16) : 1;
int32_t tY = rect.y();
for(int32_t j = 0; j < 16; j++)
{
int32_t start = (tY * VIDEO_WIDTH + rect.x() + j * xStep) * 3;
uint8_t gray = (uint8_t)((19595 * rgb[start] + 38469 * rgb[start+1] + 7472 * rgb[start+2]) >> 16);
if(w2b)
{
countGray += ((gray > base) ? 1 : 0);
}
else
{
countGray += ((gray < base) ? 1 : 0);
}
tY += yStep;
}
//countGray = countGray >> 8;
return countGray;
}
4. 内存交互问题
采集端获取是YUV,是个内存,然后转码成BGR,此时又得一个内存
肯定存在内存拷贝问题
后面还有大的环节:OSD叠加、H264编码源、显示UI,这些都是需要内存的
如果每个环节各管各的(强解耦),虽然流程简单了,但每次都是拷贝,性能非常受限
因此设计一个内存自动管理机制(类似智能指针或许是个好的解决办法)
5. 显示问题
双缓冲?等等,如何快速,如何不影响其他流程,等等也是很头疼的问题