织布缺陷——断针缺陷检测

本文针对织布生产过程中,由于断针造成的织布缺陷图像,进行检测,如下图1所示.

    

1. 图像质量增强

  由于织布原图的图像整体偏暗,不利于缺陷部分的检测,考虑改善图像质量,而图像质量增强的算法有很多,本文借鉴Retinex的图像增强算法(SSR, MSR, MSRCR)的实现,效果如图2所示.

织布缺陷——断针缺陷检测_第1张图片

 

 

参考:https://blog.csdn.net/ajianyingxiaoqinghan/article/details/71435098

2. 低通滤波/边缘检测

 观测图像2中增强后的图像,虽然织布瑕疵部分,相对正常区域有明显的细长暗条纹痕迹,但是如果直接使用边缘检测的方式,来定位瑕疵部分的边缘,很难找到明显的边界条纹(受织布正常纹理部分的影响,如图3的对比效果图所示).

 这里用低通滤波器,实现对织布纹理背景的过滤,以提供织布瑕疵缺陷的高频细节部分,利于后续的边缘检测效果.

织布缺陷——断针缺陷检测_第2张图片

   低通滤波算法,参考:https://blog.csdn.net/weixin_40647819/article/details/80600918?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.channel_param

  边缘检测算法,参考:https://blog.csdn.net/dieju8330/article/details/82814529?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param

3. 形态学处理

对边缘检测后的图像,进行形态学的膨胀处理,就可以增大缺陷区域的轮廓,以便于利用opencv中找轮廓的方式,定位瑕疵部分的位置.

织布缺陷——断针缺陷检测_第3张图片

4. 定位瑕疵区域

     对形态学处理后的图像,利用opencv中的findContours()函数,通过轮廓的宽高、面积大小等条件设置,来定位织布的瑕疵轮廓区域,具体实现参见代码部分:

5. 实现代码部分

MSRCR.h

#pragma once

#ifndef _MSRCR_H_
#define _MSRCR_H_

#include "cv.h"
#include "highgui.h"
#include "opencv2\opencv.hpp"
#include 

// 该处使用USE_EXACT_SIGMA,则使用自定义的滤波算法;
// 不使用USE_EXACT_SIGMA,则使用OpenCV自带的高斯滤波算法;
//#define USE_EXACT_SIGMA

using namespace cv;
using namespace std;

class Msrcr
{
private:
#define pc(image, x, y, c) image->imageData[(image->widthStep * y) + (image->nChannels * x) + c]
#define INT_PREC 1024.0
#define INT_PREC_BITS 10
    inline double int2double(int x) { return (double)x / INT_PREC; }
    inline int double2int(double x) { return (int)(x * INT_PREC + 0.5); }
    inline int int2smallint(int x) { return (x >> INT_PREC_BITS); }
    inline int int2bigint(int x) { return (x << INT_PREC_BITS); }
public:
    vector<double> CreateKernel(double sigma);
    vector<int> CreateFastKernel(double sigma);
    void FilterGaussian(IplImage* img, double sigma);
    void FilterGaussian(Mat src, Mat &dst, double sigma);
    void FastFilter(IplImage *img, double sigma);
    void FastFilter(Mat src, Mat &dst, double sigma);
    void Retinex(IplImage *img, double sigma, int gain = 128, int offset = 128);
    void Retinex(Mat src, Mat &dst, double sigma, int gain = 128, int offset = 128);
    void MultiScaleRetinex(IplImage *img, vector<double> weights, vector<double> sigmas, int gain = 128, int offset = 128);
    void MultiScaleRetinex(Mat src, Mat &dst, vector<double> weights, vector<double> sigmas, int gain = 128, int offset = 128);
    void MultiScaleRetinexCR(IplImage *img, vector<double> weights, vector<double> sigmas, int gain = 128, int offset = 128,
        double restoration_factor = 6, double color_gain = 2);
    void MultiScaleRetinexCR(Mat src, Mat &dst, vector<double> weights, vector<double> sigmas, int gain = 128, int offset = 128,
        double restoration_factor = 6, double color_gain = 2);
};

#endif
View Code

 

MSRCR.cpp

#include "MSRCR.h"

/*===========================================================
* 函数: CreateKernel
* 说明:创建一个标准化的一维高斯核;
* 参数:
*   double sigma: 高斯核标准偏差
* 返回值:
*   double*: 长度为((6*sigma)/2) * 2 + 1的double数组
* 注:
*   调用者需要删除该内核;
* --------------------------------------------------------
*  Summary:
*  Creates a normalized 1 dimensional gaussian kernel.
*
*  Arguments:
*  double sigma - the standard deviation of the gaussian kernel.
*
*  Returns:
*  vector - an vector of values of length ((6*sigma)/2) * 2 + 1.
*  Note:
*  Caller is responsable for deleting the kernel.
=============================================================
*/
vector<double> Msrcr::CreateKernel(double sigma)
{
    int i, x, filter_size;
    vector<double> filter;
    double sum;

    // set sigma's upline
    // 为sigma设定上限
    if (sigma > 300) sigma = 300;

    // get needed filter size (enforce oddness)
    // 获取需要的滤波尺寸,且强制为奇数;
    filter_size = (int)floor(sigma * 6) / 2;
    filter_size = filter_size * 2 + 1;

    // Calculate exponential
    // 计算指数
    sum = 0;
    for (i = 0; i < filter_size; i++)
    {
        double tmpValue;
        x = i - (filter_size / 2);
        tmpValue = exp(-(x*x) / (2 * sigma*sigma));
        filter.push_back(tmpValue);

        sum += tmpValue;
    }

    // Normalize
    // 归一化计算
    for (i = 0, x; i < filter_size; i++)
        filter[i] /= sum;

    return filter;
}

