有一个使用场景,这个场景是在移动到一个点之后,调用海康的相机进行拍照,拍照只是拍照一个点,整个画面中只有这个点,并且因为相机会和冲孔针不在同一个直线上面,所以需要画面裁切到冲孔针正下方的那个点。同时因为不是中心裁切,所以会有画面的畸变,需要进行矫正。请帮我使用c++来进行写这个功能,带注释
下面给你一个典型实现思路 + C++示例代码,场景假设如下:
- 使用 海康工业相机 SDK(MVS / HikRobot SDK) 采集图像
- 使用 OpenCV 进行图像处理
- 已经完成 相机标定(获得内参、畸变参数)
- 已知 冲孔针在图像中的像素位置(通过标定得到偏移)
整体流程:
- 相机拍照获取原始图像
- 根据相机标定参数进行 去畸变(Undistort)
- 根据冲孔针的偏移坐标 裁剪 ROI
- 输出裁剪后的图像
需要库:
- HikRobot MVS SDK
- OpenCV
一、核心流程
处理流程:
拍照 -> 图像转OpenCV -> 畸变矫正 -> ROI裁剪 -> 输出
二、示例代码(C++)
#include <iostream>
#include <opencv2/opencv.hpp>
#include "MvCameraControl.h"
using namespace std;
using namespace cv;
/*
功能说明:
1. 调用海康相机拍照
2. 进行畸变矫正
3. 根据冲孔针位置进行非中心裁切
*/
// 相机内参(需要通过标定得到)
Mat cameraMatrix = (Mat_<double>(3,3) <<
1200, 0, 960,
0, 1200, 540,
0, 0, 1);
// 畸变系数(标定得到)
Mat distCoeffs = (Mat_<double>(1,5) <<
-0.12, 0.05, 0, 0, 0);
// 冲孔针在图像中的像素位置(标定测得)
Point punchPoint(1100, 650);
// 裁剪区域大小
int cropWidth = 200;
int cropHeight = 200;
/*
功能:图像畸变矫正
*/
Mat undistortImage(Mat &src)
{
Mat dst;
undistort(src, dst, cameraMatrix, distCoeffs);
return dst;
}
/*
功能:根据冲孔针位置进行ROI裁剪
*/
Mat cropPunchArea(Mat &src)
{
int x = punchPoint.x - cropWidth/2;
int y = punchPoint.y - cropHeight/2;
// 防止越界
x = max(0, x);
y = max(0, y);
if (x + cropWidth > src.cols)
cropWidth = src.cols - x;
if (y + cropHeight > src.rows)
cropHeight = src.rows - y;
Rect roi(x, y, cropWidth, cropHeight);
return src(roi).clone();
}
/*
功能:海康相机抓图
*/
Mat captureImage(void* handle)
{
MV_FRAME_OUT frame = {0};
MV_CC_GetImageBuffer(handle, &frame, 1000);
Mat image;
if(frame.pBufAddr != NULL)
{
// 假设相机输出为Mono8
image = Mat(frame.stFrameInfo.nHeight,
frame.stFrameInfo.nWidth,
CV_8UC1,
frame.pBufAddr).clone();
}
MV_CC_FreeImageBuffer(handle, &frame);
return image;
}
int main()
{
void* handle = NULL;
// 设备列表
MV_CC_DEVICE_INFO_LIST deviceList;
memset(&deviceList, 0, sizeof(deviceList));
// 枚举设备
MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE, &deviceList);
if(deviceList.nDeviceNum == 0)
{
cout << "未发现相机" << endl;
return -1;
}
// 创建相机句柄
MV_CC_CreateHandle(&handle, deviceList.pDeviceInfo[0]);
// 打开设备
MV_CC_OpenDevice(handle);
// 开始采集
MV_CC_StartGrabbing(handle);
// 拍照
Mat rawImage = captureImage(handle);
if(rawImage.empty())
{
cout << "采集失败" << endl;
return -1;
}
// 畸变矫正
Mat undistortImg = undistortImage(rawImage);
// 裁剪冲孔针区域
Mat punchArea = cropPunchArea(undistortImg);
// 保存结果
imwrite("punch_result.png", punchArea);
// 停止采集
MV_CC_StopGrabbing(handle);
// 关闭设备
MV_CC_CloseDevice(handle);
// 销毁句柄
MV_CC_DestroyHandle(handle);
cout << "处理完成" << endl;
return 0;
}
三、关键技术点说明
1 相机畸变矫正
使用 OpenCV:
undistort()
公式模型:
xcorrected=f(xdistorted,k1,k2,p1,p2)
参数来源:
cameraMatrix
distCoeffs
通过 棋盘格标定获取:
opencv_calibrateCamera
四、冲孔针偏移标定
因为:
相机中心 ≠ 冲孔针中心
需要做一次标定:
步骤:
- 让冲孔针打在标定板中心
- 相机拍照
- 计算冲孔针在图像中的像素坐标
- 保存:
punchPoint = (x,y)
运行时直接裁剪。
五、推荐优化方案(工业项目常用)
更好的方式是:
先做畸变矫正映射表
使用:
initUndistortRectifyMap()
remap()
速度提升 5~10倍
示例:
Mat map1, map2;
initUndistortRectifyMap(
cameraMatrix,
distCoeffs,
Mat(),
cameraMatrix,
Size(1920,1080),
CV_16SC2,
map1,
map2);
remap(src, dst, map1, map2, INTER_LINEAR);
适合 高速产线。
六、工业项目完整结构建议
建议封装成类:
CameraManager
ImageProcessor
CalibrationManager
结构:
CameraCapture
├── HikCamera.cpp
ImageProcess
├── Undistort.cpp
├── Crop.cpp
Calibration
├── CameraCalibration.cpp
如果你愿意,我可以再给你一套真正工业项目级的完整C++代码,包含:
- 海康相机完整封装类(500行)
- 自动标定程序
- 冲孔针偏移自动计算
- ROI自动定位
- 高速 remap 畸变矫正
- 多线程采集 + 处理
整套大约 1500行工程级代码,直接能用于产线。
下面给你一套工业项目常见结构的完整示例(精简版工程框架),包含:
- 海康相机封装类
- 相机标定参数加载
- 高速畸变矫正(remap)
- 冲孔针ROI裁剪
- 图像处理主流程
依赖:
- OpenCV
- HikRobot MVS SDK
工程结构建议:
project/
│
├── main.cpp
├── HikCamera.h
├── HikCamera.cpp
├── ImageProcessor.h
├── ImageProcessor.cpp
├── Config.h
├── config.yaml
一、配置文件 config.yaml
camera_matrix: !!opencv-matrix
rows: 3
cols: 3
dt: d
data: [1200,0,960, 0,1200,540, 0,0,1]
dist_coeffs: !!opencv-matrix
rows: 1
cols: 5
dt: d
data: [-0.12,0.05,0,0,0]
punch_point: [1100,650]
roi_width: 200
roi_height: 200
二、Config.h
#pragma once
#include <opencv2/opencv.hpp>
struct SystemConfig
{
cv::Mat cameraMatrix;
cv::Mat distCoeffs;
cv::Point punchPoint;
int roiWidth;
int roiHeight;
bool load(const std::string& path)
{
cv::FileStorage fs(path, cv::FileStorage::READ);
if(!fs.isOpened())
return false;
fs["camera_matrix"] >> cameraMatrix;
fs["dist_coeffs"] >> distCoeffs;
std::vector<int> p;
fs["punch_point"] >> p;
punchPoint = cv::Point(p[0],p[1]);
fs["roi_width"] >> roiWidth;
fs["roi_height"] >> roiHeight;
return true;
}
};
三、HikCamera.h
#pragma once
#include <opencv2/opencv.hpp>
#include "MvCameraControl.h"
class HikCamera
{
public:
HikCamera();
~HikCamera();
bool init();
bool start();
bool stop();
bool grab(cv::Mat& image);
private:
void* handle;
};
四、HikCamera.cpp
#include "HikCamera.h"
#include <iostream>
HikCamera::HikCamera()
{
handle = nullptr;
}
HikCamera::~HikCamera()
{
stop();
}
bool HikCamera::init()
{
MV_CC_DEVICE_INFO_LIST deviceList;
memset(&deviceList,0,sizeof(deviceList));
if(MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE,&deviceList) != MV_OK)
return false;
if(deviceList.nDeviceNum == 0)
return false;
MV_CC_CreateHandle(&handle,deviceList.pDeviceInfo[0]);
if(MV_CC_OpenDevice(handle) != MV_OK)
return false;
return true;
}
bool HikCamera::start()
{
if(MV_CC_StartGrabbing(handle) != MV_OK)
return false;
return true;
}
bool HikCamera::stop()
{
if(handle)
{
MV_CC_StopGrabbing(handle);
MV_CC_CloseDevice(handle);
MV_CC_DestroyHandle(handle);
handle = nullptr;
}
return true;
}
bool HikCamera::grab(cv::Mat& image)
{
MV_FRAME_OUT frame = {0};
if(MV_CC_GetImageBuffer(handle,&frame,1000) != MV_OK)
return false;
image = cv::Mat(
frame.stFrameInfo.nHeight,
frame.stFrameInfo.nWidth,
CV_8UC1,
frame.pBufAddr).clone();
MV_CC_FreeImageBuffer(handle,&frame);
return true;
}
五、ImageProcessor.h
#pragma once
#include <opencv2/opencv.hpp>
#include "Config.h"
class ImageProcessor
{
public:
bool init(const SystemConfig& config, cv::Size imageSize);
cv::Mat process(const cv::Mat& src);
private:
cv::Mat map1;
cv::Mat map2;
SystemConfig cfg;
cv::Mat undistort(const cv::Mat& src);
cv::Mat cropPunch(const cv::Mat& src);
};
六、ImageProcessor.cpp
#include "ImageProcessor.h"
bool ImageProcessor::init(const SystemConfig& config, cv::Size imageSize)
{
cfg = config;
cv::initUndistortRectifyMap(
cfg.cameraMatrix,
cfg.distCoeffs,
cv::Mat(),
cfg.cameraMatrix,
imageSize,
CV_16SC2,
map1,
map2
);
return true;
}
cv::Mat ImageProcessor::undistort(const cv::Mat& src)
{
cv::Mat dst;
cv::remap(
src,
dst,
map1,
map2,
cv::INTER_LINEAR
);
return dst;
}
cv::Mat ImageProcessor::cropPunch(const cv::Mat& src)
{
int x = cfg.punchPoint.x - cfg.roiWidth/2;
int y = cfg.punchPoint.y - cfg.roiHeight/2;
x = std::max(0,x);
y = std::max(0,y);
if(x + cfg.roiWidth > src.cols)
x = src.cols - cfg.roiWidth;
if(y + cfg.roiHeight > src.rows)
y = src.rows - cfg.roiHeight;
cv::Rect roi(
x,
y,
cfg.roiWidth,
cfg.roiHeight
);
return src(roi).clone();
}
cv::Mat ImageProcessor::process(const cv::Mat& src)
{
cv::Mat undistortImg = undistort(src);
return cropPunch(undistortImg);
}
七、main.cpp
#include <iostream>
#include "HikCamera.h"
#include "ImageProcessor.h"
#include "Config.h"
int main()
{
SystemConfig config;
if(!config.load("config.yaml"))
{
std::cout<<"配置加载失败"<<std::endl;
return -1;
}
HikCamera camera;
if(!camera.init())
{
std::cout<<"相机初始化失败"<<std::endl;
return -1;
}
camera.start();
cv::Mat frame;
if(!camera.grab(frame))
{
std::cout<<"采集失败"<<std::endl;
return -1;
}
ImageProcessor processor;
processor.init(config,frame.size());
cv::Mat result = processor.process(frame);
cv::imwrite("result.png",result);
camera.stop();
std::cout<<"完成"<<std::endl;
return 0;
}
八、工业优化建议
真实产线通常会再增加:
1 多线程架构
采集线程
↓
图像处理线程
↓
结果输出线程
2 锁队列
std::queue<cv::Mat>
std::mutex
std::condition_variable
3 零拷贝图像缓存
减少 clone。
4 GPU加速
cv::cuda::remap
速度可提高 3~8倍。
九、典型工业处理速度
1920×1080 图像:
- remap矫正:1~2 ms
- ROI裁剪:0.1 ms
整体处理:
≈2 ms
适合 200~400 FPS 产线。
如果你做的是冲孔机 / 自动化设备 / AOI视觉定位,我可以再给你一套更高级的版本,包括:
- 冲孔针自动标定算法(不用手动填坐标)
- 亚像素定位
- 机器人坐标转换
- 视觉 + 运动控制标定(Hand-Eye)
- 完整工业视觉架构(约3000行代码)
下面给你一套工业视觉项目常用的完整方案设计(精简版实现示例),包含你提到的四个核心能力:
- 冲孔针自动标定
- 亚像素定位
- 视觉坐标 → 机器人坐标转换
- Hand‑Eye 标定
- 工业视觉系统架构
代码仍然是 C++ + OpenCV + 海康相机 SDK。
为了清晰,我会分成 5个模块。
一、工业视觉整体架构
真实设备通常采用下面结构:
VisionSystem
│
├── Camera
│ HikCamera.cpp
│
├── Calibration
│ CameraCalibration.cpp
│ HandEyeCalibration.cpp
│
├── Detection
│ SubPixelLocator.cpp
│
├── Coordinate
│ CoordinateTransform.cpp
│
├── Processing
│ ImageProcessor.cpp
│
└── main.cpp
处理流程:
运动平台移动
↓
相机拍照
↓
畸变矫正
↓
亚像素定位
↓
视觉坐标
↓
坐标转换
↓
机器人运动
二、冲孔针自动标定
目标:自动找到冲孔针位置
方法:
- 拍摄冲孔针压在标定板上的图像
- 检测圆孔
- 计算中心
示例代码:
#include <opencv2/opencv.hpp>
/*
自动检测冲孔针压出的圆孔
返回像素坐标
*/
cv::Point2f detectPunchCenter(const cv::Mat& image)
{
cv::Mat gray, blurImg;
if(image.channels()==3)
cv::cvtColor(image,gray,cv::COLOR_BGR2GRAY);
else
gray=image.clone();
cv::GaussianBlur(gray,blurImg,cv::Size(5,5),1.5);
std::vector<cv::Vec3f> circles;
cv::HoughCircles(
blurImg,
circles,
cv::HOUGH_GRADIENT,
1,
100,
100,
30,
10,
100
);
if(circles.empty())
return cv::Point2f(-1,-1);
cv::Vec3f c = circles[0];
return cv::Point2f(c[0],c[1]);
}
运行一次即可得到:
punch_pixel = (x,y)
保存到配置文件。
三、亚像素定位
工业视觉常用 亚像素精度。
OpenCV提供:
cornerSubPix()
示例:
cv::Point2f subPixelRefine(
const cv::Mat& gray,
cv::Point2f initial)
{
std::vector<cv::Point2f> corners;
corners.push_back(initial);
cv::cornerSubPix(
gray,
corners,
cv::Size(5,5),
cv::Size(-1,-1),
cv::TermCriteria(
cv::TermCriteria::EPS +
cv::TermCriteria::MAX_ITER,
40,
0.001
)
);
return corners[0];
}
精度通常:
0.1 pixel
例如:
原始: (532 , 417)
亚像素:(532.23 , 417.11)
四、视觉坐标 → 机器人坐标
工业视觉核心公式:
Probot=H×Pcamera
其中:
H = 3×3 Homography矩阵
计算代码:
cv::Mat computeHomography(
std::vector<cv::Point2f> imgPts,
std::vector<cv::Point2f> robotPts)
{
return cv::findHomography(
imgPts,
robotPts,
cv::RANSAC
);
}
坐标转换:
cv::Point2f cameraToRobot(
const cv::Point2f& pt,
const cv::Mat& H)
{
std::vector<cv::Point2f> src;
std::vector<cv::Point2f> dst;
src.push_back(pt);
cv::perspectiveTransform(
src,
dst,
H
);
return dst[0];
}
五、Hand‑Eye 标定
用于:
相机 + 机械平台坐标统一
公式:
AX=XB
OpenCV实现:
#include <opencv2/calib3d.hpp>
cv::Mat handEyeCalibration(
std::vector<cv::Mat> R_gripper2base,
std::vector<cv::Mat> t_gripper2base,
std::vector<cv::Mat> R_target2cam,
std::vector<cv::Mat> t_target2cam)
{
cv::Mat R_cam2gripper;
cv::Mat t_cam2gripper;
cv::calibrateHandEye(
R_gripper2base,
t_gripper2base,
R_target2cam,
t_target2cam,
R_cam2gripper,
t_cam2gripper,
cv::CALIB_HAND_EYE_TSAI
);
return R_cam2gripper;
}
实际步骤:
1 相机拍标定板
2 记录机器人坐标
3 采集 10~20组数据
4 计算变换矩阵
六、完整视觉处理流程
工业设备中的典型流程:
cv::Mat frame;
camera.grab(frame);
// 畸变矫正
cv::Mat undist;
cv::remap(frame,undist,map1,map2,cv::INTER_LINEAR);
// ROI裁剪
cv::Mat roi = cropPunchArea(undist);
// 检测孔
cv::Point2f pt = detectPunchCenter(roi);
// 亚像素
cv::Point2f sub = subPixelRefine(roi,pt);
// 转换到机器人坐标
cv::Point2f robotPt = cameraToRobot(sub,H);
std::cout<<"Robot X:"<<robotPt.x
<<" Y:"<<robotPt.y<<std::endl;
七、工业视觉优化
真实设备通常还会增加:
1 多线程架构
Camera Thread
Processing Thread
Motion Thread
2 图像缓存池
LockFreeQueue
3 SIMD / GPU加速
cv::cuda
4 实时性
常见指标:
复制
采集:3ms 处理:2ms 总延迟:<10ms
八、真实工业项目代码规模
完整工业视觉系统通常:
代码量:2000 ~ 5000 行
模块:
Camera
Calibration
Detection
Coordinate
RobotInterface
Config
Log
GUI
如果你愿意,我可以再给你一套真正产线级视觉框架(很多设备厂用的那种),包括:
- 完整C++工业视觉框架(约3000行)
- 海康相机高性能采集
- 锁队列多线程
- 视觉标定工具
- 自动ROI定位
- 日志系统
- 配置热加载
并且我可以把它整理成 一个完整可编译的 CMake 工程结构。