背景
本来想利用面阵相机进行传送带上超长目标(超长面阵视野)的识别,在做实验的过程中,虽然在传送带上贴靶标,也布置好待识别目标,但在传送带运动过程中采集到的视频数据却不能完成拼接,这主要的原因是传送带上靶标的固定方式。传送带是带有弹性的,贴上不带弹性的靶标后,由于相对滑动,靶标固定不牢固。如果在靶标外再贴一圈胶布,则由于传送带左右两侧弹性不一致,导致整个一圈靶标全部滑向弹性较小的一边,直到被两边的限位装置卡住。
如果在虚拟环境中采集图片,则可以认为图像是等间隔采集的,传送带运动速度也是均匀的。这样可以省去算法的第一步,图像的拼接。而这一步在使用线阵相机后就不再是问题。
仿真环境
在虚拟环境下采集的视频如下:
超长板:
单个目标超出相机视野,需要通过目标的平移运动来进行目标轮廓的恢复。
并行板:
不仅单个目标超出相机视野,而且会有多个目标并行的情况,这对算法的逻辑提出了更高的要求。
问题分析:
对于超长相机视野的板,需要进行图像的拼接。原来做过用靶标进行拼接的算法,https://blog.csdn.net/iamqianrenzhan/article/details/89913917
对于在仿真环境下获取到的视频,由于传送带运动速度固定,相机采集帧率固定门,所有不需要进行拼接,每次从当前帧中提取固定高度的区域进行拼接就可以。这个固定的高度可以认为是传送带的“像素速度”,“像素速度”这个概念在后面需要给识别到的目标打时间戳时很关键。
代码:
需要事先定义一些变量:
int count = 0;
bool newpic = false;
Mat rawpic; //原始RGB图像
Mat resultMat; //拼接结果
Mat process; //拼接结果后续处理
Mat current; //当前正在处理的部分
Mat left; //上次留下的部分
bool isleft = false; //上次是否留下
每次取固定高度进行拼接:
Mat submat = frame(Rect(0, 0, 1280, BELTVELOCITY));
cvtColor(submat, submat, COLOR_RGB2GRAY);
GaussianBlur(submat, submat, Size(3, 3), 0);
threshold(submat, submat, 200, 255, THRESH_BINARY); //90以上
Mat temp = submat.clone();
//imshow("aa", submat);
if (resultMat.rows > 500)
{
string filename = to_string(count) + ".bmp";
//imwrite(filename, resultMat);
process = resultMat.clone();
resultMat = temp;
newpic = true;
cout << "拼接到一帧图像" << endl;
}
else
{
if (count == 1)
resultMat = temp;
else
{
resultMat = mergeRows(temp, resultMat); //.clone()
newpic = false;
}
}
图像处理:
if (newpic)
{
//首先判断上次是否留下需要拼接的部分
//需要拼接的情况有:上次找到的轮廓中至少有一个在上边缘。
if (isleft) //如果有,和本次图像进行拼接
{
current = mergeRows(process, left);
isleft = false;
}
else //如果没有,直接使用当前图像
{
current = process;
}
Mat temp = current.clone();
bitwise_not(temp,temp); //黑色背景,白色前景作为一个轮廓
findContours(temp, contours_all, hierarchy_all, RETR_TREE, CHAIN_APPROX_NONE, Point(0, 0));
//Mat show;
//cvtColor(process, show, COLOR_GRAY2BGR);
//drawContours(show, contours_all, -1, Scalar(255, 0, 0),-1);
//imshow("轮廓", show);
//string filename = to_string(count) + ".bmp";
//imwrite(filename, show);
int max = 0; //不能裁的轮廓的最下
bool need = false;
for (int i = 0; i < contours_all.size(); i++) //对每一条轮廓进行处理
{
//判断该轮廓是否在边缘,不在,进行提取处理,否则,进行拼接操作
int edge;
for (int j = 0; j < contours_all[i].size(); j++)
{
if (contours_all[i][j].y == 0)
{
edge = 1; //上边缘
break;
}
else if (contours_all[i][j].y == temp.rows-1)
{
edge = -1; //下边缘
break;
}
else
edge = 0;
}
if (edge == 0) //如果一个轮廓不在边缘,需要进行提取
{
//输出轮廓
Mat show;
cvtColor(current, show, COLOR_GRAY2BGR);
drawContours(show, contours_all, i, Scalar(255, 0, 0),-1);
imshow("轮廓", show);
//计算轮廓重心位置
Point p;
GetContourCenter(contours_all[i], p);
cout << "重心纵坐标:" << p.y << endl;
//图片产生时间计算
int outcount = count - p.y / BELTVELOCITY;
string filename = to_string(count) + "-" + to_string(outcount) + ".bmp";
imwrite(filename, show);
//轮廓颜色判断
//轮廓长宽,匹配情况判读
//使用时间戳和像素x值进行判重
}
else if(edge == 1) //如果在上边缘,需要进行拼接操作
{
//找到所有轮廓的最下
for (int j = 0; j < contours_all[i].size(); j++)
{
if (contours_all[i][j].y > max)
max = contours_all[i][j].y;
}
//置需要裁剪标志位
need = true;
}
else //如果在下边缘,不做处理
{
}
}
//把是否需要裁剪的判断放到循环外面,主要为了处理多个轮廓都和上边缘接触。
/*
如果没有和上边缘相交的轮廓,则它们已经被提取出去了
(这还有一个bug,即一个短板和长板在一起时,短板可能会被识别多次。
但是可以使用时间戳和像素x位置进行判重)
*/
if (need)
{
max = min(max + 10, temp.rows - 1);
Mat temp = current(Rect(0, 0, 1280, max + 10));
left = temp.clone();
isleft = true;
}
newpic = false;
}
处理结果:
超长板:
识别帧-时间戳-颜色-轮廓信息
并行板:
识别帧-时间戳
用到的函数
把视频处理成需要的分辨率
void processvideo()
{
VideoWriter writer;
string outFlie = "testoutputp2.avi";
Size videoSize(1280, 320);
int rate = 30;
writer.open(outFlie, VideoWriter::fourcc('M', 'J', 'P', 'G'), rate, videoSize);
VideoCapture cap("testp2.avi");
if (!cap.isOpened())
{
return;
}
Mat frame;
int count = 0;
string show;
while (1)
{
cap >> frame;
if (frame.empty())
break;
//进行分辨率转换
Mat submat = frame(Rect(0, 0, 512, 128));
Size size(1280, 320);
Mat dst;
resize(submat, dst, size);
writer.write(dst);
if (waitKey(1) >= 0)
break;
}
writer.release();
}
图像拼接
Mat mergeRows(Mat A, Mat B)
{
// cv::CV_ASSERT(A.cols == B.cols&&A.type() == B.type());
int totalRows = A.rows + B.rows;
Mat mergedDescriptors(totalRows, A.cols, A.type());
Mat submat = mergedDescriptors.rowRange(0, A.rows);
A.copyTo(submat);
submat = mergedDescriptors.rowRange(A.rows, totalRows);
B.copyTo(submat);
return mergedDescriptors;
}
获取轮廓的重心
void GetContourCenter(vector<Point> contour, Point &p)
{
Moments m = moments(contour);
p.x = m.m10 / m.m00;
p.y = m.m01 / m.m00;
}