/*===========================================================
* 函数: CreateFastKernel
* 说明:创建一个近似浮点的整数类型(左移8bits)的快速高斯核;
* 参数:
*   double sigma: 高斯核标准偏差
* 返回值:
*   double*: 长度为((6*sigma)/2) * 2 + 1的int数组
* 注:
*   调用者需要删除该内核;
* --------------------------------------------------------
* Summary:
* Creates a faster gaussian kernal using integers that
* approximate floating point (leftshifted by 8 bits)
*
* Arguments:
* double sigma - the standard deviation of the gaussian kernel.
*
* Returns:
* vector - an vector of values of length ((6*sigma)/2) * 2 + 1.
*
* Note:
* Caller is responsable for deleting the kernel.
=============================================================
*/
vector<int> Msrcr::CreateFastKernel(double sigma)
{
    vector<double> fp_kernel;
    vector<int> kernel;
    int i, filter_size;

    // Reject unreasonable demands
    // 设置上限
    if (sigma > 300) sigma = 300;

    // get needed filter size (enforce oddness)
    // 获取需要的滤波尺寸,且强制为奇数;
    filter_size = (int)floor(sigma * 6) / 2;
    filter_size = filter_size * 2 + 1;

    // Create Kernel
    // 创建内核
    fp_kernel = CreateKernel(sigma);

    // Change kernel's data type from double to int
    // double内核转为int型
    for (i = 0; i < filter_size; i++)
    {
        int tmpValue;
        tmpValue = double2int(fp_kernel[i]);
        kernel.push_back(tmpValue);
    }

    return kernel;
}

/*===========================================================
* 函数:FilterGaussian
* 说明:通过内核计算高斯卷积,内核由sigma值得到,且在内核两端值相等;
* 参数:
*   img: 被滤波的IplImage*类型图像
*   double sigma: 高斯核标准偏差
* --------------------------------------------------------
* Summary:
* Performs a gaussian convolution for a value of sigma that is equal
* in both directions.
*
* Arguments:
* IplImage* img - the image to be filtered in place.
* double sigma - the standard deviation of the gaussian kernel to use.
=============================================================
*/
void Msrcr::FilterGaussian(IplImage* img, double sigma)
{
    int i, j, k, source, filter_size;
    vector<int> kernel;
    IplImage* temp;
    int v1, v2, v3;

    // Reject unreasonable demands
    // 设置上限
    if (sigma > 300) sigma = 300;

    // get needed filter size (enforce oddness)
    // 获取需要的滤波尺寸,且强制为奇数;
    filter_size = (int)floor(sigma * 6) / 2;
    filter_size = filter_size * 2 + 1;

    // Create Kernel
    // 创建内核
    kernel = CreateFastKernel(sigma);

    temp = cvCreateImage(cvSize(img->width, img->height), img->depth, img->nChannels);

    // filter x axis
    // X轴滤波
    for (j = 0; j < temp->height; j++)
    {
        for (i = 0; i < temp->width; i++)
        {
            // inner loop has been unrolled
            // 内层循环已经展开
            v1 = v2 = v3 = 0;
            for (k = 0; k < filter_size; k++)
            {
                source = i + filter_size / 2 - k;

                if (source < 0) source *= -1;
                if (source > img->width - 1) source = 2 * (img->width - 1) - source;

                v1 += kernel[k] * (unsigned char)pc(img, source, j, 0);
                if (img->nChannels == 1) continue;
                v2 += kernel[k] * (unsigned char)pc(img, source, j, 1);
                v3 += kernel[k] * (unsigned char)pc(img, source, j, 2);
            }

            // set value and move on
            pc(temp, i, j, 0) = (char)int2smallint(v1);
            if (img->nChannels == 1) continue;
            pc(temp, i, j, 1) = (char)int2smallint(v2);
            pc(temp, i, j, 2) = (char)int2smallint(v3);

        }
    }

    // filter y axis
    // Y轴滤波
    for (j = 0; j < img->height; j++)
    {
        for (i = 0; i < img->width; i++)
        {
            v1 = v2 = v3 = 0;
            for (k = 0; k < filter_size; k++)
            {
                source = j + filter_size / 2 - k;

                if (source < 0) source *= -1;
                if (source > temp->height - 1) source = 2 * (temp->height - 1) - source;

                v1 += kernel[k] * (unsigned char)pc(temp, i, source, 0);
                if (img->nChannels == 1) continue;
                v2 += kernel[k] * (unsigned char)pc(temp, i, source, 1);
                v3 += kernel[k] * (unsigned char)pc(temp, i, source, 2);
            }

            // set value and move on
            pc(img, i, j, 0) = (char)int2smallint(v1);
            if (img->nChannels == 1) continue;
            pc(img, i, j, 1) = (char)int2smallint(v2);
            pc(img, i, j, 2) = (char)int2smallint(v3);

        }
    }

    cvReleaseImage(&temp);
}

