OpenCV----简单目标提取和分割

本文介绍了一种基于OpenCV的图像处理方法,通过去除噪声、消除光照影响并采用不同分割算法来提取图像中的物体特征,如面积和质心等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目要求:提取一张背景简单的图像中物体的信息,包括面积,质心等特征;考虑图像存在噪声,首先对图像进行预处理,然后使用分割算法,在图像中标注对应信息;可能的话考虑在一个窗口中显示图像处理过程结果;

分析:
1)对图像降噪的基本流程包括:噪声消除、光消除、二值化;
2)常用的分割算法包括:轮廓检测findContours()和连通域分析connectedComponent();
3)考虑设计多窗口类MWindow,支持初始化定义窗口大小、子窗口数量设计;
4)考虑到光消除算法可以提供背景图,也可以不提供背景图,分割算法可以之获取分割图像,可以获取连通域面积特征,也可以获取连通域质心或轮廓特征,因此设计支持命令行控制的CommandLineParser对象;

代码实现:

  • MWindow类设计:
// mwindow.hpp
/*
@File          :mwindow.hpp
@Description:  :multiwindow to displace multi images
@Date          :2021/12/22 15:56:02
@Author        :xieyin
@version       :1.0
*/
#pragma once

#include<iostream>
#include<string>
#include<vector>
using namespace std;

#include<opencv2/core.hpp>
#include<opencv2/highgui.hpp>
using namespace cv;

class MWindow{
    public:
        // consturtor
        MWindow(string windowTitle, int rows, int cols, int flags);
        // add image into canvas
        int addImage(string title, Mat img, bool render = false);
        // remove image from canvas
        void removeImage(int pos);
        // adjust all image size in canvas
        void render();
    private:
        int mRows, mCols;
        string mWindowTitle;
        Mat mCanvas;
        vector<string> mSubTitles;
        vector<Mat> mSubImages;
        
};

MWindow::MWindow(string windowTitle, int rows, int cols, int flags):mWindowTitle(windowTitle), mRows(rows), mCols(cols){
    /*
    @description  : MWindow constructor
    @param  : 
        windowTitle : whole window title
        rows : sub window rows
        cols : sub window cols
        flags : namedWindow flags (eg, WINDOW_AUTOSIZE)
    @Returns  : 
    */
   // create canvas
    namedWindow(mWindowTitle, flags);
    mCanvas = Mat(700, 1500, CV_8UC3);
    imshow(mWindowTitle, mCanvas);
}

int MWindow::addImage(string title, Mat img, bool render){
    /*
    @description  : add title and image into canvas
    @param  : 
        title : sub image title
        img : image to be added
        render : render(flag) whether need to adjust the image for canvas
    @Returns  : 
        index : sub image index in total mRows * mCols
    */
    mSubTitles.push_back(title);
    mSubImages.push_back(img);
    if(render){
        MWindow::render();
    }
    return mSubImages.size() - 1;
}


void MWindow::removeImage(int pos){
    /*
    @description  : remove image from canvas based on index
    @param  : 
        pos : sub image index in total mRows * mCols
    @Returns  : 
        None
    */
    mSubTitles.erase(mSubTitles.begin() + pos);
    mSubImages.erase(mSubImages.begin() + pos);
}

void MWindow::render(){
    /*
    @description  : fill title and image into canvas in suitable way
    @param  : 
        None
    @Returns  :
        None 
    */
    mCanvas.setTo(Scalar(20, 20, 20));
    // get sub canvas size
    int cellH = mCanvas.rows / mRows;
    int cellW = mCanvas.cols / mCols;
    int margin = 10;
    // set total number of images to load
    int numImgs = mSubImages.size() > mRows * mCols ? mRows * mCols : mSubImages.size();
    for(int i = 0; i < numImgs; i++){
        // get title
        string title = mSubTitles[i];
        // get sub canvas top left location
        int cellX = (cellW) * ((i) % mCols);
        int cellY = (cellH) * floor( (i) / (float) mCols);
        Rect mask(cellX, cellY, cellW, cellH);
        // set subcanvas size
        rectangle(mCanvas, Rect(cellX, cellY, cellW, cellH), Scalar(200, 200, 200), 1);
        Mat cell(mCanvas, mask);
        Mat imgResz;
        // get cell aspect
        double cellAspect = (double) cellW / (double) cellH;
        // get image
        Mat img = mSubImages[i];
        // get image aspect
        double imgAspect = (double) img.cols / (double) img.cols;
        double wAspect = (double) cellW / (double) img.cols;
        double hAspect = (double) cellH / (double) img.rows;
        // get suitable aspect and resize image
        double aspect = cellAspect < imgAspect ? wAspect : hAspect;
        resize(img, imgResz, Size(0, 0), aspect, aspect);
        // if gray image, convert to BGR
        if(imgResz.channels() == 1){
            cvtColor(imgResz, imgResz, COLOR_GRAY2BGR);
        }

        Mat subCell(mCanvas, Rect(cellX, cellY, imgResz.cols, imgResz.rows));
        imgResz.copyTo(subCell);
        putText(cell, title, Point(20, 20), FONT_HERSHEY_SIMPLEX, 0.6, Scalar(255, 0, 0));
    }
    // show total canvas
    imshow(mWindowTitle, mCanvas);

}
  • 主函数:
