您好,
会员登录 快速注册
退出 ( 条未读消息)
关于本站 意见反馈 首页

公告:小宅博客网可以开发票了,需要发票的,去群里找群主哈!!
全部文章分类
  • 人工智能 >

  • 编程语言 >

  • WPF系列 >

  • ASP.NET系列 >

  • Linux >

  • 数据库 >

  • 嵌入式 >

  • WEB技术 >

  • PLC系列 >

  • 微服务与框架 >

  • 小宅DIY >

  • 学习资料 >

OpenCv基础 ANN车牌识别 yolov5车牌识别 指针式仪表识别 ROS系列 YOLO Halcon Detectron2 昇腾AI ChatGPT在线体验 英伟达JETSON ChatGLM ChatTTS FunASR 地平线 ByteTrack 魔搭社区 LangChain
C C# C++ Python Java Go
WPF
ASP.NET小功能 GPS定位系统-MVC GPS定位系统-VUE ASP.NET WebRTC
Linux Linux内核 Shell MakeFile
MySql SqlServer Oracle
STM8 STM32 51单片机
VUE入门 HTML JavaScript CSS layui镜像网站 ElementUi中文官网 element-plus 图标
三菱 欧姆龙 西门子 施耐德 松下 台达
IOTSharp IOTGateway ABP FRAMEWORK Docker
亚克力音响 编程仙途:智驭万法
面试题与技巧 Python入门技能树 微软C#教程
首页 编程之美 工具下载 全国就业 流量地图 文心一言
OpenCv基础
内容介绍与资料分享 C# OpenCv环境搭建 C++ OpenCv环境搭建 Python OpenCv环境搭建 Java OpenCv环境搭建 OpenCv组件结构解析 OpenCv命名规范 OpenCv基本专业术语与方法 OpenCV 常用函数与构造体详细说明 创建画布 打开一张图片 利用imwrite生成透明png图像 图像打开、混合显示和输出 图像腐蚀 blur图像模糊(均值滤波) sobel边缘检测 canny边缘检测 Scharr滤波器 程序性能检测及优化 视频播放 摄像头录像与播放 双摄像头操作与图像相似度检测 颜色空间转换与物体追踪 彩色目标追踪 光流法运动目标检测 OpenCV中的稠密光流 背景减除 点追踪 人脸识别 支持向量机之SVM引导 支持向量机之处理线性不可分数据 ROI矩形截取 鼠标绘制矩形 用OpenCV进行基本绘图 绘图函数(python中文显示) 把鼠标当画笔 用滑动条做调色板 图像的基础操作 图像上的算术运算 多通道图像混合 图像的亮度、对比度调整 XML和YAML文件的写入 XML和YAML文件的读取 卷积操作 三种线性滤波 两种非线性滤波 7种图像处理形态学(1) 7种图像处理形态学(2) 漫水填充 图像缩放与图像金字塔 二值化基本阈值操作 图像阈值 Laplacian图像变换(拉普拉斯算子) 霍夫变换HoughLines边缘检测与线性矢量 霍夫变换HoughLinesP边缘检测与线性矢量 霍夫变换HoughCircles边缘检测与线性矢量 LSD快速直线检测 几何变换 remap重映射 remap实现多种重映射 仿射变换综合示例 直方图均衡化 CLAHE有限对比适应性直方图均衡化 draw最大的轮廓 轮廓的性质 点到多边形的最短距离 形状匹配 椭圆拟合与直线拟合 基础轮廓查找 查找并绘制轮廓综合示例 凸缺陷/凸包检测 凸包检测 创建包围轮廓的矩形边界 创建包围轮廓的圆形边 创建包围轮廓的矩形和圆形边界框 查找和绘制图片轮廓矩 分水岭算法 图像修补 H-S二维直方图的绘制/2D直方图 一维直方图的绘制 RGB三色直方图的绘制 直方图对比 使用掩膜绘制直方图 直方图反向投影 模板匹配 多对象模板匹配 cornerHarris角点检测 cornerHarris角点检测综合示例 Shi-Tomasi角点检测 亚像素级角点检测 角点检测的FAST算法(FAST特征检测器) 颜色识别 warpPerspective透视变换 SURF/SIFT特征点检测 SURF/SIFT特征描述 使用FLANN进行特征点匹配 FLANN结合SURF/SIFT进行关键点的描述和匹配 寻找已知物体(一) 寻找已知物体(二) 目标查找与跟踪 - Meanshift与CamShift BRIEF描述符 ORB ORB算法描述与匹配 LUT 图像灰度调整 离散傅里叶变换 双目摄像头与图像拼接 环境亮度检测 stitching 全景拼接 Maze-Solver迷宫解密 使用Haar分类器之面部检测 使用Haar分类器之行人检测 OpenCv Haar/LBP/HOG分类器-人脸识别 斑点检测 使用GrabCut算法进行交互式前景提取 对极几何 摄像机标定 姿势估计 立体图像中的深度地图 OpenCv中的KNN KNN手写数字识别 ​SVM手写数字识别(使用SVM进行手写数据OCR) 英文字母的OCR 预测手写数字(预测不准) K值聚类(一) K值聚类(二) 计算摄影学-图像去噪 高动态范围成像(HDRI或HDR) MSER区域检测 二维码、条形码识别 删除图像中的水印 OpenCv深度学习dnn Kinect-深度相机 OpenCv常用数学算法 360度旋转直线绘制 向量延长线上的像素扫描 Tools工具包-窗体分配
摄像头录像与播放
颜色空间转换与物体追踪
激萌の小宅 小宅博客网 OpenCv基础

文章作者:激萌の小宅

促销:¥0

价格:¥0