/*===========================================================
* 函数:FilterGaussian
* 说明:通过内核计算高斯卷积,内核由sigma值得到,且在内核两端值相等;
* 参数:
*   Mat src: 输入图像
*   Mat &dst: 输出图像
*   double sigma: 高斯核标准偏差
* --------------------------------------------------------
* Summary:
* Performs a gaussian convolution for a value of sigma that is equal
* in both directions.
*
* Arguments:
* Mat src - Input Image.
* Mat &dst - Output Image.
* double sigma - the standard deviation of the gaussian kernel to use.
=============================================================
*/
void Msrcr::FilterGaussian(Mat src, Mat &dst, double sigma)
{
    IplImage tmp_ipl;
    tmp_ipl = IplImage(src);
    FilterGaussian(&tmp_ipl, sigma);
    dst = cvarrToMat(&tmp_ipl);

    //dst = Mat(&tmp_ipl);
}

/*===========================================================
* 函数:FastFilter
* 说明:给出任意大小的sigma值,都可以通过使用图像金字塔与可分离滤波器计算高斯卷积;
* 参数:
*   IplImage *img: 被滤波的图像
*   double sigma: 高斯核标准偏差
* --------------------------------------------------------
* Summary:
* Performs gaussian convolution of any size sigma very fast by using
* both image pyramids and seperable filters.  Recursion is used.
*
* Arguments:
* img - an IplImage to be filtered in place.
* double sigma - the standard deviation of the gaussian kernel to use.
=============================================================
*/
void Msrcr::FastFilter(IplImage *img, double sigma)
{
    int filter_size;

    // Reject unreasonable demands
    // 设置上限
    if (sigma > 300) sigma = 300;

    // get needed filter size (enforce oddness)
    // 获取需要的滤波尺寸,且强制为奇数;
    filter_size = (int)floor(sigma * 6) / 2;
    filter_size = filter_size * 2 + 1;

    // If 3 sigma is less than a pixel, why bother (ie sigma < 2/3)
    // 如果3 * sigma小于一个像素,则直接退出
    if (filter_size < 3) return;

    // Filter, or downsample and recurse
    // 处理方式:(1) 滤波  (2) 高斯光滑处理  (3) 递归处理滤波器大小
    if (filter_size < 10) {

#ifdef USE_EXACT_SIGMA
        FilterGaussian(img, sigma);
#else
        cvSmooth(img, img, CV_GAUSSIAN, filter_size, filter_size);
#endif

    }
    else
    {
        if (img->width < 2 || img->height < 2) return;
        IplImage* sub_img = cvCreateImage(cvSize(img->width / 2, img->height / 2), img->depth, img->nChannels);
        cvPyrDown(img, sub_img);
        FastFilter(sub_img, sigma / 2.0);
        cvResize(sub_img, img, CV_INTER_LINEAR);
        cvReleaseImage(&sub_img);
    }
}

/*===========================================================
* 函数:FastFilter
* 说明:给出任意大小的sigma值,都可以通过使用图像金字塔与可分离滤波器计算高斯卷积;
* 参数:
*   Mat src: 输入图像
*   Mat &dst: 输出图像
*   double sigma: 高斯核标准偏差
* --------------------------------------------------------
* Summary:
* Performs gaussian convolution of any size sigma very fast by using
* both image pyramids and seperable filters.  Recursion is used.
*
* Arguments:
* Mat src - Input Image.
* Mat &dst - Output Image.
* double sigma - the standard deviation of the gaussian kernel to use.
=============================================================
*/
void Msrcr::FastFilter(Mat src, Mat &dst, double sigma)
{
    IplImage tmp_ipl;
    tmp_ipl = IplImage(src);
    FastFilter(&tmp_ipl, sigma);
    dst = cvarrToMat(&tmp_ipl);
    //dst = Mat(&tmp_ipl);
}

/*===========================================================
* 函数:Retinex
* 说明:单通道SSR方法,基础Retinex复原算法。原图像和被滤波的图像需要被转换到
*   对数域,并做减运算;
* 参数:
*   IplImage *img: 被滤波的图像
*   double sigma: 高斯核标准偏差
*   int gain: 图像像素值改变范围的增益
*   int offset: 图像像素值改变范围的偏移量
* --------------------------------------------------------
* Summary:
* Basic retinex restoration. The image and a filtered image are converted
* to the log domain and subtracted.
*
* Arguments:
* img - an IplImage to be enhanced in place.
* sigma - the standard deviation of the gaussian kernal used to filter.
* gain - the factor by which to scale the image back into visable range.
* offset - an offset similar to the gain.
=============================================================
*/
void Msrcr::Retinex(IplImage *img, double sigma, int gain, int offset)
{
    IplImage *A, *fA, *fB, *fC;

    // Initialize temp images
    // 初始化缓存图像
    fA = cvCreateImage(cvSize(img->width, img->height), IPL_DEPTH_32F, img->nChannels);
    fB = cvCreateImage(cvSize(img->width, img->height), IPL_DEPTH_32F, img->nChannels);
    fC = cvCreateImage(cvSize(img->width, img->height), IPL_DEPTH_32F, img->nChannels);

    // Compute log image
    // 计算对数图像
    cvConvert(img, fA);
    cvLog(fA, fB);

    // Compute log of blured image
    // 计算滤波后模糊图像的对数图像
    A = cvCloneImage(img);
    FastFilter(A, sigma);
    cvConvert(A, fA);
    cvLog(fA, fC);

    // Compute difference
    // 计算两图像之差
    cvSub(fB, fC, fA);

    // Restore
    // 恢复图像
    cvConvertScale(fA, img, gain, offset);

    // Release temp images
    // 释放缓存图像
    cvReleaseImage(&A);
    cvReleaseImage(&fA);
    cvReleaseImage(&fB);
    cvReleaseImage(&fC);

}

