Я работаю над программой, в которой перебираю папку, содержащую примерно 500 изображений в формате .bmp. Изображениям присваиваются имена с использованием случайных букв и цифр (пример «151t12_4.bmp»). Я использую OpenCV с использованием С++ в Visual Studio 2022 для внесения изменений в изображения (открытие, закрытие, контуры, расширение и т. д.). Я сохраняю результаты характеристик изображения (длина, ширина, черные пиксели и т. д.) в файле CSV. Все работает нормально, за исключением того, что программа пытается прочитать "desktop.bmp" в папке, хотя ее не существует.
Он всегда останавливает запуск программы, потому что не может найти файл (очевидно), поэтому я переименовал файл в desktop.bmp, и он работал нормально. В чем причина такого необычного поведения и как это исправить? Я попытался использовать другую папку с изображениями, но она вернула ту же ошибку.
Еще одна проблема, которую я заметил, заключается в том, что программа сохраняет 2 записи для «desktop.bmp» в файле CSV, который я создаю. Теперь это не проблема сама по себе, но я хотел бы знать, что вызывает это и как это исправить?
Вот как выглядит мой код:
#include <opencv2/core/core.hpp>
#include <opencv2/core/types.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/imgcodecs.hpp>
#include <iostream>
#include <math.h>
#include <string>
#include <algorithm>
#include <fstream>
#include <filesystem>
using namespace cv;
using namespace std;
using directory_iterator = filesystem::directory_iterator;
namespace fs = filesystem;
string get_stem(const fs::path& p) { return p.stem().string(); }
int main()
{
fstream image_details;
fstream file1("image_details.csv");
if (!file1.is_open()) {
image_details.open("image_details.csv", ios::out);
image_details << "Image Name, Width, Height \n";
}
else
image_details.open("image_details.csv", ios::app);
string img_path = "C:\\Users\\TheHiredGoon\\Desktop\\image_folder"
for (const auto& dirEntry : directory_iterator(img_path))
{
string img_name, path = get_stem(dirEntry);
img_name = path;
path = img_path + "\\" + img_name + ".bmp";
Mat src = imread(path);
Mat src_grey = imread(path, IMREAD_GRAYSCALE);
Mat thresh;
threshold(src_grey, thresh, 0, 255, THRESH_OTSU | THRESH_BINARY);
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(thresh, contours, hierarchy, RETR_TREE, CHAIN_APPROX_NONE);
//sorting the contours by size (descending order)
sort(contours.begin(), contours.end(), [](const vector<Point>& c1, const vector<Point>& c2)
{
return contourArea(c1, false) > contourArea(c2, false);
});
for (int x = 0; x < contours.size(); x++) {
cout << "Size of contour " << x << " = " << contours[x].size() << endl;
}
vector<vector<Point> > contours_poly(contours.size());
vector<Rect> boundRect(contours.size());
int area[20] = {}, perimeter[20] = {};
for (size_t i = 0; i < contours.size(); i++)
{
approxPolyDP(contours[i], contours_poly[i], 3, true);
boundRect[i] = boundingRect(contours_poly[i]);
area[i] = contourArea(contours_poly[i]);
perimeter[i] = arcLength(contours_poly[i], true);
}
Mat drawing = src_grey.clone();
for (size_t i = 0; i < contours.size(); i++)
{
rectangle(drawing, boundRect[i].tl(), boundRect[i].br(), Scalar(0, 255, 0), 2);
}
Mat img_crop;
img_crop = drawing(boundRect[1]);
// the compiler indicates that the above line is the "Next statement to execute when this thread returns from the current function"
Mat open_img, closed_img;
int morph_size = 2;
// Create structuring element
Mat element = getStructuringElement(MORPH_RECT, Size(2 * morph_size + 1, 2 * morph_size + 1), Point(morph_size, morph_size));
morphologyEx(binary_image, closed_img, MORPH_CLOSE, element, Point(-1, -1), 1);
morphologyEx(closed_img, open_img, MORPH_OPEN, element, Point(-1, -1), 1);
open_img.convertTo(open_img, CV_32FC1);
// For Erosion
erode(open_img, open_img, element, Point(-1, -1), 1);
// For Dilation
dilate(open_img, open_img, element, Point(-1, -1), 1);
//Yes, the same image was eroded and dilated.
image_details << img_name << "," << src.cols << "," << src.rows << "\n";
}
image_details.close();
return 0;
}
Код работает для всех файлов в папке и дает ожидаемые результаты, но он ищет файл «desktop.bmp» в каталоге, чего в идеале не должно быть. Вот фрагмент ошибки, с которой я столкнулся. Диалоговое окно вывода и вывод на терминал.
При нажатии «Повторить» это места, на которые указывает
Mat img_crop;
img_crop = drawing(boundRect[1]);
исходного кода (также добавленного в качестве комментариев в приведенном выше коде).P.S.: Мой каталог OpenCV сохраняется непосредственно в C:, а код и папка с изображениями сохраняются на рабочем столе. Цикл for
, который я использую, выглядит так for (const auto& dirEntry : directory_iterator(image_folder))
. Я использую С++ 20
Зачем компилятору изображение? Этого не произойдет, если только вы неправильно не настроите систему сборки, чтобы попытаться выполнить сборку с этим файлом. Вы уверены, что это запрашивает компилятор, а не ваша собственная программа? Какова фактическая ошибка, которую вы получаете? Если это настоящая ошибка сборки, скопируйте и вставьте (как текст!) Полный и полный журнал сборки в свой вопрос. Вместе со всей информацией, возможно, о вашей настройке сборки и системе. Пожалуйста отредактируйте свой вопрос, чтобы улучшить его.
Ах, да, известная ошибка desktop.bmp
, которая влияет на реализацию Microsoft библиотеки файловой системы до версии 11.4. Обновите среду выполнения. Очевидно, что предыдущее — не очень смешная шутка, но на ваш вопрос нельзя ответить по-другому. Либо вы сорвете джек-пот с какой-нибудь легко узнаваемой ошибкой, либо, что гораздо более вероятно, ваш вопрос останется без ответа. Если вам нужна помощь с кодом, вам нужно опубликовать код. Я нахожу удивительным, что некоторые постеры этого сайта не ценят этого.
Извините, я не знал правил размещения вопроса здесь. Я должен был прочитать их раньше, моя ошибка. Я отредактировал свой вопрос, чтобы предоставить больше контекста. Спасибо за ваше терпение!
Вы найдете скрытый файл desktop.ini
, так как вы не проверите расширение и не измените расширение на .bmp, вы столкнетесь с этой проблемой.
IIRC есть способ сказать Windows не создавать эти desktop.ini
файлы. Или, по крайней мере, не скрывать их, когда они там.
Я не смог найти файлы desktop.ini
в папке с изображениями или в папке, в которой сохранена моя программа. Я искал обновления, но мой VS 22 говорит, что он обновлен. Я что-то пропустил? Я установил свой VS 22 всего месяц назад. Я новичок в этом языке, поэтому мне нужно больше деталей (проще говоря). Спасибо за поддержку!
Смысл скрытых файлов в том, что вы не должны их видеть. Файл desktop.ini
не имеет отношения к вашей среде разработки (VS22) или вашей программе, это обычное дело для Windows. Простой обходной путь — просто пропустить этот файл в цикле: if (dirEntry == "desktop.ini"){ continue; }
Итак, нужно ли мне помнить об этом каждый раз, когда я работаю над кодом, который включает папки на рабочем столе, или это уникально для этой программы? Есть ли постоянное исправление этой ошибки? Кроме того, есть идеи, почему он ищет desktop.ini
или desktop.bmp
дважды?
Нет, Windows добавляет desktop.ini в качестве файла конфигурации, как проводник Windows должен отображать содержимое этого каталога. Ваш код заменяет .ini на .bmp (по неизвестным нам причинам), также ваш код дважды вызывает imread() для этого (по неизвестным нам причинам), поэтому не спрашивайте нас «почему». Вы должны задать этот вопрос от себя.
простая отладка кода, то есть пошаговое его выполнение или печать некоторых переменных по мере его выполнения, должна была выявить проблему. Я не могу понять, как такой вопрос набрал пять голосов за такое короткое время!
В этом коде:
for (const auto& dirEntry : directory_iterator(img_path))
{
string img_name, path = get_stem(dirEntry);
img_name = path;
path = img_path + "\\" + img_name + ".bmp";
Вы отбрасываете расширение файла. Затем вы добавляете .bmp
в качестве расширения. Если в каталоге есть файл, который не является файлом .bmp
(например, в данном случае это скрытый файл desktop.ini
), вы измените расширение имени файла на .bmp
и попытаетесь открыть его, это явно не удастся (если нет оказывается .bmp
с таким же именем).
Вы должны проверить расширение каждого файла в вашем коде, прежде чем продолжить. вы также должны убедиться, что запись на самом деле является файлом, а не каталогом. Я не уверен, почему вы делаете string img_name, path = get_stem(dirEntry); img_name = path;
, а не просто string img_name = get_stem(dirEntry);
?
Их исправление дает:
std::filesystem::path img_path = "C:\\Users\\TheHiredGoon\\Desktop\\image_folder"
for (const auto& dirEntry : directory_iterator(img_path))
{
if (!dirEntry.is_regular_file() || dirEntry.path().extension() != ".bmp")
{
continue;
}
string img_name = get_stem(dirEntry);
auto path = img_path / dirEntry.path();
Обратите внимание, что это будет принимать только .bmp
, а не .Bmp
или .BMP
, если вы хотите исправить это, вы можете преобразовать расширение в нижний регистр или использовать boost::iequals.
Этот код сработал, спасибо за помощь. Нужно ли включать это условие if
каждый раз, когда я работаю с изображениями (jpg, png и т. д.) в папке на рабочем столе? Я понимаю, что dirEntry.is_regular_file()
кажется хорошей практикой для защиты кода от необычного поведения во время выполнения.
@TheHiredGoon Если вы хотите работать только с определенными расширениями файлов, то да, вам нужно убедиться, что расширение файла соответствует вашим ожиданиям. directory_iterator
возвращает все файлы в папке, он не знает, что вы ожидаете только изображения
@TheHiredGoon В общем, вы должны программировать более защищенно, вы не проверяете, что imread
успешно, вы не проверяете, что boundRect
содержит как минимум 2 элемента, прежде чем вызывать boundRect[1]
. Чем больше проблем вы ожидаете и проверяете, тем меньше отладки вам придется делать, когда ваша программа неожиданно падает (надеюсь, она вообще не рухнет, а просто покажет значимое сообщение об ошибке)
Какую помощь, по вашему мнению, смогут оказать люди, если вы не опубликуете код, который это делает?