“利用可见光传输信息的软件”项目简介(二)

团队——天亮说晚安

视频解码模块

//生成遮罩
std::vector<cv::Mat> mask(8);
for(int i = 0; i < 8; i++) {
   mask[i] = ...
}

目前,项目读取编码的手段是将图像根据定位点将图像尺寸还原,用遮罩捕获目标区域,所以需要先初始化std::vector<mask>

std::vector<cv::Mat>video_pro;
for (int k = 0; k < frames ; k++) {
   //彩色转灰度
   cv::cvtColor(src, src, cv::COLOR_RGB2GRAY);

   //阈值
   int SRC_THRESH_LOW = this->THRESH;
   cv::threshold(src, src, SRC_THRESH_LOW, 255, cv::THRESH_BINARY);

   //Canny边缘检测
   cv::Mat src_gray = src;
   cv::Mat canny_output;
   cv::Canny(src_gray, canny_output, 200, 255);

   //轮廓提取
   std::vector<std::vector<cv::Point> > contours;
   std::vector<cv::Vec4i> hierarchy;
   cv::findContours(canny_output, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);

   if(contours.size()<1)//若提取失败,跳过此帧
       continue;

   //提取最大矩形,即屏幕现实范围
   cv::Rect poly_rect_max=cv::boundingRect(contours[0]);
   for (int i = 1; i < contours.size(); i++) {
        if(poly_rect_max.area()<cv::boundingRect(contours[i]).area())
             poly_rect_max=cv::boundingRect(contours[i]);
   }

   //ROI坐标生成 tl=topleft,br=bottomright
   int tl_x =  poly_rect_max.tl().x;
   int br_x =  poly_rect_max.br().x;
   int tl_y =  poly_rect_max.tl().y;
   int br_y =  poly_rect_max.br().y;

   //ROI区域生成
   cv::Rect rect(tl_x, tl_y, br_x - tl_x, br_y - tl_y);
   cv::Mat srcRoi=cv::Mat::zeros(src.size(), CV_8UC1);
   srcRoi=src(rect);
   cv::resize(srcRoi, srcRoi, RSFRAME);

   //存入预降噪vector保存
   video_pro.push_back(srcRoi);
}

这一步是降噪得以实现的核心,通过电脑屏幕底色的白色,将目标区域初步的提取出来,降低外部的噪声。cv::cvtColor()cv::threshold()实现初步的噪声去除,cv::Canny()cv::findContours提取出ROI区域和相应的轮廓,将轮廓外的区域全部消除。此步本应该为可选项,但是该段逻辑在Release 1.0的版本本是被强制执行的,会出现用户很精准的没有录入噪声,却读取失败的情况,Beta 2.0版本中拟予以优化。

std::vector<cv::Mat> video_diff;
for(int i=1;i<video_pro.size();i++){
   //差值
   cv::Mat st=video_pro[i]-video_pro[i-1];

   //形态学开
   cv::Mat kernelsrc = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(23, 23), cv::Point(-1, -1));
   cv::morphologyEx(st, st, cv::MORPH_OPEN, kernelsrc);

  //存入差值vector

  video_diff.push_back(st);
}

使用帧差法有两个目的,第一个是进一步去除噪声。第二目的是捕获关键帧。这里的关键帧是指清晰成像的,带有信息的图像。实际帧之间做差时,有三种情况:

  • [i-1]帧为空白帧,[i]帧为关键帧(无论清晰与否,下同)——这时,由于减去白色的缘故,[i]帧信息全部丢失。
  • [i-1]帧为关键帧,[i]也为关键帧——做差后,[i]会删去大部分信息,留下一些无用的噪点。
  • [i-1]帧为关键帧,[i]空白帧——这时,空白帧[i]会因为剪掉了信息区域外的黑色而保留信息区域。

这个时候,理论上,第一种情况产生黑色空白帧直接被忽略,第二种情况在形态学开操作cv::morphologyEx()的处理下,退化为黑色空白帧,只有第三种情况产生的帧会被捕获。但在实际解算时,由于灰阶响应的缘故,阈值后的信息会出现不完整的情况。这种情况会直接导致程序不正常退出或者OpenCV断言错误,实际UI显示为卡66%的进度。
Relese 1.0版本中这个现象尤为严重,因此为Relese 2.0版本发布前的重中之重。

for(int i=0;i<video_diff.size();i++){
   //过滤低信息图片
   if(cv::countNonZero(video_diff[i])==0)
       continue;
   cv::Mat read=video_diff[i];
   //Canny边缘查找
   cv::Mat src_gray = read;
   cv::Mat canny_output;
   cv::Canny(src_gray, canny_output, 200, 255);

   //轮廓查找
   std::vector<std::vector<cv::Point> > contours;
   std::vector<cv::Vec4i> hierarchy;
   cv::findContours(canny_output, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
   if(contours.size()<=2)
       continue;
   //从轮廓生成最小矩形
   std::vector<cv::Rect>poly_rect(contours.size());
   for (int i = 0; i < contours.size(); i++) {
      poly_rect[i] = cv::boundingRect(contours[i]);
   }
   //矩形轮廓x坐标整理
   for (int i = poly_rect.size(); i > 0; i--) {
       for (int j = 0; j < i - 1; j++) {
           if (poly_rect[j].tl().x > poly_rect[j + 1].tl().x)
               swap(poly_rect[j], poly_rect[j + 1]);
       }
   }
   //矩形轮廓y坐标整理
   for (int i = 0; i + 1 < poly_rect.size(); i += 2) {
       if (poly_rect[i].tl().y > poly_rect[i + 1].tl().y)
            swap(poly_rect[i], poly_rect[i + 1]);
   }

   //ROI坐标生成 tl=topleft,br=bottomright
   int tl_x = poly_rect[0].tl().x;
   int br_x = poly_rect[poly_rect.size() - 1].br().x;
   int tl_y = poly_rect[0].tl().y;
   int br_y = poly_rect[poly_rect.size() - 1].br().y;
   if(tl_x&&br_x&&tl_y&&br_y==0)
       continue;

   //矩形轮廓重建
   cv::Mat rscrc = cv::Mat::zeros(canny_output.size(), CV_8UC1);
   for (int i = 0; i < poly_rect.size(); i++) {
       cv::rectangle(rscrc, poly_rect[i], WHITE, cv::FILLED);
   }

   //ROI区域生成
   cv::Rect rect(tl_x, tl_y, br_x - tl_x, br_y - tl_y);
   cv::Mat srcRoi=cv::Mat::zeros(rscrc.size(), CV_8UC1);
   srcRoi=rscrc(rect);
   cv::resize(srcRoi, srcRoi, RSFRAME);

   //解码
   char c=0;
   for (int i = 7; i >= 0; i--) {
       //缓存帧
       cv::Mat p = cv::Mat::zeros(RSFRAME, CV_8UC1);

       //添加遮罩
       p = srcRoi-mask[i];

       //译码
       if (cv::countNonZero(p) >500)
           c |= 0x01;
       if (i != 0)
           c <<= 1;
   }
}

最后解码阶段,相当于第一步的复现,没有新的内容,最后的循环实现了解码,相当于编码的逆过程。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容