基于C++的OpenCV项目实战——物品检测(降噪、去除背景、连通域检测、轮廓定位)

感谢

基于C++的OpenCV项目实战——零部件的自动光学检测_opencv 滤波增强 零件测量-CSDN博客

基于opencv的c++图像处理(图像二值化)_opencv c++ 二值化-CSDN博客

代码实现

main函数

#include<opencv2/opencv.hpp>
#include<iostream>
#include<string>

#include "Display.h"
#include "Blur.h"
using namespace cv;
using namespace std;

// 使用智能指针
shared_ptr<Display> multi_window;

int main(int argc, char** argv)
{

	//图片和背景图
	String total_path = "./resource/原图.jpeg";
	String background_path = "./resource/背景图.jpg";
	Mat image = imread(total_path);
	Mat backgroud_image = imread(background_path);

	//图片去噪处理
	Mat image_denoising = image.clone();
	Mat backgroud = backgroud_image.clone();
	Blur br;
	image_denoising = br.smooth_image(image);
	backgroud = br.smooth_image(backgroud_image);

	//去除背景
	Mat image_no_backgroud = br.remove_background_minus(image_denoising, backgroud);
	
	//连通域检测
	Mat image_connection_check = br.connection_check(image_no_backgroud);

	// 边缘检测
	Mat image_contour = br.get_contour(image_no_backgroud);

	//展示

	multi_window = make_shared<Display>("Review for all", 3, 2, WINDOW_NORMAL);
	multi_window->add_window("image", image);
	multi_window->add_window("denoise", image_denoising);
	multi_window->add_window("bg", backgroud);
	multi_window->add_window("remove bg", image_no_backgroud);
	multi_window->add_window("segment", image_connection_check);
	multi_window->add_window("contour", image_contour);

	// 保存结果
	imwrite("./result/结果展示.png", multi_window->canvas);
	waitKey(0);
	return 0;
}

构建多窗口展示Display类

Display.cpp文件

#include "Display.h"

//初始化窗口
Display::Display(String t, int c, int r, int flags) :title(t), cols(c), rows(r) {
	height = 1080;
	width = 1920;
	namedWindow(title, flags);
	canvas = Mat(height, width, CV_8UC3);
	imshow(title, canvas);
}

//增加图片
int Display::add_window(String win_name, Mat image, bool flag) {
	win_names.push_back(win_name);
	images.push_back(image);
	if (flag) {
		draw();
	}
	return win_names.size();
}

// 删除图片
int Display::delete_window(String win_name) {
	int index = 0;
	for (const auto& it : win_names) {
		if (it == win_name) break;
		index++;
	}
	win_names.erase(win_names.begin() + index);
	images.erase(images.begin() + index);
	//删除后更新绘图
	draw();
	return win_names.size();
}

//绘图
void Display::draw() {
	canvas.setTo(Scalar(20, 20, 20));
	int single_width = width / cols;
	int single_height = height / rows;
	int max_win = win_names.size() > cols * rows ? cols * rows : win_names.size();

	int i = 0;
	auto iw = win_names.begin();
	for (auto it = images.begin(); it != images.end() && i < max_win; it++, i++, iw++) {
		String win_name = *iw;
		Mat img = *it;

		int x = (single_width) * (i % cols);
		int y = (single_height)*floor(i * 1.0 / cols);
		Rect mask(x, y, single_width, single_height);

		rectangle(canvas, mask, Scalar(255, 255, 255), 9);

		Mat resized_img;
		resize(img, resized_img, Size(single_width, single_height));

		Mat sub_canvas(canvas, mask);
		if (resized_img.channels() == 1) {
			cvtColor(resized_img, resized_img, COLOR_GRAY2BGR);
		}
		resized_img.copyTo(sub_canvas);
		putText(sub_canvas, win_name, Point(50, 50), FONT_HERSHEY_COMPLEX, 2, Scalar(0, 0, 255), 3, LINE_AA);
	}
	imshow(title, canvas);
}

Display.h

#pragma once
#include<opencv2/opencv.hpp>
#include<string>
#include<iostream>

using namespace cv;
using namespace std;

//多窗口展示类
class Display {
private:
	int cols, rows, width, height;
	String title;
	vector<String> win_names;
	vector<Mat> images;
	
public:
	Mat canvas;
	Display(String t, int c, int r, int flags);
	//添加图片
	int add_window(String win_name, Mat image, bool flag = true); 
	int delete_window(String win_name);// 实现删除窗口
	void draw();
};


构建图像处理类(降噪、去除背景、连通域检测、边缘检测等)

Blur.cpp文件

#include "Blur.h"

Mat Blur::get_backgroud(Mat& image) {
	Mat img_out;
	medianBlur(image, img_out,3);
	GaussianBlur(img_out, img_out, Size(3, 3), 0);
	return img_out;
}

Mat Blur::smooth_image(Mat& image) {
	Mat img_out;
	medianBlur(image, img_out, 3);
	GaussianBlur(img_out, img_out, Size(5, 5), 0);
	return img_out;
}

