本周有机会接触了一点opnev, 在此做一下记录, 最终以框选出下图箱子为目的(图片箱子为相机实拍结果,曝光有点低,会有亿点点暗 ), 本文会拆解步骤并附上图片, 完整的源码在最后.PS:本文参考了好多大佬分享的理论知识, 在此先感谢大佬的分享~~

首先是梳理一下流程, 下图是本次图片处理的大概流程和部分效果图以及部分会用到的算子~~

1.图像读取

图像读取可以直接使用如下模块读取直接路径的图片:

img1 = cv::imread("C:/Users/Jiang/Desktop/data/1.png");

但如果预处理图片较多, 需要从文件夹循环读取处理的话可以用如下模块,只需更改文件夹地址以及文件命名格式就行, 这里我文件夹里的图片名字均为"1.png"这样的格式:


for (int ii = 1; ii < 11; ii++)
{vector<cv::Mat> images;string name = cv::format("C:/Users/Jiang/Desktop/data3/%d.png", ii);cv::Mat img1 = cv::imread(name);if (img1.empty()){printf("没找到图片");return -1;}images.push_back(img1);//如果是循环读取,后面的处理就放在这个位置,不要出花括号哦~
}

2.提取需要处理的roi区域

这里的示例是拍照空间区域中存在一个托盘, 托盘上方有预提取的箱子, 所以需要建立托盘的roi区域, 之后只单独对这个区域进行处理, 这样可以排除干扰项~

cv::resize(img1, img2, cv::Size(img1.cols / 7, img1.rows / 7), 0, 0, cv::INTER_NEAREST);//这里是因为图片太大了,所以等比例缩小了一下面积
//通过roi区域筛选出托盘位置
cv::Mat roi(img2, cv::Rect(190, 140, 280, 250));

3.灰度处理

这一步转成灰度图, 并进行阈值提取,方便之后提取

cv::cvtColor(roi, g_grayImage, cv::COLOR_BGR2GRAY);
cv::threshold(g_grayImage, img3, 12, 255, cv::THRESH_BINARY);

4.灰度处理

这里的腐蚀是为了将阈值分割之后的部分噪声去除, 膨胀是为了还原上一步被删除了的特征点,偷个懒用上面的图了哈~其实这一步就和直接用闭运算是一样的, 但是为了能看出过程的变换我就拆分开了.

//定义腐蚀和膨胀的结构化元素和迭代次数
element = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(2, 2));
int iteration = 2;
int iteration2 = 1;
//腐蚀
cv::morphologyEx(img3, img3, cv::MORPH_ERODE, element, cv::Point(-1, -1), iteration);
cv::imshow("1", roi);
cv::imshow("腐蚀后", img3);
//膨胀
cv::morphologyEx(img3, img3, cv::MORPH_DILATE, element, cv::Point(-1, -1), iteration2);
cv::imshow("膨胀后", img3);

5.提取连通区域并删除其中面积较小的区域

// 提取连通区域,并剔除小面积联通区域
std::vector<std::vector<cv::Point>> contours;
cv::findContours(img3, contours, cv::RETR_LIST, cv::CHAIN_APPROX_NONE);
contours.erase(std::remove_if(contours.begin(), contours.end(), [](const std::vector<cv::Point>& c)
{return cv::contourArea(c) < 150;}), contours.end());// 显示图像
img3.setTo(0);
cv::drawContours(img3, contours, -1, cv::Scalar(255), cv::FILLED);
cv::imshow("删除了小面积区域", img3);    //cv::waitKey(0);

6.筛选出面积较大的区域部分 并绘制矩形 框选出箱子轮廓

这一块的逻辑就是第一次筛选掉小面积区域后, 对剩下的区域再进行一次筛选,这次筛选面积较大的区域, 并对每一个面积大的区域进行单独绘制出轮廓, 并将这个轮廓点集用minAreaRect算子, 输入点集输出四个点的坐标, 并将这四个点用直线连接起来, 就可以得到结果了~

