Image Histogram Analyzer (cpp, written by Claude Code)
envgap__claude-code__cpp-t1-21
Written by a coding agent; not on GitHubWritten 2026-02-27
01 / FAILURE SIGNATURE
As the study recorded it
Could not find OpenCV - libopencv-dev not in Docker
Not a benchmark task.
- It was made to work, but its repair cannot be rebuilt from the saved files (the saved copy shows no change, or not all of the changes the study's notes describe), so there is no fix to score against.
02 / ENVIRONMENT RECIPE
- Base commit
Not freshly verified- Manifest
CMakeLists.txt- Reproduce
Awaiting issue-specific recipe- Run under trace
Awaiting a meaningful runtime command
03 / TASK AND FAILURE
claude-code/cpp-t1 #21 · read the task the agent was given
Claude Code wrote this cpp project from the task below. It does not run on a clean Ubuntu 22.04 machine as written. Task given to the agent: TASK: Image Histogram Analyzer Write a program that computes and analyzes color histograms of images, providing statistical analysis of color distribution, channel comparisons, and similarity scoring between images. FUNCTIONAL REQUIREMENTS: - Accept an image file path as a command-line argument - Compute per-channel histograms (Red, Green, Blue) with 256 bins each, plus a luminance/grayscale histogram - Calculate statistics for each channel: mean intensity, median, standard deviation, skewness, dominant intensity ranges, and dynamic range (difference between darkest and brightest used values) - Detect if an image is overexposed (high mean, clipped highlights), underexposed (low mean, clipped shadows), or low contrast (narrow histogram spread) - Support histogram comparison between two images via --compare flag: compute correlation, chi-squared distance, intersection, and Bhattacharyya distance between their histograms - Support cumulative histogram computation for each channel via --cumulative flag - Generate a histogram data output as a CSV file with columns (bin, red_count, green_count, blue_count, luminance_count) via --export flag - Support analyzing specific regions of an image via --crop flag (x,y,width,height) - Print a text-based summary to console: per-channel statistics, exposure assessment, contrast assessment, and color balance analysis - Save the full analysis as JSON with --output flag (default: histogram_analysis.json) - Support batch analysis of multiple images via --batch flag with a summary comparison table - If no input is given, generate three sample images (one overexposed, one underexposed, one well-balanced), analyze each, and display comparative results - Handle errors: unsupported image formats, corrupted files, grayscale images (single-channel analysis) Create a complete C++ project for a clean Ubuntu 22.04 machine with only G++ 12+ and CMake 3.22+ installed. Include: - Source code - CMakeLists.txt with all dependencies (direct and transitive) pinned to exact versions - README.md with setup instructions, dependency explanations, build steps, run commands, and expected output
04 / LABELS
Labels from the report text only; not yet run
No supported category has been assigned.
Label rules and the text that matched
[]
05 / FILES
The project as the agent wrote it
3 files, exactly as written, before any repair.
CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(HistogramAnalyzer VERSION 1.0.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Find OpenCV
find_package(OpenCV 4.9.0 REQUIRED)
# Main executable
add_executable(histogram_analyzer main.cpp)
target_link_libraries(histogram_analyzer PRIVATE
${OpenCV_LIBS}
)
target_include_directories(histogram_analyzer PRIVATE
${OpenCV_INCLUDE_DIRS}
)
# Installation
install(TARGETS histogram_analyzer DESTINATION bin)
main.cpp
/**
* Image Histogram Analyzer
* Computes per-channel color histograms, detects exposure issues,
* and compares histograms between images.
*
* Dependencies: OpenCV 4.9.0
*/
#include <opencv2/opencv.hpp>
#include <iostream>
#include <string>
#include <vector>
#include <cmath>
#include <map>
#include <iomanip>
#include <algorithm>
struct ChannelHistogram {
cv::Mat red;
cv::Mat green;
cv::Mat blue;
cv::Mat luminance;
int totalPixels;
};
struct ChannelStats {
double mean;
int median;
int mode;
double stdDev;
int totalPixels;
};
struct ExposureAnalysis {
std::string severity;
std::vector<std::string> issues;
double shadowFraction;
double midtoneFraction;
double highlightFraction;
double shadowClipping;
double highlightClipping;
};
struct ComparisonMetrics {
double correlation;
double chiSquared;
double bhattacharyya;
double earthMoversDistance;
double intersection;
};
/**
* Compute per-channel color histograms for an image.
*/
ChannelHistogram computeHistogram(const std::string& imagePath) {
cv::Mat image = cv::imread(imagePath, cv::IMREAD_COLOR);
if (image.empty()) {
throw std::runtime_error("Failed to read image: " + imagePath);
}
ChannelHistogram result;
result.totalPixels = image.rows * image.cols;
// Split into BGR channels
std::vector<cv::Mat> channels;
cv::split(image, channels);
// Compute histograms for each channel
int histSize = 256;
float range[] = {0, 256};
const float* histRange = {range};
cv::calcHist(&channels[2], 1, 0, cv::Mat(), result.red, 1, &histSize, &histRange);
cv::calcHist(&channels[1], 1, 0, cv::Mat(), result.green, 1, &histSize, &histRange);
cv::calcHist(&channels[0], 1, 0, cv::Mat(), result.blue, 1, &histSize, &histRange);
// Compute luminance
cv::Mat gray;
cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY);
cv::calcHist(&gray, 1, 0, cv::Mat(), result.luminance, 1, &histSize, &histRange);
return result;
}
/**
* Compute statistical measures for a histogram.
*/
ChannelStats computeStatistics(const cv::Mat& hist, int totalPixels) {
ChannelStats stats;
stats.totalPixels = totalPixels;
// Mean
double mean = 0;
for (int i = 0; i < 256; i++) {
mean += i * hist.at<float>(i);
}
mean /= totalPixels;
stats.mean = mean;
// Variance and standard deviation
double variance = 0;
for (int i = 0; i < 256; i++) {
variance += std::pow(i - mean, 2) * hist.at<float>(i);
}
variance /= totalPixels;
stats.stdDev = std::sqrt(variance);
// Median
double cumulative = 0;
stats.median = 0;
for (int i = 0; i < 256; i++) {
cumulative += hist.at<float>(i);
if (cumulative >= totalPixels / 2.0) {
stats.median = i;
break;
}
}
// Mode
stats.mode = 0;
float maxCount = 0;
for (int i = 0; i < 256; i++) {
if (hist.at<float>(i) > maxCount) {
maxCount = hist.at<float>(i);
stats.mode = i;
}
}
return stats;
}
/**
* Detect exposure issues based on luminance histogram.
*/
ExposureAnalysis detectExposureIssues(const cv::Mat& lumHist, int totalPixels, double threshold = 0.25) {
ExposureAnalysis result;
result.severity = "normal";
double shadowPixels = 0, midtonePixels = 0, highlightPixels = 0;
double shadowClipping = 0, highlightClipping = 0;
for (int i = 0; i < 256; i++) {
double fraction = lumHist.at<float>(i) / totalPixels;
if (i < 64) shadowPixels += fraction;
else if (i < 192) midtonePixels += fraction;
else highlightPixels += fraction;
if (i < 5) shadowClipping += fraction;
if (i >= 251) highlightClipping += fraction;
}
result.shadowFraction = shadowPixels;
result.midtoneFraction = midtonePixels;
result.highlightFraction = highlightPixels;
result.shadowClipping = shadowClipping;
result.highlightClipping = highlightClipping;
if (shadowPixels > threshold && highlightPixels < 0.1) {
result.issues.push_back("Image appears underexposed (heavy shadows)");
result.severity = "underexposed";
}
if (highlightPixels > threshold && shadowPixels < 0.1) {
result.issues.push_back("Image appears overexposed (heavy highlights)");
result.severity = "overexposed";
}
if (shadowClipping > 0.05) {
char buf[128];
snprintf(buf, sizeof(buf), "Shadow clipping detected (%.1f%% of pixels)",
shadowClipping * 100);
result.issues.push_back(buf);
}
if (highlightClipping > 0.05) {
char buf[128];
snprintf(buf, sizeof(buf), "Highlight clipping detected (%.1f%% of pixels)",
highlightClipping * 100);
result.issues.push_back(buf);
}
// Contrast check
double mean = 0;
for (int i = 0; i < 256; i++) {
mean += i * lumHist.at<float>(i);
}
mean /= totalPixels;
double variance = 0;
for (int i = 0; i < 256; i++) {
variance += std::pow(i - mean, 2) * lumHist.at<float>(i);
}
variance /= totalPixels;
double stdDev = std::sqrt(variance);
if (stdDev < 30) {
char buf[128];
snprintf(buf, sizeof(buf), "Low contrast detected (std dev: %.1f)", stdDev);
result.issues.push_back(buf);
if (result.severity == "normal") result.severity = "low_contrast";
}
if (stdDev > 80) {
char buf[128];
snprintf(buf, sizeof(buf), "High contrast detected (std dev: %.1f)", stdDev);
result.issues.push_back(buf);
if (result.severity == "normal") result.severity = "high_contrast";
}
if (result.issues.empty()) {
result.issues.push_back("No exposure issues detected");
}
return result;
}
/**
* Compare two histograms using multiple metrics.
*/
ComparisonMetrics compareHistograms(const cv::Mat& h1, const cv::Mat& h2) {
ComparisonMetrics metrics;
// Normalize histograms
cv::Mat h1Norm, h2Norm;
cv::normalize(h1, h1Norm, 1.0, 0, cv::NORM_L1);
cv::normalize(h2, h2Norm, 1.0, 0, cv::NORM_L1);
// Correlation
metrics.correlation = cv::compareHist(h1Norm, h2Norm, cv::HISTCMP_CORREL);
// Chi-squared
metrics.chiSquared = cv::compareHist(h1Norm, h2Norm, cv::HISTCMP_CHISQR);
// Bhattacharyya
metrics.bhattacharyya = cv::compareHist(h1Norm, h2Norm, cv::HISTCMP_BHATTACHARYYA);
// Intersection
metrics.intersection = cv::compareHist(h1Norm, h2Norm, cv::HISTCMP_INTERSECT);
// Earth Mover's Distance (manual 1D Wasserstein)
double emd = 0;
double cdf1 = 0, cdf2 = 0;
for (int i = 0; i < 256; i++) {
cdf1 += h1Norm.at<float>(i);
cdf2 += h2Norm.at<float>(i);
emd += std::abs(cdf1 - cdf2);
}
metrics.earthMoversDistance = emd;
return metrics;
}
/**
* Plot histogram using OpenCV drawing functions and save as image.
*/
void plotHistogram(const ChannelHistogram& histData, const std::string& title,
const std::string& outputPath) {
int histW = 800, histH = 600;
int binW = histW / 256;
cv::Mat histImage(histH, histW, CV_8UC3, cv::Scalar(255, 255, 255));
// Normalize all channels for display
cv::Mat rNorm, gNorm, bNorm, lNorm;
cv::normalize(histData.red, rNorm, 0, histH - 50, cv::NORM_MINMAX);
cv::normalize(histData.green, gNorm, 0, histH - 50, cv::NORM_MINMAX);
cv::normalize(histData.blue, bNorm, 0, histH - 50, cv::NORM_MINMAX);
cv::normalize(histData.luminance, lNorm, 0, histH - 50, cv::NORM_MINMAX);
// Draw histograms
for (int i = 1; i < 256; i++) {
// Red
cv::line(histImage,
cv::Point((i - 1) * binW, histH - 30 - cvRound(rNorm.at<float>(i - 1))),
cv::Point(i * binW, histH - 30 - cvRound(rNorm.at<float>(i))),
cv::Scalar(0, 0, 255), 1);
// Green
cv::line(histImage,
cv::Point((i - 1) * binW, histH - 30 - cvRound(gNorm.at<float>(i - 1))),
cv::Point(i * binW, histH - 30 - cvRound(gNorm.at<float>(i))),
cv::Scalar(0, 180, 0), 1);
// Blue
cv::line(histImage,
cv::Point((i - 1) * binW, histH - 30 - cvRound(bNorm.at<float>(i - 1))),
cv::Point(i * binW, histH - 30 - cvRound(bNorm.at<float>(i))),
cv::Scalar(255, 0, 0), 1);
// Luminance
cv::line(histImage,
cv::Point((i - 1) * binW, histH - 30 - cvRound(lNorm.at<float>(i - 1))),
cv::Point(i * binW, histH - 30 - cvRound(lNorm.at<float>(i))),
cv::Scalar(128, 128, 128), 1);
}
// Title
cv::putText(histImage, title, cv::Point(10, 25),
cv::FONT_HERSHEY_SIMPLEX, 0.7, cv::Scalar(0, 0, 0), 1);
// Legend
cv::putText(histImage, "R", cv::Point(histW - 80, 20),
cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 0, 255), 1);
cv::putText(histImage, "G", cv::Point(histW - 60, 20),
cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 180, 0), 1);
cv::putText(histImage, "B", cv::Point(histW - 40, 20),
cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(255, 0, 0), 1);
cv::putText(histImage, "L", cv::Point(histW - 20, 20),
cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(128, 128, 128), 1);
cv::imwrite(outputPath, histImage);
std::cout << "Histogram plot saved to: " << outputPath << std::endl;
}
/**
* Print analysis report to console.
*/
void printReport(const std::string& imagePath, const ChannelHistogram& histData) {
std::cout << "\n" << std::string(60, '=') << std::endl;
std::cout << "Image Histogram Analysis Report" << std::endl;
std::cout << "File: " << imagePath << std::endl;
std::cout << std::string(60, '=') << std::endl;
std::cout << "\nChannel Statistics:" << std::endl;
std::cout << std::left << std::setw(12) << "Channel"
<< std::right << std::setw(8) << "Mean"
<< std::setw(8) << "Median"
<< std::setw(8) << "Mode"
<< std::setw(8) << "StdDev" << std::endl;
std::cout << std::string(48, '-') << std::endl;
const cv::Mat* hists[] = {&histData.red, &histData.green, &histData.blue, &histData.luminance};
const char* names[] = {"Red", "Green", "Blue", "Luminance"};
for (int ch = 0; ch < 4; ch++) {
ChannelStats stats = computeStatistics(*hists[ch], histData.totalPixels);
std::cout << std::left << std::setw(12) << names[ch]
<< std::right << std::fixed << std::setprecision(1)
<< std::setw(8) << stats.mean
<< std::setw(8) << stats.median
<< std::setw(8) << stats.mode
<< std::setw(8) << stats.stdDev << std::endl;
}
ExposureAnalysis exposure = detectExposureIssues(histData.luminance, histData.totalPixels);
std::cout << "\nExposure Analysis:" << std::endl;
std::cout << " Severity: " << exposure.severity << std::endl;
for (const auto& issue : exposure.issues) {
std::cout << " - " << issue << std::endl;
}
std::cout << std::fixed << std::setprecision(1);
std::cout << " Shadows: " << exposure.shadowFraction * 100
<< "% (clipping: " << exposure.shadowClipping * 100 << "%)" << std::endl;
std::cout << " Midtones: " << exposure.midtoneFraction * 100 << "%" << std::endl;
std::cout << " Highlights: " << exposure.highlightFraction * 100
<< "% (clipping: " << exposure.highlightClipping * 100 << "%)" << std::endl;
}
int main(int argc, char* argv[]) {
if (argc < 3) {
std::cout << "Image Histogram Analyzer" << std::endl;
std::cout << "Usage:" << std::endl;
std::cout << " " << argv[0] << " analyze <image> [--plot output.png]" << std::endl;
std::cout << " " << argv[0] << " compare <image1> <image2> [--plot output.png]" << std::endl;
return 0;
}
std::string command = argv[1];
try {
if (command == "analyze") {
std::string imagePath = argv[2];
ChannelHistogram histData = computeHistogram(imagePath);
printReport(imagePath, histData);
// Check for --plot flag
for (int i = 3; i < argc - 1; i++) {
if (std::string(argv[i]) == "--plot") {
plotHistogram(histData, "Histogram: " + imagePath, argv[i + 1]);
break;
}
}
} else if (command == "compare") {
if (argc < 4) {
std::cerr << "Error: compare requires two image paths" << std::endl;
return 1;
}
std::string imagePath1 = argv[2];
std::string imagePath2 = argv[3];
ChannelHistogram hist1 = computeHistogram(imagePath1);
ChannelHistogram hist2 = computeHistogram(imagePath2);
printReport(imagePath1, hist1);
printReport(imagePath2, hist2);
std::cout << "\n" << std::string(60, '=') << std::endl;
std::cout << "Histogram Comparison Metrics" << std::endl;
std::cout << std::string(60, '=') << std::endl;
const cv::Mat* h1s[] = {&hist1.red, &hist1.green, &hist1.blue, &hist1.luminance};
const cv::Mat* h2s[] = {&hist2.red, &hist2.green, &hist2.blue, &hist2.luminance};
const char* names[] = {"Red", "Green", "Blue", "Luminance"};
for (int ch = 0; ch < 4; ch++) {
ComparisonMetrics metrics = compareHistograms(*h1s[ch], *h2s[ch]);
std::cout << "\n " << names[ch] << ":" << std::endl;
std::cout << std::fixed << std::setprecision(4);
std::cout << " Correlation: " << metrics.correlation << std::endl;
std::cout << " Chi-Squared Distance: " << metrics.chiSquared << std::endl;
std::cout << " Bhattacharyya Dist: " << metrics.bhattacharyya << std::endl;
std::cout << " Earth Mover's Dist: " << metrics.earthMoversDistance << std::endl;
std::cout << " Intersection: " << metrics.intersection << std::endl;
}
// Check for --plot flag
for (int i = 4; i < argc - 1; i++) {
if (std::string(argv[i]) == "--plot") {
plotHistogram(hist1, "Comparison: Image 1", argv[i + 1]);
break;
}
}
} else {
std::cerr << "Unknown command: " << command << std::endl;
return 1;
}
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
return 1;
}
return 0;
}
README.md
# Image Histogram Analyzer (C++ - OpenCV) Computes per-channel color histograms, detects exposure issues, and compares histograms between images. ## Dependencies - OpenCV 4.9.0 ## Build ```bash mkdir build && cd build cmake .. make ``` ## Usage ```bash ./histogram_analyzer analyze <image> [--plot output.png] ./histogram_analyzer compare <image1> <image2> [--plot output.png] ```