ISP(with opencv) c++ #6

천동민·2023년 7월 23일

Ampoules Defect Detection project

패턴 매칭 raw code로 구현
opencv에서 제공하는 matchTemplate 함수를 쓰지 않고
직접 구현하여 프로젝트 완성해보기

진행 흐름

이미지 전처리

1.이미지와 패턴 이미지를 설정해 기준점 찾기
2.기준점을 기준으로 Ampoules 내용물 범위 확보
3.범위안에서 정보 획득
4.획득한 정보에서 판단
5.이미지에 표현

이미지 전처리

이미지의 대비 향상을 위해 평활화(Equalization)와 이미지 스트레치(Image Stretch) 진행


cv::Mat image = cv::imread("ampoules_02.png", cv::IMREAD_COLOR);

// 이미지를 그레이스케일로 변환합니다.
cv::Mat gray_image;
cv::cvtColor(image, gray_image, cv::COLOR_BGR2GRAY);

//평활화 -> 대비 향상
Mat gray_img_histoEql;
cv::equalizeHist(gray_image, gray_img_histoEql);


double minVal, maxVal;
minMaxLoc(gray_img_histoEql, &minVal, &maxVal);
double newMin = 0.2 * minVal;
double newMax = 0.8 * maxVal;
// 이미지 스트레치 -> 대비 향상
Mat stretched_img;
gray_img_histoEql.convertTo(stretched_img, CV_8UC1, 255.0 / (newMax - newMin), 
-newMin * 255.0 / (newMax - newMin));

1.이미지와 패턴 이미지를 설정해 기준점 찾기

이 부분을 기준점으로 선정(패턴 이미지)
패턴 매칭을 통해 패턴 이미지와 같은 부분을 찾는다

 for (size_t i = 0; i <= height; i++)
    {
        if (height - i < 158) { break; }
        
        for (size_t j = 0; j < width; j++)
        {
            cnt = 0;
            if (width - j < 78) { break; }
                   
            cv::Rect location_Rect(j, i, 78, 158);
            Mat test_img = dilated_image(location_Rect).clone();
            uchar* pDatatest = test_img.data;
            for (size_t a = 0; a < 158; a++)
            {
                for (size_t b = 0; b < 78; b++)
                {
                    if (pDatatest[a* 78 + b] == pDatainput[a* 78 + b]) { cnt += 1; }
                    
                    else { cnt += 0; }                    
                }
            }
            if (cnt >= 11300) // 유사도 부분 11300-> 91.69%, 100% -> 12324 <158*78> 
            {
                res_cnt += 1;                
                pDatalocation[i * width + j] = 255; -> contours를 잡기 위해
            }
            else { cnt = 0; }       

        }
    }
    std::vector<std::vector<cv::Point>> contours;
    std::vector<cv::Vec4i> hierarchy;
    cv::findContours(location, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);
    std::vector<int> res(contours.size(), 0);
    
    for (size_t i = 0; i < contours.size(); i++)
    {
    	RotatedRect rt = cv::minAreaRect(contours[i]);
        Scalar color = Scalar(rng.uniform(0, 256), rng.uniform(0, 256), rng.uniform(0, 256));
        Point2f center = (rt.boundingRect().br() + rt.boundingRect().tl()) / 2;
        cv::rectangle(image, Rect(center.x, center.y, width_input, height_input), color, 1);

pDatalocation[i * width + j] = 255;의 결과물

2.기준점을 기준으로 Ampoules 내용물 범위 확보

이미지에서 [0] 영역이 ampoule의 내용물
밑에 부분은 패턴 1번 과정을 통한 패턴 매칭 결과물
확대한 부분에서 어두운 부분과 밝은 부분의 경계선이 내용물이 들어있는 높이

 cv::Rect roiRect(((center.x + width_input + center.x) / 2) - 10, 100, 20, 100);

 cv::rectangle(image, Rect(((center.x + width_input + center.x) / 2) - 10, 100, 20, 100), color, 1);
 string msg = std::format("[{}]", i);
 putText(image, msg, Point(((center.x + width_input + center.x) / 2) - 10, 100),
            HersheyFonts::FONT_HERSHEY_SIMPLEX, 0.4, Scalar(20, 20, 20), 1);
        
 cv::Mat input_img = stretched_img(roiRect).clone();  // roi 생성

3.범위안에서 정보 획득

영역의 크기는 넓이 20 높이 100이다
경계선의 극명한 차이를 보기 위해 int h[100]을 만들고 각 행의 원소들을 모두 더하여 배열 h에 저장한다

		uchar* pDatainput = input_img.data;
        size_t width = input_img.cols;
        size_t height = input_img.rows;

        int h[100] = { 0 };
        for (size_t k = 0; k < height; k++)
        {

            for (size_t l = 0; l < width; l++)
            {
                h[k] += pDatainput[k * width + l];
            }
        }

int h[100] 예시

4.획득한 정보에서 판단

for (size_t a = 0; a < 100; a++)
        {
            if (h[a] > 4970)
            {
                res[i] = a; 
                cv::rectangle(image, Rect(center.x, 100 + a, 65, 3), (0, 0, 255), 3);
                break;

            }

        }

배열 h에서 4970의 값을 경계선으로 설정하고 그 부분을 표시하는 것
for 문이 다 돌고 난 후

for (size_t q = 0; q < res.size(); q++)
    {

        if (abs(8 - res[q]) > 10)
        {
            RotatedRect rt = cv::minAreaRect(contours[q]);
            Point2f center = (rt.boundingRect().br() + rt.boundingRect().tl()) / 2;
            cv::rectangle(image, Rect(center.x, 0, 78, 598), (0, 0, 255), 3);
            string msg = std::format("Defect!");
            putText(image, msg, Point(center.x + 5, 10), HersheyFonts::FONT_HERSHEY_SIMPLEX, 0.4, (255, 0, 0), 1);

        }

    }

res에 들어있는 값이 경계선의 높이 (내용물이 차있는 높이)
약간의 차이는 정상으로 볼 수 있기 때문에 10 픽셀 이상 차이나는 것을 불량으로 판단

5.이미지에 표현 <실행 결과>

review

아쉬운 부분: 사실 실행결과에서 7번과 6번 부분의 경계선도 표현이 되었어야 정상이지만
워낙 이미지의 대비가 좋지 않아서 처리가 되지 않았다
Raw code vs matchTemplate
matchTemplate의 경우 이미지 자체를 input(패턴이미지)으로 넣기 때문에 사진이 바뀌어도 재설정 할 필요가 없지만
Raw code의 경우 이미지 안에서 input(패턴이미지)를 설정하기 때문에 사진이 바뀌면 좌표값과 크기를 다시 설정해줘야 하는 번거로움이 있다
또한 실행 시간에 있어 내가 구현한 Raw code는 7초 정도의 시간이 소요되는데
matchTemplate 라이브러리는 굉장히 빠르게 실행되었다

2개의 댓글

comment-user-thumbnail
2023년 7월 23일

감사합니다. 이런 정보를 나눠주셔서 좋아요.

1개의 답글