# 怎么用C++ OpenCV制作電子相冊查看器
## 一、項目概述
### 1.1 電子相冊查看器的功能需求
現代電子相冊查看器需要具備以下核心功能:
- 支持主流圖片格式(JPEG/PNG/BMP等)
- 基本圖片操作(縮放/旋轉/翻轉)
- 幻燈片播放模式
- 圖片過渡動畫效果
- 縮略圖導航功能
- 簡單的圖片編輯功能(亮度/對比度調整)
### 1.2 OpenCV的優勢
OpenCV作為計算機視覺庫在圖像處理方面具有獨特優勢:
- 跨平臺支持(Windows/Linux/macOS)
- 高效的圖像解碼/編碼能力
- 豐富的圖像處理算法
- 硬件加速支持(通過IPP、CUDA等)
- 活躍的開發者社區
## 二、開發環境搭建
### 2.1 基礎工具準備
```bash
# Ubuntu安裝示例
sudo apt update
sudo apt install build-essential cmake git
sudo apt install libopencv-dev
推薦使用vcpkg進行跨平臺依賴管理:
# CMakeLists.txt示例配置
find_package(OpenCV REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})
target_link_libraries(your_target ${OpenCV_LIBS})
/PhotoViewer
├── include/ # 頭文件
├── src/ # 源代碼
├── resources/ # 測試圖片
├── CMakeLists.txt
└── README.md
class ImageLoader {
public:
explicit ImageLoader(const std::string& path) {
if (!fs::exists(path)) {
throw std::runtime_error("Directory not exists");
}
for (const auto& entry : fs::directory_iterator(path)) {
if (isImageFile(entry.path())) {
imagePaths.push_back(entry.path().string());
}
}
if (imagePaths.empty()) {
throw std::runtime_error("No images found");
}
}
cv::Mat loadImage(size_t index) const {
if (index >= imagePaths.size()) return {};
return cv::imread(imagePaths[index], cv::IMREAD_COLOR);
}
private:
bool isImageFile(const fs::path& path) const {
static const std::set<std::string> extensions = {
".jpg", ".jpeg", ".png", ".bmp", ".tiff"
};
return extensions.count(path.extension().string());
}
std::vector<std::string> imagePaths;
};
class ImageViewer {
public:
void showImage(const cv::Mat& img, const std::string& title = "PhotoViewer") {
cv::namedWindow(title, cv::WINDOW_NORMAL | cv::WINDOW_KEEPRATIO);
cv::imshow(title, img);
// 自適應窗口大小
int maxHeight = cv::getWindowImageRect(title).height;
double ratio = static_cast<double>(maxHeight) / img.rows;
cv::resizeWindow(title, img.cols * ratio, img.rows * ratio);
}
void setCallback(const std::function<void(int)>& callback) {
cv::setMouseCallback("PhotoViewer",
[](int event, int x, int y, int flags, void* userdata) {
if (event == cv::EVENT_LBUTTONDOWN) {
(*static_cast<std::function<void(int)>*>(userdata))(1);
}
else if (event == cv::EVENT_RBUTTONDOWN) {
(*static_cast<std::function<void(int)>*>(userdata))(-1);
}
}, &callback);
}
};
class ImageProcessor {
public:
enum class RotateDirection { CW, CCW };
static cv::Mat rotate(const cv::Mat& src, RotateDirection dir) {
cv::Mat dst;
cv::rotate(src, dst, dir == RotateDirection::CW ?
cv::ROTATE_90_CLOCKWISE : cv::ROTATE_90_COUNTERCLOCKWISE);
return dst;
}
static cv::Mat adjustBrightness(const cv::Mat& src, int beta) {
cv::Mat dst;
src.convertTo(dst, -1, 1, beta);
return dst;
}
static cv::Mat applySepia(const cv::Mat& src) {
cv::Mat kernel = (cv::Mat_<float>(3,3) <<
0.272, 0.534, 0.131,
0.349, 0.686, 0.168,
0.393, 0.769, 0.189);
cv::transform(src, dst, kernel);
return dst;
}
};
class SlideShow {
public:
SlideShow(const ImageLoader& loader, int interval = 2000)
: loader(loader), interval(interval), running(false) {}
void start() {
running = true;
showThread = std::thread([this]() {
size_t index = 0;
while (running) {
auto img = loader.loadImage(index);
if (!img.empty()) {
cv::imshow("SlideShow", img);
index = (index + 1) % loader.count();
}
std::this_thread::sleep_for(
std::chrono::milliseconds(interval));
}
});
}
void stop() {
running = false;
if (showThread.joinable()) {
showThread.join();
}
}
private:
const ImageLoader& loader;
std::thread showThread;
int interval;
std::atomic<bool> running;
};
class TransitionEffects {
public:
static void crossFade(const cv::Mat& src1, const cv::Mat& src2,
int steps = 10, int delay = 50) {
cv::Mat dst;
for (int i = 0; i <= steps; ++i) {
double alpha = static_cast<double>(i) / steps;
cv::addWeighted(src1, 1.0 - alpha, src2, alpha, 0.0, dst);
cv::imshow("Transition", dst);
cv::waitKey(delay);
}
}
static void slideIn(const cv::Mat& src1, const cv::Mat& src2,
int direction = 0, int steps = 20) {
// 0=右到左 1=左到右 2=上到下 3=下到上
cv::Mat combined;
cv::Size size = src1.size();
for (int i = 0; i <= steps; ++i) {
double ratio = static_cast<double>(i) / steps;
int offset = 0;
switch (direction) {
case 0: // 右到左
offset = static_cast<int>(size.width * ratio);
combined = cv::Mat::zeros(size, src1.type());
src2(cv::Rect(0, 0, offset, size.height))
.copyTo(combined(cv::Rect(size.width - offset, 0, offset, size.height)));
src1(cv::Rect(offset, 0, size.width - offset, size.height))
.copyTo(combined(cv::Rect(0, 0, size.width - offset, size.height)));
break;
// 其他方向實現類似...
}
cv::imshow("Transition", combined);
cv::waitKey(30);
}
}
};
class ControlPanel {
public:
void createTrackbars() {
cv::namedWindow("Controls", cv::WINDOW_AUTOSIZE);
cv::createTrackbar("Brightness", "Controls", &brightness, 100);
cv::createTrackbar("Contrast", "Controls", &contrast, 100);
cv::createTrackbar("Zoom", "Controls", &zoomLevel, 200);
}
void applyEffects(cv::Mat& img) {
// 亮度調整 (-50到+50)
double beta = (brightness - 50) * 2.55;
img.convertTo(img, -1, 1, beta);
// 對比度調整 (0.5到1.5)
double alpha = 0.5 + contrast / 100.0;
img.convertTo(img, -1, alpha, 0);
// 縮放 (50%到200%)
if (zoomLevel != 100) {
double scale = zoomLevel / 100.0;
cv::resize(img, img, cv::Size(), scale, scale);
}
}
private:
int brightness = 50;
int contrast = 50;
int zoomLevel = 100;
};
void handleKeyboard(int key, ImageViewer& viewer, ImageLoader& loader,
size_t& currentIndex) {
switch (key) {
case 'n': // 下一張
currentIndex = (currentIndex + 1) % loader.count();
viewer.showImage(loader.loadImage(currentIndex));
break;
case 'p': // 上一張
currentIndex = (currentIndex - 1 + loader.count()) % loader.count();
viewer.showImage(loader.loadImage(currentIndex));
break;
case 'r': // 順時針旋轉
viewer.showImage(ImageProcessor::rotate(
loader.loadImage(currentIndex),
ImageProcessor::RotateDirection::CW));
break;
case 's': // 保存圖片
cv::imwrite("modified_" + std::to_string(currentIndex) + ".jpg",
viewer.getCurrentImage());
break;
case 27: // ESC鍵退出
viewer.close();
break;
}
}
class ImageCache {
public:
explicit ImageCache(const ImageLoader& loader, size_t cacheSize = 5)
: loader(loader), cacheSize(cacheSize) {}
cv::Mat getImage(size_t index) {
// 檢查緩存
auto it = cache.find(index);
if (it != cache.end()) {
return it->second;
}
// 加載新圖片
cv::Mat img = loader.loadImage(index);
if (img.empty()) return {};
// 更新緩存
cache[index] = img;
if (cache.size() > cacheSize) {
cache.erase(cache.begin());
}
// 預加載相鄰圖片
preloadAdjacent(index);
return img;
}
private:
void preloadAdjacent(size_t index) {
std::thread([this, index]() {
for (int i = 1; i <= 2; ++i) {
size_t next = (index + i) % loader.count();
if (cache.count(next) == 0) {
cache[next] = loader.loadImage(next);
}
}
}).detach();
}
const ImageLoader& loader;
std::map<size_t, cv::Mat> cache;
size_t cacheSize;
};
class AsyncImageLoader {
public:
void loadAsync(size_t index, std::function<void(cv::Mat)> callback) {
std::lock_guard<std::mutex> lock(queueMutex);
taskQueue.emplace([=]() {
cv::Mat img = loader.loadImage(index);
callback(img);
});
cv.notify_one();
}
void startWorkerThreads(size_t threadCount = 4) {
for (size_t i = 0; i < threadCount; ++i) {
workers.emplace_back([this]() {
while (true) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(queueMutex);
cv.wait(lock, [this]() {
return !taskQueue.empty() || shouldStop;
});
if (shouldStop) return;
task = std::move(taskQueue.front());
taskQueue.pop();
}
task();
}
});
}
}
private:
ImageLoader loader;
std::queue<std::function<void()>> taskQueue;
std::vector<std::thread> workers;
std::mutex queueMutex;
std::condition_variable cv;
bool shouldStop = false;
};
int main(int argc, char** argv) {
if (argc < 2) {
std::cerr << "Usage: " << argv[0] << " <image_directory>" << std::endl;
return 1;
}
try {
// 初始化組件
ImageLoader loader(argv[1]);
ImageCache cache(loader);
ImageViewer viewer;
ControlPanel panel;
panel.createTrackbars();
size_t currentIndex = 0;
viewer.showImage(cache.getImage(currentIndex));
// 主循環
while (viewer.isOpen()) {
int key = cv::waitKey(30) & 0xFF;
if (key != 255) {
handleKeyboard(key, viewer, loader, currentIndex);
}
// 應用圖像調整
cv::Mat img = viewer.getCurrentImage();
panel.applyEffects(img);
viewer.showImage(img);
}
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
return 1;
}
return 0;
}
cmake_minimum_required(VERSION 3.10)
project(PhotoViewer)
set(CMAKE_CXX_STANDARD 17)
find_package(OpenCV REQUIRED)
file(GLOB SOURCES "src/*.cpp")
add_executable(PhotoViewer ${SOURCES})
target_include_directories(PhotoViewer PRIVATE include)
target_link_libraries(PhotoViewer ${OpenCV_LIBS})
if(MSVC)
target_compile_options(PhotoViewer PRIVATE /W4 /WX)
else()
target_compile_options(PhotoViewer PRIVATE -Wall -Wextra -Werror)
endif()
void detectFaces(cv::Mat& img) {
static cv::CascadeClassifier faceCascade;
if (faceCascade.empty()) {
faceCascade.load("haarcascade_frontalface_default.xml");
}
std::vector<cv::Rect> faces;
cv::Mat gray;
cv::cvtColor(img, gray, cv::COLOR_BGR2GRAY);
faceCascade.detectMultiScale(gray, faces, 1.1, 3);
for (const auto& face : faces) {
cv::rectangle(img, face, cv::Scalar(0, 255, 0), 2);
}
}
#include <libexif/exif-data.h>
void printExifInfo(const std::string& path) {
ExifData* exifData = exif_data_new_from_file(path.c_str());
if (!exifData) return;
ExifByteOrder byteOrder = exif_data_get_byte_order(exifData);
ExifEntry* entry = exif_content_get_entry(
exifData->ifd[EXIF_IFD_0], EXIF_TAG_DATE_TIME);
if (entry) {
char buffer[1024];
exif_entry_get_value(entry, buffer, sizeof(buffer));
std::cout << "拍攝時間: " << buffer << std::endl;
}
exif_data_unref(exifData);
}
推薦使用Conan進行跨平臺依賴管理:
# conanfile.txt
[requires]
opencv/4.5.5
[generators]
cmake
本文詳細介紹了如何使用C++和OpenCV開發功能完整的電子相冊查看器,涵蓋了從基礎圖片顯示到高級功能實現的各個方面。通過本項目的實踐,開發者可以掌握:
項目代碼已遵循模塊化設計原則,便于擴展更多高級功能如圖像增強、云端同步等。建議開發者在此基礎上繼續探索計算機視覺技術的更多應用場景。 “`
注:本文實際約4500字,由于Markdown格式的代碼塊和標題會占用較多字符空間,實際文字內容略少于純文本格式。如需精確字數控制,可適當調整代碼示例的詳細程度或增加理論講解部分。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。