/*===========================================================
* 函数:Retinex
* 说明:单通道SSR方法,基础Retinex复原算法。原图像和被滤波的图像需要被转换到
*   对数域,并做减运算;
* 参数:
*   Mat src: 输入图像
*   Mat &dst: 输出图像
*   double sigma: 高斯核标准偏差
*   int gain: 图像像素值改变范围的增益
*   int offset: 图像像素值改变范围的偏移量
* --------------------------------------------------------
* Summary:
* Basic retinex restoration. The image and a filtered image are converted
* to the log domain and subtracted.
*
* Arguments:
* Mat src - Input Image.
* Mat &dst - Output Image.
* sigma - the standard deviation of the gaussian kernal used to filter.
* gain - the factor by which to scale the image back into visable range.
* offset - an offset similar to the gain.
=============================================================
*/
void Msrcr::Retinex(Mat src, Mat &dst, double sigma, int gain, int offset)
{
    IplImage tmp_ipl;
    tmp_ipl = IplImage(src);
    Retinex(&tmp_ipl, sigma, gain, offset);

    dst = cvarrToMat(&tmp_ipl);
    //dst = Mat(&tmp_ipl);
}

/*===========================================================
* 函数:MultiScaleRetinex
* 说明:多通道MSR算法。原图像和一系列被滤波的图像转换到对数域,并与带权重的原图像做减运算。
* 通常情况下,三个权重范围选择低、中、高标准偏差;
*
* 参数:
*   IplImage *img: 被滤波的图像
*   vector weights: 通道权重
*   vector sigmas: 高斯核标准偏差
*   int gain: 图像像素值改变范围的增益
*   int offset: 图像像素值改变范围的偏移量
* --------------------------------------------------------
* Summary:
* Multiscale retinex restoration.  The image and a set of filtered images are
* converted to the log domain and subtracted from the original with some set
* of weights. Typicaly called with three equaly weighted scales of fine,
* medium and wide standard deviations.
*
* Arguments:
* IplImage* img - an IplImage to be enhanced in place.
* vector weights - Weights of channels
* vector sigma - the standard deviation of the gaussian kernal used to filter.
* int gain - the factor by which to scale the image back into visable range.
* int offset - an offset similar to the gain.
=============================================================
*/
void Msrcr::MultiScaleRetinex(IplImage *img, vector<double> weights, vector<double> sigmas, int gain, int offset)
{
    int i;
    double weight;
    int scales = sigmas.size();
    IplImage *A, *fA, *fB, *fC;

    // Initialize temp images
    fA = cvCreateImage(cvSize(img->width, img->height), IPL_DEPTH_32F, img->nChannels);
    fB = cvCreateImage(cvSize(img->width, img->height), IPL_DEPTH_32F, img->nChannels);
    fC = cvCreateImage(cvSize(img->width, img->height), IPL_DEPTH_32F, img->nChannels);


    // Compute log image
    cvConvert(img, fA);
    cvLog(fA, fB);

    // Normalize according to given weights
    for (i = 0, weight = 0; i < scales; i++)
        weight += weights[i];

    if (weight != 1.0) cvScale(fB, fB, weight);

    // Filter at each scale
    for (i = 0; i < scales; i++)
    {
        A = cvCloneImage(img);
        double tmp = sigmas[i];
        FastFilter(A, tmp);

        cvConvert(A, fA);
        cvLog(fA, fC);
        cvReleaseImage(&A);

        // Compute weighted difference
        cvScale(fC, fC, weights[i]);
        cvSub(fB, fC, fB);
    }

    // Restore
    cvConvertScale(fB, img, gain, offset);

    // Release temp images
    cvReleaseImage(&fA);
    cvReleaseImage(&fB);
    cvReleaseImage(&fC);
}

/*===========================================================
* 函数:MultiScaleRetinex
* 说明:多通道MSR算法。原图像和一系列被滤波的图像转换到对数域,并与带权重的原图像做减运算。
* 通常情况下,三个权重范围选择低、中、高标准偏差;
*
* 参数:
*   Mat src: 输入图像
*   Mat &dst: 输出图像
*   vector weights: 通道权重
*   vector sigmas: 高斯核标准偏差
*   int gain: 图像像素值改变范围的增益
*   int offset: 图像像素值改变范围的偏移量
* --------------------------------------------------------
* Summary:
* Multiscale retinex restoration.  The image and a set of filtered images are
* converted to the log domain and subtracted from the original with some set
* of weights. Typicaly called with three equaly weighted scales of fine,
* medium and wide standard deviations.
*
* Arguments:
* Mat src - Input Image.
* Mat &dst - Output Image.
* vector weights - Weights of channels
* vector sigmas - the standard deviation of the gaussian kernal used to filter.
* int gain - the factor by which to scale the image back into visable range.
* int offset - an offset similar to the gain.
=============================================================
*/
void Msrcr::MultiScaleRetinex(Mat src, Mat &dst, vector<double> weights, vector<double> sigmas, int gain, int offset)
{
    IplImage tmp_ipl;
    tmp_ipl = IplImage(src);
    MultiScaleRetinex(&tmp_ipl, weights, sigmas, gain, offset);

    dst = cvarrToMat(&tmp_ipl);

    //dst = Mat(&tmp_ipl);
}