配送方式: 购买后立即生效(如购买异常,请联系站长)
付款之后一定要等待自动跳转结束,否则购买可能会失败
  • 0 天

    有效期

  • 0

    总销量

  • 0

    累计评价

双摄像头操作与图像相似度检测

本章节内容是博主网上收集的,主要内容包括:双摄像头的调用方法、图像窗体的固定、均方误差(MSE)、结构相似度指数(SSIM)、峰值信噪比

参考博客:https://www.freesion.com/article/50591020225/


当前系列所有demo下载地址:

https://github.com/GaoRenBao/OpenCv4-Demo

不同编程语言对应的OpenCv版本以及开发环境信息如下: 

语言

OpenCv版本

IDE

C#

OpenCvSharp4.4.8.0.20230708

Visual Studio 2022

C++

OpenCv-4.5.5-vc14_vc15

Visual Studio 2022

Python

OpenCv-Python (4.6.0.66)

PyCharm Community Edition 2022.1.3


C#版本运行效果及代码如下:

6379670641173703788070263 (1).gif

using OpenCvSharp;
using System;
using System.Windows.Forms;

namespace WindowsFormsApp
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        #region 双摄像头调用
        /// <summary>
        /// 双摄像头调用
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button1_Click(object sender, EventArgs e)
        {
            VideoCapture Cap0 = new VideoCapture();
            VideoCapture Cap1 = new VideoCapture();

            // 打开ID为0的摄像头
            Cap0.Open(0);
            // 判断摄像头是否成功打开
            if (!Cap0.IsOpened())
            {
                MessageBox.Show("摄像头0打开失败.");
                return;
            }

            // 打开ID为1的摄像头
            Cap1.Open(1);
            // 判断摄像头是否成功打开
            if (!Cap1.IsOpened())
            {
                MessageBox.Show("摄像头1打开失败.");
                return;
            }

            Cap0.Set(VideoCaptureProperties.FrameWidth, 320);  // 设置采集的图像宽度:320
            Cap0.Set(VideoCaptureProperties.FrameHeight, 240); // 设置采集的图像高度:240
            Cap1.Set(VideoCaptureProperties.FrameWidth, 320);  // 设置采集的图像宽度:320
            Cap1.Set(VideoCaptureProperties.FrameHeight, 240); // 设置采集的图像高度:240

            Mat frame0 = new Mat(), frame1 = new Mat();

            while (Cap0.IsOpened() && Cap1.IsOpened())
            {
                if (Cap0.Read(frame0))
                {
                    Cv2.ImShow("frame0", frame0);
                    Cv2.SetWindowTitle("frame0", "On Top");
                }
                if (Cap1.Read(frame1))
                {
                    Cv2.ImShow("frame1", frame1);
                    Cv2.MoveWindow("frame1", frame0.Cols, 0);
                }

                if (Cv2.WaitKey(2) == 27)// ESC按钮
                    break;
            }

            // When everything done, release the capture
            Cap0.Release();
            Cap1.Release();
        }
        #endregion

        #region 计算相似度

        /// <summary>
        /// 计算相似度
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button2_Click(object sender, EventArgs e)
        {
            VideoCapture Cap = new VideoCapture();
            Cap.Open(0);

            // 打开ID为1的摄像头
            Cap.Open(0);
            // 判断摄像头是否成功打开
            if (!Cap.IsOpened())
            {
                MessageBox.Show("摄像头打开失败.");
                return;
            }

            Mat frame = new Mat();
            Mat temp = new Mat();
            Mat gray = new Mat();

            if (Cap.Read(frame))
            {
                Cv2.CvtColor(frame, temp, ColorConversionCodes.BGR2GRAY);
                Cv2.ImShow("temp", temp);
            }

            while (Cap.IsOpened())
            {
                if (Cap.Read(frame))
                {
                    Cv2.CvtColor(frame, gray, ColorConversionCodes.BGR2GRAY);
                    Cv2.ImShow("gray", gray);
                    using (similarity si = new similarity())
                    {
                        label1.Text = $"mse:{si.mse(temp, gray)}\r\n";
                    }
                    using (similarity si = new similarity())
                    {
                        label1.Text += $"ssim:{si.ssim(temp, gray)}\r\n";
                    }
                    using (similarity si = new similarity())
                    {
                        label1.Text += $"psnr:{si.psnr(temp, gray)}";
                    }
                }
                Cv2.WaitKey(30);
            }
            Cap.Release();
        }
        #endregion
    }

    /// <summary>
    /// 相似度
    /// </summary>
    public  class similarity : IDisposable
    {
        void IDisposable.Dispose() { }
        public void Dispose() { }

        /// <summary>
        /// 均方误差(MSE)
        /// </summary>
        /// <param name="imageA"></param>
        /// <param name="imageB"></param>
        /// <returns></returns>
        public double mse(Mat imageA, Mat imageB)
        {
            // 在使用OpenCV时可以通过矩阵操作来避免for循环嵌套计算。
            // 需要注意的是乘除操作一般要注意将图像本身的uint8转换成float后再做,否则精度误差可能会导致较大偏差。
            Mat M1 = imageA.Clone();
            Mat M2 = imageB.Clone();
            Mat Diff = new Mat();

            // 提前转换为32F精度
            M1.ConvertTo(M1, MatType.CV_32F);
            M2.ConvertTo(M2, MatType.CV_32F);
            Diff.ConvertTo(Diff, MatType.CV_32F);

            Cv2.Absdiff(M1, M2, Diff); //  Diff = | M1 - M2 |
            Diff = Diff.Mul(Diff);     // | M1 - M2 |.^2
            Scalar S = Cv2.Sum(Diff);  //分别计算每个通道的元素之和

            double sse;   // square error
            if (Diff.Channels() == 3)
                sse = S.Val0 + S.Val1 + S.Val2;  // sum of all channels
            else
                sse = S.Val0;
            long nTotalElement = M2.Channels() * M2.Total();
            double mse = (sse / (double)nTotalElement);
            return mse;
        }

        // 结构相似度指数(SSIM),是一种衡量两幅图像相似度的指标,
        // 也是一种全参考的图像质量评价指标,它分别从亮度、对比度、结构三方面度量图像相似性
        // 参考: https://www.freesion.com/article/50591020225/
        // C1,C2和C3为常数,是为了避免分母为0而维持稳定。
        // L = 255( 是像素值的动态范围,一般都取为255)。
        // K1 = 0.01, K2 = 0.03。结构相似性的范围为 - 1 到 1 。
        // 当两张图像一模一样时,SSIM的值等于1。
        public double ssim(Mat i1, Mat i2)
        {
            // 跑久了,会报错。。。。
            const double C1 = 6.5025, C2 = 58.5225;
            int d = MatType.CV_32F;
            Mat I1 = new Mat(), I2 = new Mat();
            i1.ConvertTo(I1, d);
            i2.ConvertTo(I2, d);
            Mat I1_2 = I1.Mul(I1);
            Mat I2_2 = I2.Mul(I2);
            Mat I1_I2 = I1.Mul(I2);
            Mat mu1 = new Mat(), mu2 = new Mat();
            Cv2.GaussianBlur(I1, mu1, new Size(11, 11), 1.5);
            Cv2.GaussianBlur(I2, mu2, new Size(11, 11), 1.5);
            Mat mu1_2 = mu1.Mul(mu1);
            Mat mu2_2 = mu2.Mul(mu2);
            Mat mu1_mu2 = mu1.Mul(mu2);
            Mat sigma1_2 = new Mat(), sigam2_2 = new Mat(), sigam12 = new Mat();
            Cv2.GaussianBlur(I1_2, sigma1_2, new Size(11, 11), 1.5);
            sigma1_2 -= mu1_2;

            Cv2.GaussianBlur(I2_2, sigam2_2, new Size(11, 11), 1.5);
            sigam2_2 -= mu2_2;

            Cv2.GaussianBlur(I1_I2, sigam12, new Size(11, 11), 1.5);
            sigam12 -= mu1_mu2;
            Mat t1, t2, t3;
            t1 = 2 * mu1_mu2 + C1;
            t2 = 2 * sigam12 + C2;
            t3 = t1.Mul(t2);

            t1 = mu1_2 + mu2_2 + C1;
            t2 = sigma1_2 + sigam2_2 + C2;
            t1 = t1.Mul(t2);

            Mat ssim_map = new Mat();
            Cv2.Divide(t3, t1, ssim_map);
            Scalar mssim = Cv2.Mean(ssim_map);

            double ssim = (mssim.Val0 + mssim.Val1 + mssim.Val2) / 3;
            return ssim;
        }

        // 峰值信噪比,一种评价图像的客观标准,用来评估图像的保真性。
        // 峰值信噪比经常用作图像压缩等领域中信号重建质量的测量方法,
        // 它常简单地通过均方差(MSE)进行定义,使用两个m×n单色图像I和K。PSNR的单位为分贝
        // PSNR值越大,就代表失真越少,图像压缩中典型的峰值信噪比值在 30 到 40dB 之间,小于30dB时考虑图像无法忍受。
        public double psnr(Mat I1, Mat I2)
        {
            //注意,当两幅图像一样时这个函数计算出来的psnr为0 
            Mat s1 = new Mat();
            Cv2.Absdiff(I1, I2, s1);
            s1.ConvertTo(s1, MatType.CV_32F);//转换为32位的float类型,8位不能计算平方  
            s1 = s1.Mul(s1);
            Scalar s = Cv2.Sum(s1);  //计算每个通道的和  
            double sse = s.Val0 + s.Val1 + s.Val2;
            if (sse <= 1e-10) // for small values return zero  
                return 0;
            else
            {
                double mse = sse / (double)(I1.Channels() * I1.Total()); //  sse/(w*h*3)  
                double psnr = 10.0 * Math.Log10((255 * 255) / mse);
                return psnr;
            }
        }
    }
}


