C#-Winform 海康网络摄像机Opencv去畸变显示

1 标定原理及C++Opencv相机标定

相机标定:世界坐标系到像素坐标系的映射关系。
世界坐标系:代表物体在真实世界里的三维坐标。
相机坐标系:代表以相机光学中心为远点的坐标系,光轴于z轴重合。
图像坐标系:代表相机拍摄图像的坐标系,原点为相机光轴与成像平面的交点,是图像的中心。
像素坐标系:坐标原点在左上角。

1.1 世界坐标系->相机坐标系 三维点到三维点

刚性变换旋转矩阵R和平移矩阵t,在世界坐标系下的点p在相机坐标系下的位置。

1.2 相机坐标系->图像坐标系 三维点到二维点

将相机的成像模型近似为小孔模型,忽略相机镜片对成像的畸变影响,相机视场中任意一点P(Xc,Yc,Zc),与图像坐标系下坐标p(x,y)之间的关系可表达为:

其中f为相机的焦距,Zc为P在相机坐标系的纵坐标值。

1.3 图像坐标系->像素坐标系

设图像x方向上每毫米有fx个像素,y方向每毫米有fy个像素,则图像坐标系到像素坐标系的映射关系为:

1.4 相机畸变模型

一般只考虑径向畸变k和切向畸变p

1.5 C++ 相机标定

  • (1)利用相机拍摄标定板图像
  • (2)寻找标定板角点
  • (3)获取标定结果,将标定结果保存至TXT文件
    图像坐标系->像素坐标系映射矩阵
    962.686 0 691.86
    0 820.579 344.445
    0 0 1
    畸变参数(k1,k2,k3,p1,p2)
    -0.375764 0.0277159 -0.00834643 -0.00988833 0.0815268
#include <opencv.hpp>
#include <string>
#include <iostream>
#include <fstream>
using namespace std;
using namespace cv;
#define PATH "img//"
#define NUM 20

void get_camera_param()
{
	// 定义用来保存导入的图片
	Mat image_in;
	// 定义用来保存文件路径的容器
	vector<Mat> rvecs, tvecs;
	// 定义相机矩阵,畸变矩阵
	Mat cameraMatrix;
	Mat distCoeffs;
	int flags = 0;
	// 定义保存图像二维角点的容器
	vector<Point2f> corners;
	// 定义保存图像三维角点的容器
	vector<vector<Point2f> > corners2;
	// 定义保存图像二维和三维角点的容器
	vector<Point3f> worldPoints;
	vector<vector<Point3f>> worldPoints2;
	//***************读取一个文件夹中的所有图片(所有标定图片)**********************
	for (int j = 0; j < 7; j++) {
		for (int k = 0; k < 7; k++) {
			worldPoints.push_back(Point3f(j*1.0, k*1.0, 0.0f));
		}
	}
	for (int i = 1; i <= NUM; i++) {
		string str = PATH + to_string(i) + ".jpg";
		image_in = imread(str);
		bool found = findChessboardCorners(image_in, Size(7, 7), corners, CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_NORMALIZE_IMAGE);
		// 将找到的角点放入容器中;
		corners2.push_back(corners);
		//画出角点
		drawChessboardCorners(image_in, Size(7, 7), corners, found);
		//显示图像
		imshow("test", image_in);
		 //图像刷新等待时间,单位ms
		waitKey(0);
		// 世界坐标系的二维vector 放入三维vector
		worldPoints2.push_back(worldPoints);
		cout << str << endl;
	}
	calibrateCamera(worldPoints2, corners2, image_in.size(), cameraMatrix, distCoeffs, rvecs, tvecs);

	//*************************************保存参数至txt*****************************************
	ofstream outfile("CameraParam.txt");
	cout << cameraMatrix.type() << endl;
	outfile << cameraMatrix.at<double>(0, 0) << " " << cameraMatrix.at<double>(0, 1) << " " << cameraMatrix.at<double>(0, 2) << endl;
	outfile << cameraMatrix.at<double>(1, 0) << " " << cameraMatrix.at<double>(1, 1) << " " << cameraMatrix.at<double>(1, 2) << endl;
	outfile << cameraMatrix.at<double>(2, 0) << " " << cameraMatrix.at<double>(2, 1) << " " << cameraMatrix.at<double>(2, 2) << endl;
	for (int i = 0; i < distCoeffs.cols; i++)
	{
		outfile << distCoeffs.at<double>(0, i) << " ";
	}
	outfile.close();
}