/*===========================================================
* 函数:MultiScaleRetinexCR
* 说明:MSRCR算法,MSR算法加上颜色修复。原图像和一系列被滤波的图像转换到对数域,并与带权重的原图像做减运算。
* 通常情况下,三个权重范围选择低、中、高标准偏差;之后,颜色修复权重应用于每个颜色通道中;
*
* 参数:
*   IplImage *img: 被滤波的图像
*   double sigma: 高斯核标准偏差
*   int gain: 图像像素值改变范围的增益
*   int offset: 图像像素值改变范围的偏移量
*   double restoration_factor: 控制颜色修复的非线性
*   double color_gain: 控制颜色修复增益
* --------------------------------------------------------
* Summary:
* Multiscale retinex restoration with color restoration.  The image and a set of
* filtered images are converted to the log domain and subtracted from the
* original with some set of weights. Typicaly called with three equaly weighted
* scales of fine, medium and wide standard deviations. A color restoration weight
* is then applied to each color channel.
*
* Arguments:
* IplImage *img - an IplImage to be enhanced in place.
* double sigma - the standard deviation of the gaussian kernal used to filter.
* int gain - the factor by which to scale the image back into visable range.
* int offset - an offset similar to the gain.
* double restoration_factor - controls the non-linearaty of the color restoration.
* double color_gain - controls the color restoration gain.
=============================================================
*/
void Msrcr::MultiScaleRetinexCR(IplImage *img, vector<double> weights, vector<double> sigmas,
    int gain, int offset, double restoration_factor, double color_gain)
{
    int i;
    double weight;
    int scales = sigmas.size();
    IplImage *A, *B, *C, *fA, *fB, *fC, *fsA, *fsB, *fsC, *fsD, *fsE, *fsF;

    // Initialize temp images
    // 初始化缓存图像
    fA = cvCreateImage(cvSize(img->width, img->height), IPL_DEPTH_32F, img->nChannels);
    fB = cvCreateImage(cvSize(img->width, img->height), IPL_DEPTH_32F, img->nChannels);
    fC = cvCreateImage(cvSize(img->width, img->height), IPL_DEPTH_32F, img->nChannels);
    fsA = cvCreateImage(cvSize(img->width, img->height), IPL_DEPTH_32F, 1);
    fsB = cvCreateImage(cvSize(img->width, img->height), IPL_DEPTH_32F, 1);
    fsC = cvCreateImage(cvSize(img->width, img->height), IPL_DEPTH_32F, 1);
    fsD = cvCreateImage(cvSize(img->width, img->height), IPL_DEPTH_32F, 1);
    fsE = cvCreateImage(cvSize(img->width, img->height), IPL_DEPTH_32F, 1);
    fsF = cvCreateImage(cvSize(img->width, img->height), IPL_DEPTH_32F, 1);

    // Compute log image
    // 计算对数图像
    cvConvert(img, fB);
    cvLog(fB, fA);

    // Normalize according to given weights
    // 依照权重归一化
    for (i = 0, weight = 0; i < scales; i++)
        weight += weights[i];

    if (weight != 1.0) cvScale(fA, fA, weight);

    // Filter at each scale
    // 各尺度上进行滤波操作
    for (i = 0; i < scales; i++) {
        A = cvCloneImage(img);
        FastFilter(A, sigmas[i]);

        cvConvert(A, fB);
        cvLog(fB, fC);
        cvReleaseImage(&A);

        // Compute weighted difference
        // 计算权重后两图像之差
        cvScale(fC, fC, weights[i]);
        cvSub(fA, fC, fA);
    }

    // Color restoration
    // 颜色修复
    if (img->nChannels > 1) {
        A = cvCreateImage(cvSize(img->width, img->height), img->depth, 1);
        B = cvCreateImage(cvSize(img->width, img->height), img->depth, 1);
        C = cvCreateImage(cvSize(img->width, img->height), img->depth, 1);

        // Divide image into channels, convert and store sum
        // 将图像分割为若干通道,类型转换为浮点型,并存储通道数据之和
        cvSplit(img, A, B, C, NULL);
        cvConvert(A, fsA);
        cvConvert(B, fsB);
        cvConvert(C, fsC);

        cvReleaseImage(&A);
        cvReleaseImage(&B);
        cvReleaseImage(&C);

        // Sum components
        // 求和
        cvAdd(fsA, fsB, fsD);
        cvAdd(fsD, fsC, fsD);

        // Normalize weights
        // 带权重矩阵归一化
        cvDiv(fsA, fsD, fsA, restoration_factor);
        cvDiv(fsB, fsD, fsB, restoration_factor);
        cvDiv(fsC, fsD, fsC, restoration_factor);

        cvConvertScale(fsA, fsA, 1, 1);
        cvConvertScale(fsB, fsB, 1, 1);
        cvConvertScale(fsC, fsC, 1, 1);

        // Log weights
        // 带权重矩阵求对数
        cvLog(fsA, fsA);
        cvLog(fsB, fsB);
        cvLog(fsC, fsC);

        // Divide retinex image, weight accordingly and recombine
        // 将Retinex图像切分为三个数组,按照权重和颜色增益重新组合
        cvSplit(fA, fsD, fsE, fsF, NULL);

        cvMul(fsD, fsA, fsD, color_gain);
        cvMul(fsE, fsB, fsE, color_gain);
        cvMul(fsF, fsC, fsF, color_gain);

        cvMerge(fsD, fsE, fsF, NULL, fA);
    }

    // Restore
    // 恢复图像
    cvConvertScale(fA, img, gain, offset);

    // Release temp images
    // 释放缓存图像
    cvReleaseImage(&fA);
    cvReleaseImage(&fB);
    cvReleaseImage(&fC);
    cvReleaseImage(&fsA);
    cvReleaseImage(&fsB);
    cvReleaseImage(&fsC);
    cvReleaseImage(&fsD);
    cvReleaseImage(&fsE);
    cvReleaseImage(&fsF);
}