C++版本运行效果及代码如下:

6379670665036039114262581 (1).gif

#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>

using namespace cv;
using namespace std;

// 双摄像头调用
void demo1()
{
    VideoCapture cap0;
    VideoCapture cap1;
    cap0.open(0);
    cap1.open(1);
    if (!cap0.isOpened())
    {
        cout << "Error0" << endl;
        return;
    }
    if (!cap1.isOpened())
    {
        cout << "Error1" << endl;
        return;
    }

    bool ret0 = cap0.set(3, 320);
    bool ret1 = cap0.set(4, 240);
    bool ret2 = cap1.set(3, 320);
    bool ret3 = cap1.set(4, 240);

    while (cap0.isOpened() && cap1.isOpened())
    {
        Mat frame0, frame1;
        cap0 >> frame0;
        cap1 >> frame1;
        if (!frame0.empty())
        {
            imshow("frame0", frame0);
            setWindowTitle("frame0", "On Top");
        }
        if (!frame1.empty())
        {
            imshow("frame1", frame1);
            moveWindow("frame1", frame0.cols, 0);
            //moveWindow("frame1", 320, 40);
        }

        if (waitKey(2) == 27)// ESC按钮
            break;
    }

    // When everything done, release the capture
    cap0.release();
    cap1.release();
    destroyAllWindows();
}