//去除背景,除法
Mat Blur::remove_background_divide(Mat& image,Mat& backgroud) {
	Mat img_out, fg, bg;
	image.convertTo(fg, CV_32F);
	backgroud.convertTo(bg, CV_32F);
	img_out = 1 - (fg / bg);
	img_out.convertTo(img_out, CV_8U, 255);
	return img_out;

}

//去除背景,减法
Mat Blur::remove_background_minus(Mat& image, Mat& background) {
	return background - image;
}

//连通域检测
Mat Blur::connection_check(Mat& image) {
	if (image.type() != CV_8U) {
		image.convertTo(image, CV_8U);
	}
	Mat srcGray;
	if (image.channels() == 3)
	{
		cvtColor(image, srcGray, COLOR_RGB2GRAY);
	}
	else {
		srcGray = image.clone();
	}
	//二值化
	Mat otsuImage;
	const int maxVal = 255;
	int threshType = 0;
	ImgB.OtsuBinarizate(srcGray, otsuImage, maxVal, threshType);

	//连通域检测
	Mat labels, stats, centroids;
	int num = connectedComponentsWithStats(otsuImage, labels, stats, centroids);
	if (num <= 1) {
		cout << "No stuff detect!!" << endl;
		return labels;
	}
	else
	{
		cout << num << " objects detected!!" << endl;
	}
	Mat display = Mat::zeros(otsuImage.rows, otsuImage.cols, CV_8UC3);
	RNG rng(12345);
	for (int i = 1; i < num; i++) {
		// 得到连通域的质心点
		Point2i pt(centroids.at<double>(i, 0), centroids.at<double>(i, 1));
		if (stats.at<int>(i, CC_STAT_AREA) < 1000) { // 面积小于1000的不要
			continue;
		}
		// 打印标签和连通域坐标和面积
		cout << "Stuff #" << i << ", Position: " << pt << " ,Area: " << stats.at<int>(i, CC_STAT_AREA) << endl;
		Mat mask = (labels == i);
		int b = rng.uniform(0, 256);
		int g = rng.uniform(0, 256);
		int r = rng.uniform(0, 256);
		display.setTo(Scalar(b,g,r), mask);
		stringstream ss;
		ss << stats.at<int>(i, CC_STAT_AREA);
		putText(display, ss.str(), pt, FONT_HERSHEY_SIMPLEX, 0.5, Scalar(255, 255, 255), 2);
	}
	return display;
}

//边缘检测
Mat Blur::get_contour(Mat& image) {
	if (image.type() != CV_8U) {
		image.convertTo(image, CV_8U);
	}
	Mat srcGray;
	if (image.channels() == 3)
	{
		cvtColor(image, srcGray, COLOR_RGB2GRAY);
	}
	else {
		srcGray = image.clone();
	}
	//二值化
	Mat otsuImage;
	const int maxVal = 255;
	int threshType = 0;
	ImgB.OtsuBinarizate(srcGray, otsuImage, maxVal, threshType);

	vector<vector<Point>> contours;
	findContours(otsuImage, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);

	Mat display = Mat::zeros(image.size(), CV_8UC3);
	if (contours.size() == 0) {
		cout << "没有轮廓" << endl;
		return display;
	}
	else {
		cout << "检测到轮廓: " << contours.size() << endl;
	}
	RNG rng(12345);
	for (int i = 0; i < contours.size(); i++) {
		int b = rng.uniform(0, 256);
		int g = rng.uniform(0, 256);
		int r = rng.uniform(0, 256);
		drawContours(display, contours, i, Scalar(b, g, r));
	}

	return display;
}

Blur.h文件

#pragma once
#include<opencv2/opencv.hpp>
#include<iostream>
#include "Binarizate.h"

using namespace cv;
using namespace std;
class Blur
{
public:
	ImgEnhance::Binarizate ImgB;//二值化
	//降噪
	Mat get_backgroud(Mat& image);
	Mat smooth_image(Mat& image);
	//去除背景图
	Mat remove_background_divide(Mat& image, Mat& backgroud);
	Mat remove_background_minus(Mat& image, Mat& background);
	//连通域检测
	Mat connection_check(Mat& image);
	//边缘检测
	Mat get_contour(Mat& image);
};


附:图像二值化方法

Binarizate.h

#pragma once
#include <iostream>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\core\core.hpp>
#include <opencv2\imgproc\imgproc.hpp>

using namespace std;
using namespace cv;
#define PROCESS_IMG_SUCESS 0
#define PROCESS_IMG_FAIL 1

namespace ImgEnhance
{
	//二值化
	class Binarizate
	{

	public:
		Binarizate() { cout << "Binarizate is being created" << endl; } // 这是构造函数声明
		~Binarizate() { cout << "Binarizate is being deleted" << endl; } // 这是析构函数声明