/*===========================================================
* 函数:MultiScaleRetinexCR
* 说明:MSRCR算法,MSR算法加上颜色修复。原图像和一系列被滤波的图像转换到对数域,并与带权重的原图像做减运算。
* 通常情况下,三个权重范围选择低、中、高标准偏差;之后,颜色修复权重应用于每个颜色通道中;
*
* 参数:
*   Mat src: 输入图像
*   Mat &dst: 输出图像
*   double sigma: 高斯核标准偏差
*   int gain: 图像像素值改变范围的增益
*   int offset: 图像像素值改变范围的偏移量
*   double restoration_factor: 控制颜色修复的非线性
*   double color_gain: 控制颜色修复增益
* --------------------------------------------------------
* Summary:
* Multiscale retinex restoration with color restoration.  The image and a set of
* filtered images are converted to the log domain and subtracted from the
* original with some set of weights. Typicaly called with three equaly weighted
* scales of fine, medium and wide standard deviations. A color restoration weight
* is then applied to each color channel.
*
* Arguments:
* Mat src - Input Image.
* Mat &dst - Output Image.
* double sigma - the standard deviation of the gaussian kernal used to filter.
* int gain - the factor by which to scale the image back into visable range.
* int offset - an offset similar to the gain.
* double restoration_factor - controls the non-linearaty of the color restoration.
* double color_gain - controls the color restoration gain.
=============================================================
*/
void Msrcr::MultiScaleRetinexCR(Mat src, Mat &dst, vector<double> weights, vector<double> sigmas,
    int gain, int offset, double restoration_factor, double color_gain)
{
    IplImage tmp_ipl;
    tmp_ipl = IplImage(src);
    MultiScaleRetinexCR(&tmp_ipl, weights, sigmas, gain, offset, restoration_factor, color_gain);

    dst = cvarrToMat(&tmp_ipl);

    //dst = Mat(&tmp_ipl);
}
View Code

 

 Main.cpp

#include "cv.h"
#include "highgui.h"
#include "opencv2\opencv.hpp"

#include "MSRCR.h"

using namespace cv;
using namespace std;

//*****************高斯低通滤波器***********************
Mat gaussianlbrf(Mat scr, float sigma)
{
    Mat gaussianBlur(scr.size(), CV_32FC1); //,CV_32FC1
    float d0 = 2 * sigma*sigma;//高斯函数参数,越小,频率高斯滤波器越窄,滤除高频成分越多,图像就越平滑
    for (int i = 0; i)
    {
        for (int j = 0; j)
        {
            float d = pow(float(i - scr.rows / 2), 2) + pow(float(j - scr.cols / 2), 2);//分子,计算pow必须为float型
            gaussianBlur.at<float>(i, j) = expf(-d / d0);//expf为以e为底求幂(必须为float型)
        }
    }
    //imshow("高斯低通滤波器", gaussianBlur);
    return gaussianBlur;
}