/*
@File          :main.cpp
@Description:  :do connected component demo in a multi window
@Date          :2021/12/22 15:56:53
@Author        :xieyin
@version       :1.0
*/
#include<iostream>
#include<string>
#include<cmath>
#include<memory>
using namespace std;

#include<opencv2/core/utility.hpp>
#include<opencv2/highgui.hpp>
#include<opencv2/imgproc.hpp>
using namespace cv;

#include"mwindow.hpp"

shared_ptr<MWindow> myWin;

Scalar randColor(RNG& rng){
    /*
    @description  : genert randow color
    @param  : 
        rng : random number generator object
    @Returns  : 
        Sacalar() : BGR scalar
    */
    auto iColor = (unsigned)rng;
    return Scalar(iColor&255, (iColor >> 8)&255, (iColor >> 16)&255);
}

Mat calLigthPattern(Mat img){
    /*
    @description  : get source image's light pattern 
    @param  : 
        img : source BGR image or Gray image
    @Returns  : 
        pattern : the light pattern
    */
    Mat pattern;
    blur(img, pattern, Size(img.cols / 3, img.cols / 3));
    return pattern;
}

Mat removeLight(Mat img, Mat pattern, int methodLight){
    /*
    @description  : remove light between img and pattern based on method light
    @param  : 
        img : source BGR/Gray image
        pattern : pattern BGR/Gray image
        methodLight : choise options: 0 difference, 1 div
    @Returns  : 
        aux : light removed BGR/Gray image
    */
    Mat aux;
    if(methodLight == 1){
        // div operation in float 32 format CV_32F
        Mat img32, pattern32;
        img.convertTo(img32, 5);
        pattern.convertTo(pattern32, 5);
        aux = 1.0 - (img32 / pattern32);
        // covert to CV_8U and clip
        aux.convertTo(aux, 0, 255);
    }
    else{
        // difference
        aux = pattern - img;
    }
    return aux;
}

void connectedComponents(Mat img_thr){
    /*
    @description  : opencv connnected components
    @param  : 
        img : threshold image
    @Returns  : 
        None
    */
   Mat labels;
   auto num_objs = connectedComponents(img_thr, labels);
   if(num_objs < 2){
       cout << "no object is detected. " << endl;
       return ;
   }
   Mat res = Mat::zeros(img_thr.rows, img_thr.cols, CV_8UC3);
   RNG rng(0xFFFFFFFF);
   for(auto i = 1; i < num_objs; i++){
       Mat mask = labels == i;
       res.setTo(randColor(rng), mask);
   }
//    imshow("result", res);
   myWin -> addImage("result", res);
}

void connectedComponentsWithStats(Mat img_thr){
    /*
    @description  : connnected components with stats
    @param  : 
        img : threshold image
    @Returns  : 
        None
    */
    Mat labels, stats, centroids;
    auto num_objs = connectedComponentsWithStats(img_thr, labels, stats, centroids);
    if(num_objs < 2){
       cout << "no object is detected. " << endl;
       return ;
   }
   Mat res = Mat::zeros(img_thr.rows, img_thr.cols, CV_8UC3);
   RNG rng(0xFFFFFFFF);
   for(auto i = 1; i < num_objs; i++){
       Mat mask = labels == i;
       res.setTo(randColor(rng), mask);
       stringstream ss;
       ss << "area: " << stats.at<int>(i, CC_STAT_AREA);
       // add text info
       putText(res, ss.str(), centroids.at<Point2d>(i), FONT_HERSHEY_SIMPLEX, 0.3, Scalar(0, 255, 0));
   }
   myWin -> addImage("result", res);

}