// 均方误差(MSE)
double mse(Mat imageA, Mat imageB)
{
    // 在使用OpenCV时可以通过矩阵操作来避免for循环嵌套计算。
    // 需要注意的是乘除操作一般要注意将图像本身的uint8转换成float后再做,否则精度误差可能会导致较大偏差。
    cv::Mat M1 = imageA.clone();
    cv::Mat M2 = imageB.clone();
    cv::Mat Diff;
    // 提前转换为32F精度
    M1.convertTo(M1, CV_32F);
    M2.convertTo(M2, CV_32F);
    Diff.convertTo(Diff, CV_32F);
    cv::absdiff(M1, M2, Diff); //  Diff = | M1 - M2 |
    Diff = Diff.mul(Diff);     // | M1 - M2 |.^2
    cv::Scalar S = cv::sum(Diff);  //分别计算每个通道的元素之和

    double sse;   // square error
    if (Diff.channels() == 3)
        sse = S.val[0] + S.val[1] + S.val[2];  // sum of all channels
    else
        sse = S.val[0];
    int nTotalElement = M2.channels() * M2.total();
    double mse = (sse / (double)nTotalElement);  //
    return mse;
}

// 结构相似度指数(SSIM),是一种衡量两幅图像相似度的指标,
// 也是一种全参考的图像质量评价指标,它分别从亮度、对比度、结构三方面度量图像相似性
// 参考: https://www.freesion.com/article/50591020225/
// C1,C2和C3为常数,是为了避免分母为0而维持稳定。
// L = 255( 是像素值的动态范围,一般都取为255)。
// K1 = 0.01, K2 = 0.03。结构相似性的范围为 - 1 到 1 。
// 当两张图像一模一样时,SSIM的值等于1。
double ssim(Mat& i1, Mat& i2) {
    const double C1 = 6.5025, C2 = 58.5225;
    int d = CV_32F;
    Mat I1, I2;
    i1.convertTo(I1, d);
    i2.convertTo(I2, d);
    Mat I1_2 = I1.mul(I1);
    Mat I2_2 = I2.mul(I2);
    Mat I1_I2 = I1.mul(I2);
    Mat mu1, mu2;
    GaussianBlur(I1, mu1, Size(11, 11), 1.5);
    GaussianBlur(I2, mu2, Size(11, 11), 1.5);
    Mat mu1_2 = mu1.mul(mu1);
    Mat mu2_2 = mu2.mul(mu2);
    Mat mu1_mu2 = mu1.mul(mu2);
    Mat sigma1_2, sigam2_2, sigam12;
    GaussianBlur(I1_2, sigma1_2, Size(11, 11), 1.5);
    sigma1_2 -= mu1_2;

    GaussianBlur(I2_2, sigam2_2, Size(11, 11), 1.5);
    sigam2_2 -= mu2_2;

    GaussianBlur(I1_I2, sigam12, Size(11, 11), 1.5);
    sigam12 -= mu1_mu2;
    Mat t1, t2, t3;
    t1 = 2 * mu1_mu2 + C1;
    t2 = 2 * sigam12 + C2;
    t3 = t1.mul(t2);

    t1 = mu1_2 + mu2_2 + C1;
    t2 = sigma1_2 + sigam2_2 + C2;
    t1 = t1.mul(t2);

    Mat ssim_map;
    divide(t3, t1, ssim_map);
    Scalar mssim = mean(ssim_map);

    double ssim = (mssim.val[0] + mssim.val[1] + mssim.val[2]) / 3;
    return ssim;
}

// 峰值信噪比,一种评价图像的客观标准,用来评估图像的保真性。
// 峰值信噪比经常用作图像压缩等领域中信号重建质量的测量方法,
// 它常简单地通过均方差(MSE)进行定义,使用两个m×n单色图像I和K。PSNR的单位为分贝
// PSNR值越大,就代表失真越少,图像压缩中典型的峰值信噪比值在 30 到 40dB 之间,小于30dB时考虑图像无法忍受。
double psnr(Mat& I1, Mat& I2) { //注意,当两幅图像一样时这个函数计算出来的psnr为0 
    Mat s1;
    absdiff(I1, I2, s1);
    s1.convertTo(s1, CV_32F);//转换为32位的float类型,8位不能计算平方  
    s1 = s1.mul(s1);
    Scalar s = sum(s1);  //计算每个通道的和  
    double sse = s.val[0] + s.val[1] + s.val[2];
    if (sse <= 1e-10) // for small values return zero  
        return 0;
    else
    {
        double mse = sse / (double)(I1.channels() * I1.total()); //  sse/(w*h*3)  
        double psnr = 10.0 * log10((255 * 255) / mse);
        return psnr;
    }
}

// 图像相似度对比
void demo2()
{
    VideoCapture cap;
    cap.open(0);

    if (!cap.isOpened())
    {
        cout << "Error0" << endl;
        return;
    }

    Mat frame;
    cap >> frame;

    Mat temp;
    cvtColor(frame, temp, COLOR_BGR2GRAY);
    imshow("temp", temp);

    while (cap.isOpened())
    {
        cap >> frame;
        if (frame.empty())
        {
            continue;
        }

        Mat gray;
        cvtColor(frame, gray, COLOR_BGR2GRAY);
        imshow("gray", gray);
        
        cout << "mse:" << mse(temp, gray) << "\tssim:" << ssim(temp, gray) << "\tpsnr:" << psnr(temp, gray) << endl;
        waitKey(200);
    }

    cap.release();
    destroyAllWindows();
}

void main()
{
    demo2();
}


Python版本代码如下:

python效果和C++差不多,这里就不进行图像演示了,感兴趣的童鞋,可以自己跑一下

# 需要安装 pip install Scikit-Image

# 老版本
# from skimage.measure import compare_ssim as ssim
# from skimage.measure import compare_mse as mse