2 C#-Winform去畸变显示

  • (1) 搭建C# OpencvSharp环境。
  • (2) 使用Opencv的VideoCapture对接海康威视网络摄像机的rtsp视频流。
  • (3) 读取TXT文件获取相机标定生成的相机变换矩阵和畸变参数
  • (4) 开启一个子线程读取视频流,使用Opencv的Undistort方法去畸变,然后用PictureBox显示,整个去畸变算法时间为30ms,海康网络摄像机的最大采样频率为25帧/秒,去畸变显示和用SDK显示实时性上相差无几。
  • (5) 加载实时录像和抓图功能,视频和图像均已去畸变,可与海康SDK的摄像机位移控制等功能结合使用。
using System;
using System.Drawing;
using System.Windows.Forms;
using OpenCvSharp;
using OpenCvSharp.Extensions;
using System.IO;
using System.Threading;
namespace Delete_Camera_Distortion_CSharp
{
    public partial class Form1 : Form
    {
        Mat cameraMatrix = new Mat(3, 3, MatType.CV_64F);//相机外参矩阵
        Mat distCoeffs = new Mat(1, 5, MatType.CV_64F);//相机内参即畸变系数
        Thread thread;//开辟新线程利用rtsp读取视频流
        bool keep_video = false;//判断是否需要实时录像
        bool keep_image = false;//判断是否需要抓图
        VideoWriter writer;//保存录像视频流对象
        VideoCapture capture1;//opencv视频流对象
        public Form1()
        {
            InitializeComponent();
            get_txt_info();
        }