void findContours(Mat img_thr){
    /*
    @description  : find contours and put text
    @param  : 
        img : threshold image
    @Returns  : 
        None
    */
    vector<vector<Point>> contours;
    findContours(img_thr, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
    if(contours.size() == 0){
        cout << "no contours are found ." << endl;
        return;
    }
    RNG rng(0xFFFFFFFF);
    Mat res = Mat::zeros(img_thr.rows, img_thr.cols, CV_8UC3);
    // calculate moments
	vector<Moments> mu(contours.size());
	for (int i = 0; i < contours.size(); i++)
	{
		mu[i] = moments(contours[i], false);
	}
	// calculate centroids
	vector<Point2f> mc(contours.size());
	for (int i = 0; i < contours.size(); i++)
	{
		mc[i] = Point2d(mu[i].m10 / mu[i].m00, mu[i].m01 / mu[i].m00);
	}
    for(auto i = 0; i < contours.size(); i++){
        drawContours(res, contours, i, randColor(rng));
        putText(res, "*", Point(mc[i].x, mc[i].y), FONT_HERSHEY_SIMPLEX, 0.4, Scalar(255, 0, 255), 1);
    }
    myWin -> addImage("result", res);
}

const char* keys  = {
    "{help h usage ? | | print this message}"
    "{@image | | Image to process}"
    "{@lightPattern | | Image light pattern applied to image}"
    "{lightMethod | 1 | Method to remove image background, 0 diff, 1 div, 2 no light removal}"
    "{segMethod | 2 | Method to segment: 1 connected components, 2 connected components with states, 3 find contours}"
};

int main(int argc, const char** argv){
    CommandLineParser parser(argc, argv, keys);
    if(parser.has("help")){
        parser.printMessage();
        return 0;
    }
    // load source img and light pattern img, set light and segment method
    String imgFile = parser.get<String>(0);
    String lightPat = parser.get<String>(1);
    auto methodLight = parser.get<int>("lightMethod");
    auto methodSeg = parser.get<int>("segMethod");
    
    // check commoand format
    if(!parser.check()){
        parser.printErrors();
        return 0;
    }
    
    // read source image
    Mat img = imread(imgFile, 0);
    if(img.data == NULL){
        cout << "Error load image file . " << endl;
        return 0;
    }

    myWin = make_shared<MWindow>("Main Window", 2, 3, WINDOW_AUTOSIZE);
    // create denoise image and box_smooth img
    Mat img_noise, img_box_smooth;
    medianBlur(img, img_noise, 3);
    blur(img, img_box_smooth, Size(3, 3));

        
    // read light image
    Mat imgLightPat = imread(lightPat, 0);
    if(imgLightPat.data == NULL){
        // use blur with large kernel to get light pattern
        imgLightPat = calLigthPattern(img_noise);   
    }
    medianBlur(imgLightPat, imgLightPat, 3);

    // for image without light pattern 
    Mat img_no_light;
    img_noise.copyTo(img_no_light);
    if(methodLight != 2){
        img_no_light = removeLight(img_noise, imgLightPat, methodLight);
    }

    Mat img_thread;
    if(methodLight != 2){
        threshold(img_no_light, img_thread, 30, 255, THRESH_BINARY);
    }else{
        threshold(img_no_light, img_thread, 140, 255, THRESH_BINARY_INV);
    }

    // show image
    myWin->addImage("Input", img);
    myWin->addImage("Input without noise", img_noise);
    myWin->addImage("light pattern", imgLightPat);
    myWin->addImage("no light", img_no_light);
    myWin->addImage("thread", img_thread);

    // add result image
    switch (methodSeg){
    case 1:
        connectedComponents(img_thread);
        break;
    case 2:
        connectedComponentsWithStats(img_thread);
        break;
    case 3:
        findContours(img_thread);
    default:
        break;
    }
    
    myWin->render();
    waitKey(0);
    return 0;
}

结果展示:

./main -h 	#命令行查看使用方法

请添加图片描述

./main ../data/test.pgm  -lightMethod=2 -segMethod=2 # 无需背景图片输入

请添加图片描述

./main ../data/test.pgm ../data/light.pgm -lightMethod=2 -segMethod=2 # 一张原始图像和一张背景图像进行分割

请添加图片描述

./main ../data/test.pgm ../data/light.pgm -lightMethod=0 -segMethod=3 # 根据原始图像和背景图像进行轮廓和质心提取

请添加图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值