//*****************频率域滤波*******************
Mat freqfilt(Mat scr, Mat blur)
{
    //***********************DFT*******************
    Mat plane[] = { scr, Mat::zeros(scr.size() , CV_32FC1) }; //创建通道,存储dft后的实部与虚部(CV_32F,必须为单通道数)
    Mat complexIm;
    merge(plane, 2, complexIm);//合并通道 (把两个矩阵合并为一个2通道的Mat类容器)
    dft(complexIm, complexIm);//进行傅立叶变换,结果保存在自身  

                              //***************中心化********************
    split(complexIm, plane);//分离通道(数组分离)

    int cx = plane[0].cols / 2; int cy = plane[0].rows / 2;//以下的操作是移动图像  (零频移到中心)
    Mat part1_r(plane[0], Rect(0, 0, cx, cy));  //元素坐标表示为(cx,cy)
    Mat part2_r(plane[0], Rect(cx, 0, cx, cy));
    Mat part3_r(plane[0], Rect(0, cy, cx, cy));
    Mat part4_r(plane[0], Rect(cx, cy, cx, cy));

    Mat temp;
    part1_r.copyTo(temp);  //左上与右下交换位置(实部)
    part4_r.copyTo(part1_r);
    temp.copyTo(part4_r);

    part2_r.copyTo(temp);  //右上与左下交换位置(实部)
    part3_r.copyTo(part2_r);
    temp.copyTo(part3_r);

    Mat part1_i(plane[1], Rect(0, 0, cx, cy));  //元素坐标(cx,cy)
    Mat part2_i(plane[1], Rect(cx, 0, cx, cy));
    Mat part3_i(plane[1], Rect(0, cy, cx, cy));
    Mat part4_i(plane[1], Rect(cx, cy, cx, cy));

    part1_i.copyTo(temp);  //左上与右下交换位置(虚部)
    part4_i.copyTo(part1_i);
    temp.copyTo(part4_i);

    part2_i.copyTo(temp);  //右上与左下交换位置(虚部)
    part3_i.copyTo(part2_i);
    temp.copyTo(part3_i);

    //*****************滤波器函数与DFT结果的乘积****************
    Mat blur_r, blur_i, BLUR;
    multiply(plane[0], blur, blur_r); //滤波(实部与滤波器模板对应元素相乘)
    multiply(plane[1], blur, blur_i);//滤波(虚部与滤波器模板对应元素相乘)
    Mat plane1[] = { blur_r, blur_i };
    merge(plane1, 2, BLUR);//实部与虚部合并

                           //*********************得到原图频谱图***********************************
    magnitude(plane[0], plane[1], plane[0]);//获取幅度图像,0通道为实部通道,1为虚部,因为二维傅立叶变换结果是复数  
    plane[0] += Scalar::all(1);  //傅立叶变o换后的图片不好分析,进行对数处理,结果比较好看 
    log(plane[0], plane[0]);    // float型的灰度空间为[0,1])
    normalize(plane[0], plane[0], 1, 0, CV_MINMAX);  //归一化便于显示
    //imshow("原图像频谱图", plane[0]);


    //******************IDFT*******************************
    /*
    Mat part111(BLUR,Rect(0,0,cx,cy));  //元素坐标(cx,cy)
    Mat part222(BLUR,Rect(cx,0,cx,cy));
    Mat part333(BLUR,Rect(0,cy,cx,cy));
    Mat part444(BLUR,Rect(cx,cy,cx,cy));
    part111.copyTo(temp);  //左上与右下交换位置(虚部)
    part444.copyTo(part111);
    temp.copyTo(part444);

    part222.copyTo(temp);  //右上与左下交换位置
    part333.copyTo(part222);
    temp.copyTo(part333);
    */

    idft(BLUR, BLUR);    //idft结果也为复数
    split(BLUR, plane);//分离通道,主要获取通道
    magnitude(plane[0], plane[1], plane[0]);  //求幅值(模)
    normalize(plane[0], plane[0], 1, 0, CV_MINMAX);  //归一化便于显示
    return plane[0];//返回参数
}