# 新版本
from skimage.metrics import structural_similarity as ssim
from skimage.metrics import mean_squared_error as mse

import matplotlib.pyplot as plt
import numpy as np
import cv2


# 双摄像头调用
def demo1():
    cap0 = cv2.VideoCapture(0)
    cap1 = cv2.VideoCapture(1)
    ret = cap0.set(3, 320)
    ret = cap0.set(4, 240)
    ret = cap1.set(3, 320)
    ret = cap1.set(4, 240)

    while cap0.isOpened() and cap1.isOpened():
        ret0, frame0 = cap0.read()
        ret1, frame1 = cap1.read()

        if ret0:
            cv2.imshow('frame0', frame0)
            cv2.setWindowTitle('frame0', 'On Top')
        if ret1:
            cv2.imshow('frame1', frame1)
            # cv2.moveWindow('frame1', x=frame0.shape[1], y=0)
            cv2.moveWindow('frame1', x=320, y=40)

        key = cv2.waitKey(delay=2)
        if key == ord("q"):
            break

    # When everything done, release the capture
    cap0.release()
    cap1.release()
    cv2.destroyAllWindows()


# mse 算法
def my_mse(imageA, imageB):
    # the 'Mean Squared Error' between the two images is the
    # sum of the squared difference between the two images;
    # NOTE: the two images must have the same dimension
    err = np.sum((imageA.astype("float") - imageB.astype("float")) * 2)
    err /= float(imageA.shape[0] * imageA.shape[1])

    # return the MSE, the lower the error, the more "similar"
    # the two images are
    return err


# 图像相似度计算(感觉不咋滴)
def demo2():
    cap = cv2.VideoCapture(0)
    if cap.isOpened() == False:
        print('Open Camera Error.')
        return

    ret = cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 640)
    ret = cap.set(cv2.CAP_PROP_FRAME_WIDTH, 480)
    title = 'camera compare'
    plt.ion()

    ret, frame = cap.read()
    if ret == True:
        temp = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        cv2.imshow("temp", temp)
        while cap.isOpened():
            ret, frame = cap.read()
            if ret == True:
                gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
                cv2.imshow("gray", gray)

                m = mse(temp, gray)  # 均方误差(MSE)
                s = ssim(temp, gray)  # 结构相似度指数(SSIM)
                print("MSE: %.2f, SSIM: %.2f" % (m, s))
                temp = gray.copy()
                cv2.waitKey(500)


# 运行demo2,如果想运行其他demo,改一下这个即可
if __name__ == '__main__':
    # demo1()
    demo2()


摄像头录像与播放
颜色空间转换与物体追踪

友情链接: CSDN激萌の小宅 95知识库 自考题库 罗分明个人网络博客 精益编程leanboot

小宅博客  www.bilibili996.com All Rights Reserved. 备案号: 闽ICP备2024034575号

网站经营许可证  福建省福州市 Copyright©2021-2025 版权所有

小宅博客
首页 智能家居 地图定位
公告:小宅博客网可以开发票了,需要发票的,去群里找群主哈!!

文章作者:激萌の小宅

促销:¥0

价格:¥0

配送方式: 购买后立即生效(如购买异常,请联系站长)
付款之后一定要等待自动跳转结束,否则购买可能会失败
  • 0 天

    有效期

  • 0

    总销量

  • 0

    累计评价

双摄像头操作与图像相似度检测

本章节内容是博主网上收集的,主要内容包括:双摄像头的调用方法、图像窗体的固定、均方误差(MSE)、结构相似度指数(SSIM)、峰值信噪比

参考博客:https://www.freesion.com/article/50591020225/


当前系列所有demo下载地址:

https://github.com/GaoRenBao/OpenCv4-Demo

不同编程语言对应的OpenCv版本以及开发环境信息如下: 

语言

OpenCv版本

IDE

C#

OpenCvSharp4.4.8.0.20230708

Visual Studio 2022

C++

OpenCv-4.5.5-vc14_vc15

Visual Studio 2022

Python

OpenCv-Python (4.6.0.66)

PyCharm Community Edition 2022.1.3


C#版本运行效果及代码如下:

6379670641173703788070263 (1).gif

using OpenCvSharp;
using System;
using System.Windows.Forms;