		int OTSU(cv::Mat srcImage);// 大律法函数实现
		int OtsuBinarizate(cv::Mat srcImage, cv::Mat& dstImage, int maxVal, int threshType);//大律法二值化
		int FixedBinarizate(cv::Mat srcImage, cv::Mat& dstImage, int thresh, int maxVal, int threshType);//固定化阈值
		int AdaptiveBinarizate(cv::Mat srcImage, cv::Mat& dstImage, int maxVal, int adaptiveMethod, int thresholdType, int blockSize, int constValue);//自适应阈值化

	};
}

Binarizate.cpp

#include "binarizate.h"

int ImgEnhance::Binarizate::OTSU(cv::Mat srcImage)
{
	int nCols = srcImage.cols;
	int nRows = srcImage.rows;
	int threshold = 0;
	// 初始化统计参数
	int nSumPix[256];
	float nProDis[256];
	for (int i = 0; i < 256; i++)
	{
		nSumPix[i] = 0;
		nProDis[i] = 0;
	}
	// 统计灰度级中每个像素在整幅图像中的个数 
	for (int i = 0; i < nCols; i++)
	{
		for (int j = 0; j < nRows; j++)
		{
			nSumPix[(int)srcImage.at<uchar>(i, j)]++;
		}
	}
	// 计算每个灰度级占图像中的概率分布
	for (int i = 0; i < 256; i++)
	{
		nProDis[i] = (float)nSumPix[i] / (nCols * nRows);
	}
	// 遍历灰度级[0,255],计算出最大类间方差下的阈值  
	float w0, w1, u0_temp, u1_temp, u0, u1, delta_temp;
	double delta_max = 0.0;
	for (int i = 0; i < 256; i++)
	{
		// 初始化相关参数
		w0 = w1 = u0_temp = u1_temp = u0 = u1 = delta_temp = 0;
		for (int j = 0; j < 256; j++)
		{
			//背景部分 
			if (j <= i)
			{
				// 当前i为分割阈值,第一类总的概率  
				w0 += nProDis[j];
				u0_temp += j * nProDis[j];
			}
			//前景部分   
			else
			{
				// 当前i为分割阈值,第一类总的概率
				w1 += nProDis[j];
				u1_temp += j * nProDis[j];
			}
		}
		// 分别计算各类的平均灰度 
		u0 = u0_temp / w0;
		u1 = u1_temp / w1;
		delta_temp = (float)(w0 * w1 * pow((u0 - u1), 2));
		// 依次找到最大类间方差下的阈值    
		if (delta_temp > delta_max)
		{
			delta_max = delta_temp;
			threshold = i;
		}
	}
	return threshold;
}

int ImgEnhance::Binarizate::OtsuBinarizate(cv::Mat srcImage, cv::Mat& dstImage, int maxVal, int threshType)
{
	if (!srcImage.data || srcImage.channels() != 1)
	{
		return 1;
	}
	// 初始化阈值参数
	int thresh = OTSU(srcImage);
	// 初始化阈值化处理的类型 
	/* 0: 二进制阈值 1: 反二进制阈值 2: 截断阈值
	3: 0阈值   4: 反0阈值*/
	//int threshType = 0;
	// 预设最大值
	//const int maxVal = 255;
	// 固定阈值化操作
	cv::threshold(srcImage, dstImage, thresh,
		maxVal, threshType);
	return 0;

}

int ImgEnhance::Binarizate::FixedBinarizate(cv::Mat srcImage, cv::Mat& dstImage, int thresh, int maxVal, int threshType)
{
	if (!srcImage.data || srcImage.channels() != 1)
	{
		return 1;
	}
	// 初始化阈值参数
	//int thresh = 130;
	// 初始化阈值化处理的类型 
	/* 0: 二进制阈值 1: 反二进制阈值 2: 截断阈值
	3: 0阈值   4: 反0阈值 8:大均法*/
	//int threshType = 0;
	// 预设最大值
	//const int maxVal = 255;
	// 固定阈值化操作
	cv::threshold(srcImage, dstImage, thresh,
		maxVal, threshType);
	return 0;
}

int ImgEnhance::Binarizate::AdaptiveBinarizate(cv::Mat srcImage, cv::Mat& dstImage, int maxVal, int adaptiveMethod, int thresholdType, int blockSize, int constValue)
{
	if (!srcImage.data || srcImage.channels() != 1)
	{
		return 1;
	}
	// 初始化自适应阈值参数
	//int blockSize = 5;
	//int constValue = 10;
	//const int maxVal = 255;
	/* 自适应阈值算法
	0:ADAPTIVE_THRESH_MEAN_C
	1: ADAPTIVE_THRESH_GAUSSIAN_C
	阈值类型
	0: THRESH_BINARY
	1: THRESH_BINARY_INV */
	//int adaptiveMethod = 0;
	//int thresholdType = 1;
	// 图像自适应阈值操作
	cv::adaptiveThreshold(srcImage, dstImage, maxVal, adaptiveMethod, thresholdType, blockSize, constValue);
	return 0;
}

附件:原图.jpeg和背景图.jpg

图片来源:【DIY贴片机】基于opencv识别定位电子元件_电感元件定位 opencv-CSDN博客

运行后的结果

结果展示.png

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值