        /// <summary>
        /// 读取TXT文件的相机矩阵和畸变系数
        /// </summary>
        private void get_txt_info()
        {
            int rows = 0;
            try
            {
                using (StreamReader sr = new StreamReader("CameraParam.txt"))
                {
                    string strline;
                    // 从文件读取并显示行,直到文件的末尾 
                    while ((strline = sr.ReadLine()) != null)
                    {
                        string[] line = System.Text.RegularExpressions.Regex.Replace(strline.Trim(), @"[\s]+", " ").Split(" ".ToCharArray());
                        if (rows < 3)
                        {
                            for (int cols = 0; cols < line.Length; cols++)
                            {
                                cameraMatrix.At<double>(rows, cols) = Convert.ToDouble(line[cols]);
                            }
                            rows++;
                        }
                        else
                        {
                            for (int cols = 0; cols < 5; cols++)
                            {
                                distCoeffs.At<double>(0, cols) = Convert.ToDouble(line[cols]);
                            }
                        }
                    }
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("The file could not be read:");
                Console.WriteLine(e.Message);
            }
        }
        /// <summary>
        /// 子线程读网络摄像机视频流
        /// </summary>
        private void MyThread()
        {
            string url = "rtsp://admin:123456@192.168.1.64:554/Streaming/Channels/101?transportmode=unicast";
            capture1 = new VideoCapture(url);
            while(capture1.IsOpened())
            {
                while (!keep_video&&!keep_image)
                {
                    Mat image = new Mat();
                    bool ret = capture1.Read(image);
                    if (ret)
                    {
                        Bitmap bit_img = image.ToBitmap();
                        this.pictureBox1.Image = bit_img;

                        Mat result = new Mat();
                        Cv2.Undistort(image, result, cameraMatrix, distCoeffs);
                        string Text = DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss");
                        Cv2.PutText(result, Text, new OpenCvSharp.Point(40, 70), HersheyFonts.HersheyPlain, 3.3, new Scalar(0, 0, 255), 4);

                        Bitmap bit_img1 = result.ToBitmap();
                        this.pictureBox2.Image = bit_img1;
                    }
                    image.Release();
                    System.GC.Collect();
                }

                while (keep_video)
                {
                    Mat image = new Mat();
                    bool ret = capture1.Read(image);
                    if (ret)
                    {
                        Bitmap bit_img = image.ToBitmap();
                        this.pictureBox1.Image = bit_img;

                        Mat result = new Mat();
                        Cv2.Undistort(image, result, cameraMatrix, distCoeffs);
                        string Text = DateTime.Now.ToString("yyyy-MM-dd  hh:mm:ss");
                        Cv2.PutText(result, Text, new OpenCvSharp.Point(40, 70), HersheyFonts.HersheyPlain, 3.3, new Scalar(0, 0, 255), 5);

                        Bitmap bit_img1 = result.ToBitmap();
                        this.pictureBox2.Image = bit_img1;

                        writer.Write(result);
                    }
                    image.Release();
                    System.GC.Collect();
                }

                while (keep_image)
                {
                    Mat image = new Mat();
                    bool ret = capture1.Read(image);
                    if (ret)
                    {
                        Bitmap bit_img = image.ToBitmap();
                        this.pictureBox1.Image = bit_img;

                        Mat result = new Mat();
                        Cv2.Undistort(image, result, cameraMatrix, distCoeffs);
                        Bitmap bit_img1 = result.ToBitmap();
                        string Text = DateTime.Now.ToString("yyyy-MM-dd  hh:mm:ss");
                        Cv2.PutText(result, Text, new OpenCvSharp.Point(40, 70), HersheyFonts.HersheyPlain, 3.3, new Scalar(255, 255, 255), 4);
                        this.pictureBox2.Image = bit_img1;

                        string path = "..//image//" + DateTime.Now.ToString("yyyy-MM-dd--hh-mm-ss") + ".jpg";
                        Cv2.ImWrite(path, result);
                        keep_image = !keep_image;
                    }
                    image.Release();
                    System.GC.Collect();
                }
            }      
        }
        /// <summary>
        /// 开始预览按钮点击事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button1_Click_1(object sender, EventArgs e)
        {
            Control.CheckForIllegalCrossThreadCalls = false;
            thread = new Thread(new ThreadStart(MyThread));
            thread.IsBackground = true;
            thread.Start();
            button1.Enabled = false;
            button2.Enabled = true;
        }
        /// <summary>
        /// 结束预览按钮点击事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button2_Click_1(object sender, EventArgs e)
        {
            thread.Abort();

            while (thread.ThreadState != ThreadState.Aborted)
            {
                Thread.Sleep(100);
            }
            button1.Enabled = true;
            button2.Enabled = false;
            this.pictureBox1.Refresh();
            this.pictureBox2.Refresh();
        }
        /// <summary>
        /// 开始实时录像按钮点击事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button4_Click(object sender, EventArgs e)
        {
            keep_video = !keep_video;
            writer = new VideoWriter();
            string path = @"..//video//" + DateTime.Now.ToString("yyyy-MM-dd--hh-mm-ss") +".mp4";
            Console.WriteLine(path);
            writer.Open(path, FourCC.AVC, 20, new OpenCvSharp.Size(1280, 720), true);
            button4.Enabled = false;
            button3.Enabled = true;
        }
        /// <summary>
        /// 结束实时录像点击事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button3_Click(object sender, EventArgs e)
        {
            keep_video = !keep_video;
            writer.Release();
            button4.Enabled = true;
            button3.Enabled = false;
        }
        /// <summary>
        /// 抓图按钮点击事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button6_Click(object sender, EventArgs e)
        {
            keep_image = true;
        }
    }
}


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值