namespace WindowsFormsApp
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        #region 双摄像头调用
        /// <summary>
        /// 双摄像头调用
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button1_Click(object sender, EventArgs e)
        {
            VideoCapture Cap0 = new VideoCapture();
            VideoCapture Cap1 = new VideoCapture();

            // 打开ID为0的摄像头
            Cap0.Open(0);
            // 判断摄像头是否成功打开
            if (!Cap0.IsOpened())
            {
                MessageBox.Show("摄像头0打开失败.");
                return;
            }

            // 打开ID为1的摄像头
            Cap1.Open(1);
            // 判断摄像头是否成功打开
            if (!Cap1.IsOpened())
            {
                MessageBox.Show("摄像头1打开失败.");
                return;
            }

            Cap0.Set(VideoCaptureProperties.FrameWidth, 320);  // 设置采集的图像宽度:320
            Cap0.Set(VideoCaptureProperties.FrameHeight, 240); // 设置采集的图像高度:240
            Cap1.Set(VideoCaptureProperties.FrameWidth, 320);  // 设置采集的图像宽度:320
            Cap1.Set(VideoCaptureProperties.FrameHeight, 240); // 设置采集的图像高度:240

            Mat frame0 = new Mat(), frame1 = new Mat();

            while (Cap0.IsOpened() && Cap1.IsOpened())
            {
                if (Cap0.Read(frame0))
                {
                    Cv2.ImShow("frame0", frame0);
                    Cv2.SetWindowTitle("frame0", "On Top");
                }
                if (Cap1.Read(frame1))
                {
                    Cv2.ImShow("frame1", frame1);
                    Cv2.MoveWindow("frame1", frame0.Cols, 0);
                }

                if (Cv2.WaitKey(2) == 27)// ESC按钮
                    break;
            }

            // When everything done, release the capture
            Cap0.Release();
            Cap1.Release();
        }
        #endregion

        #region 计算相似度

        /// <summary>
        /// 计算相似度
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button2_Click(object sender, EventArgs e)
        {
            VideoCapture Cap = new VideoCapture();
            Cap.Open(0);

            // 打开ID为1的摄像头
            Cap.Open(0);
            // 判断摄像头是否成功打开
            if (!Cap.IsOpened())
            {
                MessageBox.Show("摄像头打开失败.");
                return;
            }

            Mat frame = new Mat();
            Mat temp = new Mat();
            Mat gray = new Mat();

            if (Cap.Read(frame))
            {
                Cv2.CvtColor(frame, temp, ColorConversionCodes.BGR2GRAY);
                Cv2.ImShow("temp", temp);
            }

            while (Cap.IsOpened())
            {
                if (Cap.Read(frame))
                {
                    Cv2.CvtColor(frame, gray, ColorConversionCodes.BGR2GRAY);
                    Cv2.ImShow("gray", gray);
                    using (similarity si = new similarity())
                    {
                        label1.Text = $"mse:{si.mse(temp, gray)}\r\n";
                    }
                    using (similarity si = new similarity())
                    {
                        label1.Text += $"ssim:{si.ssim(temp, gray)}\r\n";
                    }
                    using (similarity si = new similarity())
                    {
                        label1.Text += $"psnr:{si.psnr(temp, gray)}";
                    }
                }
                Cv2.WaitKey(30);
            }
            Cap.Release();
        }
        #endregion
    }

    /// <summary>
    /// 相似度
    /// </summary>
    public  class similarity : IDisposable
    {
        void IDisposable.Dispose() { }
        public void Dispose() { }

        /// <summary>
        /// 均方误差(MSE)
        /// </summary>
        /// <param name="imageA"></param>
        /// <param name="imageB"></param>
        /// <returns></returns>
        public double mse(Mat imageA, Mat imageB)
        {
            // 在使用OpenCV时可以通过矩阵操作来避免for循环嵌套计算。
            // 需要注意的是乘除操作一般要注意将图像本身的uint8转换成float后再做,否则精度误差可能会导致较大偏差。
            Mat M1 = imageA.Clone();
            Mat M2 = imageB.Clone();
            Mat Diff = new Mat();

            // 提前转换为32F精度
            M1.ConvertTo(M1, MatType.CV_32F);
            M2.ConvertTo(M2, MatType.CV_32F);
            Diff.ConvertTo(Diff, MatType.CV_32F);

            Cv2.Absdiff(M1, M2, Diff); //  Diff = | M1 - M2 |
            Diff = Diff.Mul(Diff);     // | M1 - M2 |.^2
            Scalar S = Cv2.Sum(Diff);  //分别计算每个通道的元素之和

            double sse;   // square error
            if (Diff.Channels() == 3)
                sse = S.Val0 + S.Val1 + S.Val2;  // sum of all channels
            else
                sse = S.Val0;
            long nTotalElement = M2.Channels() * M2.Total();
            double mse = (sse / (double)nTotalElement);
            return mse;
        }

        // 结构相似度指数(SSIM),是一种衡量两幅图像相似度的指标,
        // 也是一种全参考的图像质量评价指标,它分别从亮度、对比度、结构三方面度量图像相似性
        // 参考: https://www.freesion.com/article/50591020225/
        // C1,C2和C3为常数,是为了避免分母为0而维持稳定。
        // L = 255( 是像素值的动态范围,一般都取为255)。
        // K1 = 0.01, K2 = 0.03。结构相似性的范围为 - 1 到 1 。
        // 当两张图像一模一样时,SSIM的值等于1。
        public double ssim(Mat i1, Mat i2)
        {
            // 跑久了,会报错。。。。
            const double C1 = 6.5025, C2 = 58.5225;
            int d = MatType.CV_32F;
            Mat I1 = new Mat(), I2 = new Mat();
            i1.ConvertTo(I1, d);
            i2.ConvertTo(I2, d);
            Mat I1_2 = I1.Mul(I1);
            Mat I2_2 = I2.Mul(I2);
            Mat I1_I2 = I1.Mul(I2);
            Mat mu1 = new Mat(), mu2 = new Mat();
            Cv2.GaussianBlur(I1, mu1, new Size(11, 11), 1.5);
            Cv2.GaussianBlur(I2, mu2, new Size(11, 11), 1.5);
            Mat mu1_2 = mu1.Mul(mu1);
            Mat mu2_2 = mu2.Mul(mu2);
            Mat mu1_mu2 = mu1.Mul(mu2);
            Mat sigma1_2 = new Mat(), sigam2_2 = new Mat(), sigam12 = new Mat();
            Cv2.GaussianBlur(I1_2, sigma1_2, new Size(11, 11), 1.5);
            sigma1_2 -= mu1_2;

            Cv2.GaussianBlur(I2_2, sigam2_2, new Size(11, 11), 1.5);
            sigam2_2 -= mu2_2;

            Cv2.GaussianBlur(I1_I2, sigam12, new Size(11, 11), 1.5);
            sigam12 -= mu1_mu2;
            Mat t1, t2, t3;
            t1 = 2 * mu1_mu2 + C1;
            t2 = 2 * sigam12 + C2;
            t3 = t1.Mul(t2);

            t1 = mu1_2 + mu2_2 + C1;
            t2 = sigma1_2 + sigam2_2 + C2;
            t1 = t1.Mul(t2);

            Mat ssim_map = new Mat();
            Cv2.Divide(t3, t1, ssim_map);
            Scalar mssim = Cv2.Mean(ssim_map);

            double ssim = (mssim.Val0 + mssim.Val1 + mssim.Val2) / 3;
            return ssim;
        }

        // 峰值信噪比,一种评价图像的客观标准,用来评估图像的保真性。
        // 峰值信噪比经常用作图像压缩等领域中信号重建质量的测量方法,
        // 它常简单地通过均方差(MSE)进行定义,使用两个m×n单色图像I和K。PSNR的单位为分贝
        // PSNR值越大,就代表失真越少,图像压缩中典型的峰值信噪比值在 30 到 40dB 之间,小于30dB时考虑图像无法忍受。
        public double psnr(Mat I1, Mat I2)
        {
            //注意,当两幅图像一样时这个函数计算出来的psnr为0 
            Mat s1 = new Mat();
            Cv2.Absdiff(I1, I2, s1);
            s1.ConvertTo(s1, MatType.CV_32F);//转换为32位的float类型,8位不能计算平方  
            s1 = s1.Mul(s1);
            Scalar s = Cv2.Sum(s1);  //计算每个通道的和  
            double sse = s.Val0 + s.Val1 + s.Val2;
            if (sse <= 1e-10) // for small values return zero  
                return 0;
            else
            {
                double mse = sse / (double)(I1.Channels() * I1.Total()); //  sse/(w*h*3)  
                double psnr = 10.0 * Math.Log10((255 * 255) / mse);
                return psnr;
            }
        }
    }
}