void img_processing(Msrcr &msrcr, Mat &img, Mat &outImg, vector<double> sigema, vector<double> weight)
{
    
    int edge = 10;
    cv::Rect ROI(edge, edge, img.cols - edge, img.rows - edge);

    // ************************1. 图像增强
    Mat img_msrcr;
    msrcr.MultiScaleRetinexCR(img, img_msrcr, weight, sigema, 128, 128);

    // ********************** 2. 低通滤波图
    cv::Mat img_gray;
    cv::cvtColor(img_msrcr, img_gray, cv::COLOR_BGR2GRAY);
    
    int w = getOptimalDFTSize(img_gray.cols);  //获取进行dtf的最优尺寸
    int h = getOptimalDFTSize(img_gray.rows); //获取进行dtf的最优尺寸

    Mat padded;
    copyMakeBorder(img_gray, padded, 0, h - img_gray.rows, 0, w - img_gray.cols, BORDER_CONSTANT, Scalar::all(0));  //边界填充
    padded.convertTo(padded, CV_32FC1); //将图像转换为flaot型

    Mat gaussianKernel = gaussianlbrf(padded, 50);//高斯低通滤波器
    Mat out = freqfilt(padded, gaussianKernel);//频率域滤波

    Mat imageF_8UC3;
    out.convertTo(imageF_8UC3, CV_8UC3, 255);


    // *********************3. Canny边缘检测
    Mat img_gaussian, img_canny;
    GaussianBlur(imageF_8UC3, img_gaussian, Size(5, 5), 0, 0, BORDER_DEFAULT); //高斯滤波

    int edgeThresh = 15;
    Canny(img_gaussian, img_canny, edgeThresh, edgeThresh*4, 3); //Canny检测

    cv::Mat img_canny_cut = cv::Mat(img_canny, ROI);
    cv::Mat img_canny_roi = img_canny_cut.clone();


    // ***********************4. 形态学处理
    Mat element, img_dilate;
    element = getStructuringElement(MORPH_RECT, Size(5, 5));
    dilate(img_canny_roi, img_dilate, element, Point(-1, -1), 2, BORDER_CONSTANT);

    Mat img_bin;
    img_bin = img_dilate.clone();
    std::vector > contours;
    findContours(img_bin, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE); //找轮廓,注意第5个参数为CV_RETR_EXTERNAL,只检索外框  

    if (contours.size() <= 0 || contours.size() > 999999)
    {
        printf("obj_segmentSecond 函数:coutours的大小超出限制...");
        return;
    }

    int width_less = 0.2*w;

    double least_area = 1000;

    std::vector boundRect(contours.size());    // 绘制最小外接矩形集合
    std::vector box(contours.size());    // 定义最小外接矩形集合

    Mat img_copy;
    img_copy = img.clone();

    cv::Mat img_copy_cut = cv::Mat(img_copy, ROI);
    cv::Mat img_copy_roi = img_copy_cut.clone();
    
    for (int i = 0; i < contours.size(); i++) 
    {
        double area = contourArea(contours[i]);

        if (area < least_area)
            continue;

        box[i] = cv::minAreaRect(cv::Mat(contours[i])); // 计算每个轮廓的最小外接矩形
        boundRect[i] = cv::boundingRect(cv::Mat(contours[i]));

        cv::Point2f rect[4];
        box[i].points(rect);  // 把最小外接矩形四个端点复制给rect数组

        //// 小于分块的边界长度

        // 确定竖_方向织布缺陷
        if (boundRect[i].width < boundRect[i].height) 
        {

            int box_X = boundRect[i].x;
            int box_Y = boundRect[i].y;

            // 判断缺陷窗口的坐标X是否在边界禁止范围内
            if (box_X > img_copy_roi.cols - 50 || box_Y >img_copy_roi.rows - 15)
            {
                printf("坐标X在边界禁止范围内... \n");
                continue;
            }

            cv::circle(img_copy_roi, cv::Point(box[i].center.x, box[i].center.y),
                5, cv::Scalar(0, 255, 0), -1, 8);   // 绘制最小外接矩形的中心点
            /*cv::rectangle(img_copy, cv::Point(boundRect[i].x, boundRect[i].y),
            cv::Point(boundRect[i].x + boundRect[i].width, boundRect[i].y + boundRect[i].height), cv::Scalar(0, 255, 0), 2, 8);
            */

            for (int j = 0; j<4; j++)
            {
                cv::line(img_copy_roi, rect[j], rect[(j + 1) % 4], cv::Scalar(255), 2, 8);  //绘制最小外接矩形每条边
                cv::line(img_copy_roi, rect[j], rect[(j + 1) % 4], cv::Scalar(0, 255, 0), 2, 8); //绘制最小外接矩形每条边
            }
        }
        else  // 确定横_方向织布缺陷
        {

            int box_X = boundRect[i].x;
            int box_Y = boundRect[i].y;

            // 判断缺陷窗口的坐标X是否在边界禁止范围内
            if (box_X > img_copy_roi.cols - 50 || box_Y >img_copy_roi.rows - 15)
            {
                printf("坐标X在边界禁止范围内... \n");
                continue;
            }

            if (boundRect[i].width < 0.3*w)
                continue;

            cv::circle(img_copy_roi, cv::Point(box[i].center.x, box[i].center.y),
                5, cv::Scalar(0, 255, 0), -1, 8);   // 绘制最小外接矩形的中心点
            /*cv::rectangle(img_copy, cv::Point(boundRect[i].x, boundRect[i].y),
            cv::Point(boundRect[i].x + boundRect[i].width, boundRect[i].y + boundRect[i].height), cv::Scalar(0, 255, 0), 2, 8);
            */
            for (int j = 0; j<4; j++)
            {
                cv::line(img_copy_roi, rect[j], rect[(j + 1) % 4], cv::Scalar(255), 2, 8);  //绘制最小外接矩形每条边
                cv::line(img_copy_roi, rect[j], rect[(j + 1) % 4], cv::Scalar(255, 0, 0), 2, 8); //绘制最小外接矩形每条边
            }
        
        }
    }


    outImg = img_copy_roi.clone();

}


void read_video() 
{
    vector<double> sigema;
    vector<double> weight;

    for (int i = 0; i < 3; i++)
        weight.push_back(1. / 3);

    // 由于MSRCR.h中定义了宏USE_EXTRA_SIGMA,所以此处的vector sigema并没有什么意义
    sigema.push_back(30);
    sigema.push_back(150);
    sigema.push_back(300);

    Msrcr msrcr;


    // *****************************1. 测试单张图像*****************************************
    //Mat img, dst;
    //img = imread("F:\\BaiduNetdiskDownload\\织物瑕疵检测\\video_photo_fail\\570.jpg", 1);

    //img_processing(msrcr, img, dst, sigema, weight);

    //imshow("src", img);
    //imshow("dst", dst);
    //imwrite("F:\\BaiduNetdiskDownload\\织物瑕疵检测\\frame_fail\\570_filter_50.jpg", dst);

    //waitKey(10000);
    //return;

    
    // *****************************2. 连续测试多帧图像*****************************************
    string file_path, fileName, grayFile, saveFile_path, saveFileName;

    file_path = "F:\\BaiduNetdiskDownload\\织物瑕疵检测\\video_photo_fail\\";
    saveFile_path = "F:\\BaiduNetdiskDownload\\织物瑕疵检测\\frame_detect\\";

    string str1, str2;
    Mat srcImage, grayImage, outImg;
    for (int i = 1; i <= 100000; i++)
    {
        /*if (i < 2000)
            continue;*/

        printf("i =%d \n", i);

        stringstream ss1, ss2;

        ss1 << i;
        ss1 >> str1;

        //cout << str1 << endl;
        fileName = file_path + str1 + ".jpg";
        srcImage = imread(fileName, 1);
        
        img_processing(msrcr, srcImage, outImg, sigema, weight);
        saveFileName = saveFile_path + std::to_string(i) + ".bmp";

        imwrite(saveFileName, outImg);

        imshow("src", srcImage);
        imshow("dst", outImg);
    
        waitKey(1);

    }
    
}


int main()
{

    read_video();
    system("pause");
    
    return 0;
}
View Code

 

你可能感兴趣的:(织布缺陷——断针缺陷检测)