cv::Mat delet(img3.size(), img3.type(), cv::Scalar(0, 0, 0));;cout << "剔除小区域后的轮廓个数:" << contours.size();
for (int tmp = 0; tmp < contours.size(); tmp++)
{if (cv::contourArea(contours[tmp]) > 3000){cout << "大轮廓的编号是" << tmp;cout << "此时的面积为" << cv::contourArea(contours[tmp]);cv::drawContours(delet, contours, tmp, cv::Scalar(255), cv::FILLED);cv::Canny(delet, mid, 0, 18, 3);cv::bitwise_not(mid, bitwise);// 用Canny算子检测边缘 阈值1和阈值2两者中比较小的值用于边缘连接,较大的值用来控制边缘的初始段Canny(bitwise, g_cannyMat_output, g_nThresh, g_nThresh * 2, 3);// 寻找轮廓findContours(g_cannyMat_output, g_vContours, g_vHierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE, cv::Point(0, 0));// 绘出轮廓cv::Mat drawing = cv::Mat::zeros(g_cannyMat_output.size(), CV_8UC3);for (int i = 0; i < g_vContours.size(); i++){cv::Scalar color = cv::Scalar(255, 182, 193);drawContours(drawing, g_vContours, i, color, 2, 8, g_vHierarchy, 0, cv::Point());}cv::imshow("drawidrawingdrawingng", drawing);vector<cv::Point> tempPoint;     // 点集// 将所有点集存储到tempPointfor (int k = 0; k < g_vContours.size(); k++){for (int m = 0; m < g_vContours[k].size(); m++){tempPoint.push_back(g_vContours[k][m]);}}//对给定的 2D 点集,寻找最小面积的包围矩形cv::RotatedRect box = minAreaRect(cv::Mat(tempPoint));cv::Point2f vertex[4];box.points(vertex);//绘制出最小面积的包围矩形for (int i = 0; i < 4; i++){cv::line(roi, vertex[i], vertex[(i + 1) % 4], cv::Scalar(100, 200, 211), 1, cv::LINE_AA);}cv::imshow("最终提取结果", roi);cv::waitKey(0);string Img_Name = "C:\\Users\\Jiang\\Desktop\\result\\" + to_string(k) + ".png";cv::imwrite(Img_Name, roi);//这里是按照固定路劲保存结果图片,下标从0开始,如果重复了会覆盖k++;delet = cv::Scalar(0, 0, 0);//这里是将处理过的图片阈值赋为0,即为黑色,不然处理过的图片还会残留在这里}
}

7.全部源码

#include <iostream>
#include<opencv2\opencv.hpp>
#include "testOpenCV346.h"
#include <memory>
#include <opencv2/highgui/highgui.hpp>
#include <vector>
#include <algorithm>using namespace std;cv::Mat g_srcImage,img1,img2,img3, img4, element, element2, result, result2,erzhi,mid,g_grayImage,kai, bitwise,morph;//这里有一些是定义了其他函数用的,这里没有删哦
int g_nThresh = 13;
cv::Mat g_cannyMat_output;
vector<vector<cv::Point>> g_vContours;    //向量内每个元素保存了一组由连续的Point点构成的点的集合的向量,每一组Point点集就是一个轮廓。有多少轮廓,向量contours就有多少元素
vector<cv::Vec4i> g_vHierarchy;     //“向量内每一个元素包含了4个int型变量”的向量分 别表示第i个轮廓的后一个轮廓、前一个轮廓、父轮廓、内嵌轮廓的索引编号。int main(int argc, char** argv)
{
std::shared_ptr<testOpenCV346> pMyOpenCv = std::make_shared<testOpenCV346>();//for (int ii = 1; ii < 11; ii++)
//{
//    vector<cv::Mat> images;
//    string name = cv::format("C:/Users/Jiang/Desktop/data3/%d.png", ii);
//    cv::Mat img1 = cv::imread(name);//    if (img1.empty())
//    {
//        printf("没找到图片");
//        return -1;
//    }//    images.push_back(img1);// 加载源图像,这里采用了固定路径,如果要循环,就用上面的for,记得下面的花括号打开就行img1 = cv::imread("C:/Users/Jiang/Desktop/data/1.png");cv::resize(img1, img2, cv::Size(img1.cols / 7, img1.rows / 7), 0, 0, cv::INTER_NEAREST);//通过roi区域筛选出托盘位置cv::Mat roi(img2, cv::Rect(190, 140, 280, 250));cv::Mat roi1(img2, cv::Rect(190, 160, 261, 210));//转成灰度并模糊化降噪cv::cvtColor(roi, g_grayImage, cv::COLOR_BGR2GRAY);cv::threshold(g_grayImage, img3, 12, 255, cv::THRESH_BINARY);cv::imshow("阈值之后", img3);//cv::waitKey(0);//定义腐蚀和膨胀的结构化元素和迭代次数element = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(2, 2));element2 = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3));int iteration = 2;int iteration2 = 1;//腐蚀cv::morphologyEx(img3, img3, cv::MORPH_ERODE, element, cv::Point(-1, -1), iteration);cv::imshow("1", roi);cv::imshow("腐蚀后", img3);//cv::waitKey(0);cv::morphologyEx(img3, img3, cv::MORPH_DILATE, element, cv::Point(-1, -1), iteration2);cv::imshow("膨胀后", img3);//cv::waitKey(0);// 提取连通区域,并剔除小面积联通区域std::vector<std::vector<cv::Point>> contours;cv::findContours(img3, contours, cv::RETR_LIST, cv::CHAIN_APPROX_NONE);contours.erase(std::remove_if(contours.begin(), contours.end(), [](const std::vector<cv::Point>& c){return cv::contourArea(c) < 150;}), contours.end());// 显示图像img3.setTo(0);cv::drawContours(img3, contours, -1, cv::Scalar(255), cv::FILLED);cv::imshow("删除了小面积区域", img3);    //cv::waitKey(0);//cv::waitKey(0);cv::Mat delet(img3.size(), img3.type(), cv::Scalar(0, 0, 0));;cout << "剔除小区域后的轮廓个数:" << contours.size();for (int tmp = 0; tmp < contours.size(); tmp++){if (cv::contourArea(contours[tmp]) > 3000){cout << "大轮廓的编号是" << tmp;cout << "此时的面积为" << cv::contourArea(contours[tmp]);cv::drawContours(delet, contours, tmp, cv::Scalar(255), cv::FILLED);cv::Canny(delet, mid, 0, 18, 3);cv::bitwise_not(mid, bitwise);// 用Canny算子检测边缘 阈值1和阈值2两者中比较小的值用于边缘连接,较大的值用来控制边缘的初始段Canny(bitwise, g_cannyMat_output, g_nThresh, g_nThresh * 2, 3);// 寻找轮廓findContours(g_cannyMat_output, g_vContours, g_vHierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE, cv::Point(0, 0));// 绘出轮廓cv::Mat drawing = cv::Mat::zeros(g_cannyMat_output.size(), CV_8UC3);for (int i = 0; i < g_vContours.size(); i++){cv::Scalar color = cv::Scalar(255, 182, 193);drawContours(drawing, g_vContours, i, color, 2, 8, g_vHierarchy, 0, cv::Point());}cv::imshow("drawidrawingdrawingng", drawing);//cv::waitKey(0);vector<cv::Point> tempPoint;     // 点集// 将所有点集存储到tempPointfor (int k = 0; k < g_vContours.size(); k++){for (int m = 0; m < g_vContours[k].size(); m++){tempPoint.push_back(g_vContours[k][m]);}}//对给定的 2D 点集,寻找最小面积的包围矩形cv::RotatedRect box = minAreaRect(cv::Mat(tempPoint));cv::Point2f vertex[4];box.points(vertex);//绘制出最小面积的包围矩形for (int i = 0; i < 4; i++){cv::line(roi, vertex[i], vertex[(i + 1) % 4], cv::Scalar(100, 200, 211), 1, cv::LINE_AA);}cv::imshow("最终提取结果", roi);cv::waitKey(0);string Img_Name = "C:\\Users\\Jiang\\Desktop\\result\\" + to_string(k) + ".png";cv::imwrite(Img_Name, roi);//这里是按照固定路劲保存结果图片,下标从0开始,如果重复了会覆盖k++;delet = cv::Scalar(0, 0, 0);//这里是将处理过的图片阈值赋为0,即为黑色,不然处理过的图片还会残留在这里}}//cv::waitKey(0);
//}
cout << "任务完成咯~`";
return(0);
}

至此, 本次分享的箱子提取过程完结, 图片处理其实很吃图片的像素分布情况 ( 是否有过亮过暗, 是否反光, 是否有噪声, 是否清晰等 ) , 比如本次图片就是属于较暗的情况, 在阈值分割的时候就很头疼, 用了自适应阈值和直方图阈值分割的方式, 但效果并不是很理想 ,最终还是手动给了阈值, 而且干扰项也蛮多的, 处理方式还是比较简单和基础, 最后再附一张轮廓检测的结果图 , 效果其实也还行 . 比如图片17 , 原图是三个箱子堆叠在一起 , 但是轮廓补全后还是把箱子轮廓框选出来了.

这里单独提一下,在VS的"工具"→"拓展和跟新"里可以找到"Image Watch 2017"插件,可以用来查看图片的具体像素点,在阈值处理的时候会很有用

OPENCV C++图像提取,图像处理,roi,阈值分割,连通区域筛选,边缘检测(以箱子边缘框选为例)相关推荐

  1. Python代码库OpenCV之12提取碑文文字并分割

    Python代码库OpenCV之11提取碑文文字并分割 代码参考 https://www.cnblogs.com/phil-chow/p/5612270.html 我在作者基础做了简单修改,目前适用于 ...

  2. 实战解惑 | OpenCV中如何提取不规则ROI区域

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 什么是ROI ROI是英文Region Of Interest的三 ...

  3. OpenCV中如何提取不规则ROI区域, 手眼标定hand_eye_calibration

    opencv提取ROI区域: 在做这个之前,首先来了解一下什么图像处理中的mask(遮罩),OpenCV中是如此定义Mask的:八位单通道的Mat对象,每个像素点值为零或者非零区域.当Mask对象添加 ...

  4. 图像处理——常用阈值分割方法及源码

    目录 1.Otsu阈值分割 2.自适应阈值分割 3. 最大熵阈值分割法 4. 迭代阈值分割 5.测验 1.Otsu阈值分割 Otsu(大津法或最大类间方差法)使用的是聚类的思想,把图像的灰度数按灰度级 ...

  5. 数字图像处理之阈值分割

    本章节主要包括下面的内容: 导入库 import numpy as np import matplotlib.pyplot as plt import cv2 as cv 输出图片 def show( ...

  6. 【图像处理】阈值分割

    图像阈值分割 一.简介 阈值分割常用在灰度图像中,将灰度值以一定的阈值进行分割,分为0或者255,使图像的像素值只有0或者255(非黑即白).由于不同物体的像素值不同,根据设置的阈值,将图像中的物体以 ...

  7. OpenCV二值图像处理——阈值,连通区域分析(C++)

    阈值 阈值又叫临界值,是指一个效应能够产生的最低值或最高值 对于图像的直方图存在明显边界的图像,我们可以很容易找到这个阈值,但是如果图像直方图分界不明显,那么这个阈值的寻找将变得十分困难.因此我们存在 ...

  8. 如何使用OpenCV在图像中抠出指定的颜色区域

    1 简要说明及流程 我们往往需要在图像中抠出指定颜色的区域.在实际工况下这种指定颜色不是简单的纯色,往往难以单一的用R G B的某个范围值去确定. 本文以PhotoShop为基准,在一副彩色图中按照提 ...

  9. OpenCV编程案例:使用轮廓函数检测连通区域

    转自:http://www.aiseminar.cn/bbs/thread-617-1-1.html 此案例位于CXCORE中cvDrawContours函数介绍部分给出.此程序首先载入一个二值图像文 ...

最新文章

  1. 2个月精通Python爬虫——3大爬虫框架+6场实战+分布式爬虫,包教包会
  2. 研磨设计模式之 策略模式--转
  3. python正态检验_Python检验数据是否正态分布
  4. 如何修改wince的网络配置
  5. python dataframe批量将列名加后缀_Python中的dataframe对象如何用相同的列名堆叠两个表,并从行堆叠它们以形成一个表,Dataframe,将,具有,叠加,起来,上,组成...
  6. 写文件 追加到开始_文件和流
  7. POJ 1681 高斯消元 枚举自由变元
  8. 一亿像素!小米CC9 Pro明日正式首卖:2799元起
  9. 新华三模拟器STP和RSTP及其MSTP的作用与配置
  10. 阶段3 2.Spring_09.JdbcTemplate的基本使用_6 JdbcDaoSupport的使用以及Dao的两种编写方式...
  11. 常用方法总结--文件操作篇
  12. 医院业务系统灾备建设,数腾:为生命保驾护航
  13. 学生信息管理系统(SSM+JSP)
  14. 一套非常精美的全球地貌晕渲图,有想要的吗?
  15. 冷山的博客思听有声书摘下载索引页
  16. ❤️❤️❤️Unity实现毛笔书法
  17. windows xp 实现远程关机
  18. python实现克莱姆法则
  19. Petri网-简单程序设计
  20. Amazon S3下载图片

热门文章

  1. java 集成讯飞语音 pc_【报Bug】sdk集成 讯飞语音输入jar包报错
  2. MIPI扫盲——DBI介绍
  3. vr技术在计算机导论,清华大学出版社-图书详情-《虚拟现实与增强现实技术概论》...
  4. 大数据基础课11 让你一看就懂的数据挖掘四大经典算法
  5. ps4播放器显示dlna服务器,PS4更新:期待已久的功能终于出现了
  6. linux中文件名以圆点开头的文件是,在UNIX下以小圆点开头的文件是( )。
  7. TARS-PHP:PHP构建高性能RPC框架
  8. XCTF-攻防世界CTF平台-Web类——9、PHP2(.phps文件、url编码)
  9. OpenCV入门系列2:图像叠加、填充和腐蚀
  10. 数据驱动销售——个性化推荐引擎