C++版本运行效果及代码如下:

6379670665036039114262581 (1).gif

#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>

using namespace cv;
using namespace std;

// 双摄像头调用
void demo1()
{
    VideoCapture cap0;
    VideoCapture cap1;
    cap0.open(0);
    cap1.open(1);
    if (!cap0.isOpened())
    {
        cout << "Error0" << endl;
        return;
    }
    if (!cap1.isOpened())
    {
        cout << "Error1" << endl;
        return;
    }

    bool ret0 = cap0.set(3, 320);
    bool ret1 = cap0.set(4, 240);
    bool ret2 = cap1.set(3, 320);
    bool ret3 = cap1.set(4, 240);

    while (cap0.isOpened() && cap1.isOpened())
    {
        Mat frame0, frame1;
        cap0 >> frame0;
        cap1 >> frame1;
        if (!frame0.empty())
        {
            imshow("frame0", frame0);
            setWindowTitle("frame0", "On Top");
        }
        if (!frame1.empty())
        {
            imshow("frame1", frame1);
            moveWindow("frame1", frame0.cols, 0);
            //moveWindow("frame1", 320, 40);
        }

        if (waitKey(2) == 27)// ESC按钮
            break;
    }

    // When everything done, release the capture
    cap0.release();
    cap1.release();
    destroyAllWindows();
}

// 均方误差(MSE)
double mse(Mat imageA, Mat imageB)
{
    // 在使用OpenCV时可以通过矩阵操作来避免for循环嵌套计算。
    // 需要注意的是乘除操作一般要注意将图像本身的uint8转换成float后再做,否则精度误差可能会导致较大偏差。
    cv::Mat M1 = imageA.clone();
    cv::Mat M2 = imageB.clone();
    cv::Mat Diff;
    // 提前转换为32F精度
    M1.convertTo(M1, CV_32F);
    M2.convertTo(M2, CV_32F);
    Diff.convertTo(Diff, CV_32F);
    cv::absdiff(M1, M2, Diff); //  Diff = | M1 - M2 |
    Diff = Diff.mul(Diff);     // | M1 - M2 |.^2
    cv::Scalar S = cv::sum(Diff);  //分别计算每个通道的元素之和

    double sse;   // square error
    if (Diff.channels() == 3)
        sse = S.val[0] + S.val[1] + S.val[2];  // sum of all channels
    else
        sse = S.val[0];
    int nTotalElement = M2.channels() * M2.total();
    double mse = (sse / (double)nTotalElement);  //
    return mse;
}

// 结构相似度指数(SSIM),是一种衡量两幅图像相似度的指标,
// 也是一种全参考的图像质量评价指标,它分别从亮度、对比度、结构三方面度量图像相似性
// 参考: https://www.freesion.com/article/50591020225/
// C1,C2和C3为常数,是为了避免分母为0而维持稳定。
// L = 255( 是像素值的动态范围,一般都取为255)。
// K1 = 0.01, K2 = 0.03。结构相似性的范围为 - 1 到 1 。
// 当两张图像一模一样时,SSIM的值等于1。
double ssim(Mat& i1, Mat& i2) {
    const double C1 = 6.5025, C2 = 58.5225;
    int d = CV_32F;
    Mat I1, I2;
    i1.convertTo(I1, d);
    i2.convertTo(I2, d);
    Mat I1_2 = I1.mul(I1);
    Mat I2_2 = I2.mul(I2);
    Mat I1_I2 = I1.mul(I2);
    Mat mu1, mu2;
    GaussianBlur(I1, mu1, Size(11, 11), 1.5);
    GaussianBlur(I2, mu2, Size(11, 11), 1.5);
    Mat mu1_2 = mu1.mul(mu1);
    Mat mu2_2 = mu2.mul(mu2);
    Mat mu1_mu2 = mu1.mul(mu2);
    Mat sigma1_2, sigam2_2, sigam12;
    GaussianBlur(I1_2, sigma1_2, Size(11, 11), 1.5);
    sigma1_2 -= mu1_2;

    GaussianBlur(I2_2, sigam2_2, Size(11, 11), 1.5);
    sigam2_2 -= mu2_2;

    GaussianBlur(I1_I2, sigam12, Size(11, 11), 1.5);
    sigam12 -= mu1_mu2;
    Mat t1, t2, t3;
    t1 = 2 * mu1_mu2 + C1;
    t2 = 2 * sigam12 + C2;
    t3 = t1.mul(t2);

    t1 = mu1_2 + mu2_2 + C1;
    t2 = sigma1_2 + sigam2_2 + C2;
    t1 = t1.mul(t2);

    Mat ssim_map;
    divide(t3, t1, ssim_map);
    Scalar mssim = mean(ssim_map);

    double ssim = (mssim.val[0] + mssim.val[1] + mssim.val[2]) / 3;
    return ssim;
}

