在光伏或半导体等领域,产线的缺陷检测能力至关重要,这大大影响了产品的良率,以及产线的生产效率。目前在目标检测领域,已经有了较为成熟的yolo目标检测算法,但是yolo对于小目标的检测能力是有限的,而工业中往往会遇到一些极其小的缺陷,小到只有三个像素的大小,而产线的相机在取图的过程中,往往会取到较大尺寸的原图,例如5500x5500尺寸的图,我们知道yolo的输入是固定的尺寸,比如640x640,我们能想到的第一个办法就是通过各种插值算法将我们的原图resize,但是考虑到缺陷在5500x5500的图上只占了三个像素,无论使用什么样的插值算法,resize之后的图像中的缺陷都会消失,这对于我们网络的训练效果是灾难性的,因此我们想到了通过裁图的方式,将我们的原图分图之后再训练,然后再推理端我们使用TensorRT的batch推理,提升大批量推理的速度,最后将一定数量的图拼接起来就可以实现极小缺陷的检测能力。
产线中的工业相机和彩图软件有可能会将图像以tiff的格式保存,我们需要将tiff图转成RGB保存的PNG图或者JPG图:
void convert_tiff_png(std::vector<cv::String>in_filenames, cv::String out_filename)
{
for (int i = 0; i < in_filenames.size(); i++)
{
vector<cv::Mat> Mat_list;
bool is_empty = cv::imreadmulti(in_filenames[i], Mat_list);
cv::Mat Image_DH = Mat_list[0];
cv::Mat Image_R = Mat_list[1];
cv::Mat Image_G = Mat_list[2];
cv::Mat Image_B = Mat_list[3];
int width = Image_DH.cols;
int height = Image_DH.rows;
cv::Mat result;
cv::Mat imgs[3];
imgs[2] = Image_R;
imgs[1] = Image_G;
imgs[0] = Image_B;
cv::merge(imgs, 3, result);
cv::imwrite(out_filename + to_string(i) + ".png", result);
cout << "第" << i << "张tiff已转成png格式" << endl;
/* cv::namedWindow("result", WINDOW_NORMAL);
cv::imshow("result", result);
cv::waitKey(0);*/
}
cout << "全部转换完毕" << endl;
}
此时就可以将原本保存的tiff图像转成彩色的png图像了,如下图:
此时我们发现,我们需要裁剪的图像,由于采图方式会导致周边出现很多白边和不必要的目标,我们想要的只是中间的那一块光伏板。此时,我们需要下一步的定位以及裁剪。
这一部分通过使用传统图像算法来对中间的光伏片进行定位,使用的是阈值分割方法,首先找到光伏片的大致位置,输出一个二值的掩模,然后通过cv::boundingRect()来得到rect模板,然后通过rect模板对原图进行分割:
//定位光伏片
void segment_png(std::vector<cv::String>in_filenames, cv::String outfilename)
{
for (int i = 0; i < in_filenames.size(); i++)
{
string image_path = in_filenames[i];
cv::Mat image = cv::imread(image_path, 0);
cv::Mat image1 = cv::imread(image_path);
if (image.empty())
{
cout << "请确认图像路径是否正确" << endl;
}
cv::Mat image_1;
//image.copyTo(image_1);
pyrDown(image, image);
pyrDown(image, image);
pyrDown(image, image);
cv::Mat image_binary;
cv::threshold(image, image_binary, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);
Mat struct1, struct2, struct3;
struct1 = getStructuringElement(1, Size(20, 50));
cv::erode(image_binary, image_binary, struct1);
struct2 = getStructuringElement(0, Size(20, 60));
cv::dilate(image_binary, image_binary, struct2);
vector<vector<Point>> contours;
vector<Vec4i>hierarachy;
//bitwise_not(Image_Binary, Image_Binary);
cv::findContours(image_binary, contours, hierarachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point());
int size = contours.size();
if (size > 0)
{
double area_max = 0;
int index = 0;
for (int i = 0; i < contours.size(); i++)
{
double area = cv::contourArea(contours[i]);
if (area > area_max)
{
area_max = area;
index = i;
}
}
cv::Rect rect = cv::boundingRect(contours[index]);
image_1 = image1(cv::Rect((rect.x) * 8, (rect.y) * 8, (rect.width) * 8, (rect.height) * 8));
cv::imwrite(outfilename + to_string(i) + ".png", image_1);
cout << "第" << i << "张png定位分割完毕" << endl;
//cv::namedWindow("test", WINDOW_NORMAL);
//cv::imshow("test", image_1);
//cv::waitKey(0);
}
}
cout << "全部定位分割完毕" << endl;
}
struct CeilPosition
{
Mat ceil_image;
int x;
int y;
};
vector<CeilPosition> split_image(Mat src) {
vector<CeilPosition>ceil_position;
// resize(src, src, cv::Size(WIDTH, HEIGHT));
int width = src.cols;
int height = src.rows;
OVERLAP_X = input_width - width/M;
OVERLAP_Y = input_height - height / N;
int SUB_WIDTH = width / M;
int SUB_HEIGHT = height / N;
for (int j = 0; j < N; j++)
{
for (int i = 0; i < M; i++)
{
if (i == 0 && j == 0) {
Mat image_cut, roi_img;
Rect rect(i * SUB_WIDTH, j * SUB_HEIGHT, SUB_WIDTH + OVERLAP_X, SUB_HEIGHT + OVERLAP_Y);
image_cut = Mat(src, rect);
roi_img = image_cut.clone();
ceil_position.push_back({ roi_img,i * SUB_WIDTH,j * SUB_HEIGHT });
}
else if (i == 0 && j != 0) {
Mat image_cut, roi_img;
Rect rect(i * SUB_WIDTH, j * SUB_HEIGHT - OVERLAP_Y, SUB_WIDTH + OVERLAP_X, SUB_HEIGHT + OVERLAP_Y);
image_cut = Mat(src, rect);
roi_img = image_cut.clone();
ceil_position.push_back({ roi_img,i * SUB_WIDTH, j * SUB_HEIGHT - OVERLAP_Y });
}
else if (i != 0 && j == 0) {
Mat image_cut, roi_img;
Rect rect(i * SUB_WIDTH - OVERLAP_X, j * SUB_HEIGHT, SUB_WIDTH + OVERLAP_X, SUB_HEIGHT + OVERLAP_Y);
image_cut = Mat(src, rect);
roi_img = image_cut.clone();
ceil_position.push_back({ roi_img,i * SUB_WIDTH - OVERLAP_X, j * SUB_HEIGHT });
}
else {
Mat image_cut, roi_img;
Rect rect(i * SUB_WIDTH - OVERLAP_X, j * SUB_HEIGHT - OVERLAP_Y, SUB_WIDTH + OVERLAP_X, SUB_HEIGHT + OVERLAP_Y);
image_cut = Mat(src, rect);
roi_img = image_cut.clone();
ceil_position.push_back({ roi_img,i * SUB_WIDTH - OVERLAP_X, j * SUB_HEIGHT - OVERLAP_Y });
}
}
}
return ceil_position;
}
主函数:
int main()
{
std::vector<cv::String> filenames;
cv::String folder = "";
cv::String folder_ = "";
cv::glob(folder, filenames);
cv::String outfilename = "";
cv::String outfilename_ = "";
//1.将tiff图转成彩色png图保存
//convert_tiff_png(filenames, outfilename);
//2.将png图分割出来
/*std::vector filenames_;
cv::glob(folder_, filenames_);
segment_png(filenames_, outfilename_);*/
//3.将分割后的png图切成n个小块
std::vector<CeilPosition> image_set;
std::vector<cv::String> filenames_seg;
cv::String folder_seg = "";
cv::glob(folder_seg, filenames_seg);
cv::Mat src = cv::imread(filenames_seg[1]);
image_set = split_image(src);
for (int k = 0; k < 22 * 22; k++)
{
cv::imwrite("", image_set[k].ceil_image);
cout << "第" << k << "张分图保存好了" << endl;
}
return 0;
}