// 峰值信噪比,一种评价图像的客观标准,用来评估图像的保真性。
// 峰值信噪比经常用作图像压缩等领域中信号重建质量的测量方法,
// 它常简单地通过均方差(MSE)进行定义,使用两个m×n单色图像I和K。PSNR的单位为分贝
// PSNR值越大,就代表失真越少,图像压缩中典型的峰值信噪比值在 30 到 40dB 之间,小于30dB时考虑图像无法忍受。
double psnr(Mat& I1, Mat& I2) { //注意,当两幅图像一样时这个函数计算出来的psnr为0 
    Mat s1;
    absdiff(I1, I2, s1);
    s1.convertTo(s1, CV_32F);//转换为32位的float类型,8位不能计算平方  
    s1 = s1.mul(s1);
    Scalar s = sum(s1);  //计算每个通道的和  
    double sse = s.val[0] + s.val[1] + s.val[2];
    if (sse <= 1e-10) // for small values return zero  
        return 0;
    else
    {
        double mse = sse / (double)(I1.channels() * I1.total()); //  sse/(w*h*3)  
        double psnr = 10.0 * log10((255 * 255) / mse);
        return psnr;
    }
}

// 图像相似度对比
void demo2()
{
    VideoCapture cap;
    cap.open(0);

    if (!cap.isOpened())
    {
        cout << "Error0" << endl;
        return;
    }

    Mat frame;
    cap >> frame;

    Mat temp;
    cvtColor(frame, temp, COLOR_BGR2GRAY);
    imshow("temp", temp);

    while (cap.isOpened())
    {
        cap >> frame;
        if (frame.empty())
        {
            continue;
        }

        Mat gray;
        cvtColor(frame, gray, COLOR_BGR2GRAY);
        imshow("gray", gray);
        
        cout << "mse:" << mse(temp, gray) << "\tssim:" << ssim(temp, gray) << "\tpsnr:" << psnr(temp, gray) << endl;
        waitKey(200);
    }

    cap.release();
    destroyAllWindows();
}

void main()
{
    demo2();
}


Python版本代码如下:

python效果和C++差不多,这里就不进行图像演示了,感兴趣的童鞋,可以自己跑一下

# 需要安装 pip install Scikit-Image

# 老版本
# from skimage.measure import compare_ssim as ssim
# from skimage.measure import compare_mse as mse

# 新版本
from skimage.metrics import structural_similarity as ssim
from skimage.metrics import mean_squared_error as mse

import matplotlib.pyplot as plt
import numpy as np
import cv2


# 双摄像头调用
def demo1():
    cap0 = cv2.VideoCapture(0)
    cap1 = cv2.VideoCapture(1)
    ret = cap0.set(3, 320)
    ret = cap0.set(4, 240)
    ret = cap1.set(3, 320)
    ret = cap1.set(4, 240)

    while cap0.isOpened() and cap1.isOpened():
        ret0, frame0 = cap0.read()
        ret1, frame1 = cap1.read()

        if ret0:
            cv2.imshow('frame0', frame0)
            cv2.setWindowTitle('frame0', 'On Top')
        if ret1:
            cv2.imshow('frame1', frame1)
            # cv2.moveWindow('frame1', x=frame0.shape[1], y=0)
            cv2.moveWindow('frame1', x=320, y=40)

        key = cv2.waitKey(delay=2)
        if key == ord("q"):
            break

    # When everything done, release the capture
    cap0.release()
    cap1.release()
    cv2.destroyAllWindows()


# mse 算法
def my_mse(imageA, imageB):
    # the 'Mean Squared Error' between the two images is the
    # sum of the squared difference between the two images;
    # NOTE: the two images must have the same dimension
    err = np.sum((imageA.astype("float") - imageB.astype("float")) * 2)
    err /= float(imageA.shape[0] * imageA.shape[1])

    # return the MSE, the lower the error, the more "similar"
    # the two images are
    return err


# 图像相似度计算(感觉不咋滴)
def demo2():
    cap = cv2.VideoCapture(0)
    if cap.isOpened() == False:
        print('Open Camera Error.')
        return

    ret = cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 640)
    ret = cap.set(cv2.CAP_PROP_FRAME_WIDTH, 480)
    title = 'camera compare'
    plt.ion()

    ret, frame = cap.read()
    if ret == True:
        temp = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        cv2.imshow("temp", temp)
        while cap.isOpened():
            ret, frame = cap.read()
            if ret == True:
                gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
                cv2.imshow("gray", gray)

                m = mse(temp, gray)  # 均方误差(MSE)
                s = ssim(temp, gray)  # 结构相似度指数(SSIM)
                print("MSE: %.2f, SSIM: %.2f" % (m, s))
                temp = gray.copy()
                cv2.waitKey(500)


# 运行demo2,如果想运行其他demo,改一下这个即可
if __name__ == '__main__':
    # demo1()
    demo2()