1. 统计零件数量,可以采用腐蚀,但很多时候距离变换更适用
/**
统计零件的数量
*/
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
int main(){
// 图片路径
string path = "/home/kaijun/Pictures/lingjian.png";
//1. 读取图片
Mat src = imread(path,IMREAD_COLOR);
imshow("src",src);
//2. 将图片转成灰度
Mat gray;
cvtColor(src,gray,COLOR_BGR2GRAY);
// 将灰度图转成而制图,并且去掉背景
Mat binary;
threshold(gray,binary,0,255,THRESH_BINARY|THRESH_TRIANGLE);
imshow("no background",binary);
// 距离变换
Mat dist;
distanceTransform(binary,dist,DIST_L2,3);
normalize(dist, dist, 0, 1.0, NORM_MINMAX);
imshow("dist",dist);
// 将距离变换的结果,通过阈值让图像粘连断开
threshold(dist, dist, 0.5, 1.0, THRESH_BINARY);
imshow("dist2",dist);
// 统计图像的轮廓
vector<vector<Vec2i>> contours;
vector<Vec4i> hierarchy;
cout<<dist.type()<<endl;
dist.convertTo(dist,CV_8UC1); //数据类型一定要留意
findContours(dist,contours,hierarchy,RETR_LIST,CHAIN_APPROX_NONE);
//获得零件数量
cout<<contours.size()<<endl;
// 绘制轮廓
RNG rng(123456);
Mat result(dist.size(),CV_8UC3);
for (int i = 0; i < contours.size(); ++i) {
drawContours(result,contours,i,Scalar(rng.uniform(0,255),rng.uniform(0,255),rng.uniform(0,255)),-1);
}
imshow("result",result);
waitKey(0);
return 0;
}
2. 图像修复、鼠标绘制区域作为mask,注意鼠标事件的使用
void inpaint( InputArray src, InputArray inpaintMask,OutputArray dst, double inpaintRadius, int flags );
src : 表示输入的图像
inpaintMask: 掩膜,其实就是要修复哪些区域
dst : 表示修复输出的图像
inpaintRadius: 表示修复半径
flags: 表示修复时所使用的算法,有CV_INPAINT_TELEA和CV_INPAINT_NS可选,处理效果差不多
/**
图片修复
*/
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
Point prevPt;
Mat src,inpaintMask;
void onMouse(int event, int x, int y, int flags, void* userdata){
if (event == CV_EVENT_LBUTTONUP || !(flags & CV_EVENT_FLAG_LBUTTON))
prevPt = Point(-1, -1);
else if (event == CV_EVENT_LBUTTONDOWN) //鼠标按下
prevPt = Point(x, y);
//或 (flags & EVENT_FLAG_LBUTTON),鼠标移动并且左键被按下
else if (event == CV_EVENT_MOUSEMOVE && (flags & CV_EVENT_FLAG_LBUTTON))
{
Point pt(x, y);
if (prevPt.x < 0)
prevPt = pt;
line(inpaintMask, prevPt, pt, Scalar::all(255), 5, 8, 0);//mask
// 使用inpaint函数进行图片修复
inpaint(src, inpaintMask, src, 5, CV_INPAINT_TELEA);
prevPt = pt; // 更新坐标点
imshow("inpaint", src);
}
}
int main(int argc,char** argv){
string path = "/home/kaijun/Documents/resource/opencv/img/itheima_inpaint.jpg";
src = imread(path,IMREAD_COLOR);
// 定义窗口
namedWindow("inpaint",WINDOW_NORMAL);
// 定义鼠标事件
setMouseCallback("inpaint",onMouse);
// 显示原图
imshow("inpaint",src);
// 定义掩膜
inpaintMask = Mat(src.size(),CV_8UC1);
waitKey(0);
return 0;
}
3. 图像切边,注意矩形的绘制及抠图方法
/**
去除图片多余的背景
*/
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
void fetchROI(Mat &img);
int main(){
string path = "/home/kaijun/Documents/resource/opencv/01_qiebian.jpg";
Mat src = imread(path,IMREAD_COLOR);
// 显示图片
imshow("src",src);
// 提取感兴趣的区域
fetchROI(src);
waitKey(0);
return 0;
}
void fetchROI(Mat &img){
// 将彩色图转成灰度图
Mat gray(img.size(),CV_8UC1);
cvtColor(img,gray,COLOR_BGR2GRAY);
imshow("gray",gray);
// 将图像二值化处理
Mat binary(img.size(),CV_8UC1);
threshold(gray,binary,0,255,THRESH_BINARY_INV|THRESH_OTSU);
imshow("binary",binary);
// 查找轮廓
vector<vector<Vec2i>> contours;
vector<Vec4i> hierarchy;
findContours(binary,contours,hierarchy,CV_RETR_EXTERNAL,CV_CHAIN_APPROX_NONE);
RotatedRect resultRect;
// 遍历轮廓,求轮廓的最大外切矩形
for(int i = 0; i<contours.size();i++){
// 取到当前遍历轮廓
RotatedRect rect = minAreaRect(contours[i]);
if(rect.size.width > img.size().width*0.75){
// 绘制轮廓
//drawContours(img,contours,i,Scalar(0,255,255),2,LINE_AA);
// 记录外切矩形
resultRect = rect;
}
}
Mat dst = img(resultRect.boundingRect());
imshow("dst123",dst);
}
4. 图像的旋转和切边,注意图片旋转的方法,借助getRotationMatrix2D()函数生成旋转矩阵,再进行仿射变换
- 图像的旋转
// 打印图片需要旋转的角度
cout<<resultRect.angle<<endl;
// 先对图片进行旋转
Point2f center(img.cols/2,img.rows/2);
Mat matrix = getRotationMatrix2D(center,(resultRect.angle)+90,1);
warpAffine(img,outputImg,matrix,img.size());
- 图像的切边
/**
去除图片多余的背景,切边并且旋转
*/
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
void verifyRotation(Mat &img,Mat &outputImg);
void fetchROI(Mat &img);
int main(){
string path = "/home/kaijun/Documents/resource/opencv/02_qiebian.jpg";
Mat src = imread(path,IMREAD_COLOR);
// 显示图片
imshow("src",src);
Mat rotationImg;
verifyRotation(src,rotationImg);
fetchROI(rotationImg);
waitKey(0);
return 0;
}
//裁剪图片
void fetchROI(Mat &img){
// 将彩色图转成灰度图
Mat gray(img.size(),CV_8UC1);
cvtColor(img,gray,COLOR_BGR2GRAY);
imshow("gray",gray);
// 将图像二值化处理
Mat binary(img.size(),CV_8UC1);
threshold(gray,binary,0,255,THRESH_BINARY_INV|THRESH_OTSU);
imshow("binary",binary);
// 查找轮廓
vector<vector<Vec2i>> contours;
vector<Vec4i> hierarchy;
findContours(binary,contours,hierarchy,CV_RETR_EXTERNAL,CV_CHAIN_APPROX_NONE);
RotatedRect resultRect;
// 遍历轮廓,求轮廓的最大外切矩形
for(int i = 0; i<contours.size();i++){
// 取到当前遍历轮廓
RotatedRect rect = minAreaRect(contours[i]);
if(rect.size.width > img.size().width*0.75){
// 绘制轮廓
//drawContours(img,contours,i,Scalar(0,255,255),2,LINE_AA);
// 记录外切矩形
resultRect = rect;
}
}
Mat dst = img(resultRect.boundingRect());
imshow("dst123",dst);
}
//旋转图片
void verifyRotation(Mat &img,Mat &outputImg){
// 将彩色图转成灰度图
Mat gray(img.size(),CV_8UC1);
cvtColor(img,gray,COLOR_BGR2GRAY);
imshow("gray1",gray);
// 将图像二值化处理
Mat binary(img.size(),CV_8UC1);
threshold(gray,binary,0,255,THRESH_BINARY_INV|THRESH_OTSU);
imshow("binary1",binary);
// 查找轮廓
vector<vector<Vec2i>> contours;
vector<Vec4i> hierarchy;
findContours(binary,contours,hierarchy,CV_RETR_EXTERNAL,CV_CHAIN_APPROX_NONE);
RotatedRect resultRect;
// 遍历轮廓,求轮廓的最大外切矩形
for(int i = 0; i<contours.size();i++){
// 取到当前遍历轮廓
RotatedRect rect = minAreaRect(contours[i]);
if(rect.size.width > img.size().width*0.75){
// 绘制轮廓
//drawContours(img,contours,i,Scalar(255,0,255),2,LINE_AA);
// 记录外切矩形
resultRect = rect;
}
}
// 打印图片需要旋转的角度
cout<<resultRect.angle<<endl;
// 先对图片进行旋转
Point2f center(img.cols/2,img.rows/2);
Mat matrix = getRotationMatrix2D(center,(resultRect.angle)+90,1);
warpAffine(img,outputImg,matrix,img.size());
imshow("rotateimg",outputImg);
}
5. 直线检测,通过构造一行20列的结构元素将字母去掉,值保留横线
/**
检测填空题中的直线: 采用形态学方式
*/
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
Mat src,result;
int threshold_value=95;
int max_count=255;
void detectline(int,void*);
int main(){
string path = "/home/kaijun/Documents/resource/opencv/engline.jpg";
src = imread(path,IMREAD_COLOR);
imshow("src0",src);
detectline();
createTrackbar("threshold1","src",&threshold_value,max_count,detectline);
waitKey(0);
return 0;
}
void detectline(int ,void*){
// 将彩色图转成灰度图
Mat gray;
cvtColor(src,gray,COLOR_BGR2GRAY);
// 1. 将图像进行二值化处理
Mat binary;
threshold(gray,binary,0,255,THRESH_BINARY_INV|THRESH_OTSU);
imshow("binary",binary);
// 2. 形态学操作
Mat morph;
Mat kernel = getStructuringElement(MORPH_RECT, Size(20, 1));
morphologyEx(binary,morph,MORPH_OPEN,kernel);
imshow("morphology",morph);
// 3. 膨胀一下
Mat kernel2 = getStructuringElement(MORPH_RECT,Size(3,3));
dilate(morph,morph,kernel2);
imshow("morphology2",morph);
// 4. 寻找霍夫直线
vector<Vec4i> lines;
HoughLinesP(morph,lines,1,CV_PI/180,30,20,0);
for(int i=0; i < lines.size();i++){
Vec4i ln = lines[i];
line(src,Point(ln[0],ln[1]),Point(ln[2],ln[3]),Scalar(0,0,255),1);
}
imshow("src",src);
}
6. kmeans聚类换背景,注意,这里可以直接对三维数据进行聚类。证件照换背景示例如下:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
Scalar colors[]={
Scalar(255,0,0),
Scalar(255,255,0),
Scalar(0,0,255),
Scalar(255,0,255),
Scalar(0,255,0),
};
void useKmeans(Mat &src, Mat &distImg);
void changeBackground(Mat&src ,Mat &img,Vec3b color);
int main(int argc, char** argv){
//string path = "/home/kaijun/Documents/resource/opencv/zhengjianzhao.png";
//string path = "/home/kaijun/Pictures/zjz.jpeg";
string path = "/home/kaijun/Pictures/zjz3.jpg";
// 读取原图
Mat src = imread(path);
// 显示原图像
imshow("src",src);
// 创建一张空白的图片, 用于接收kmeans聚类结果
Mat distImg(src.size(),src.type());
// 使用聚类算法 对图像进行处理
useKmeans(src, distImg);
changeBackground(src,distImg,Vec3b(125,125,120));
waitKey(0);
return 0;
}
/**
* 更换背景
* @param img
* @param result
*/
void changeBackground(Mat &src, Mat &img,Vec3b color){
// 1. 提取到背景颜色值
Vec3b backgroundColor = img.at<Vec3b>(0,0);
// 2. 遍历背景,创建一个蒙版
Mat mask(img.size(),CV_8UC1);
for (int row = 0; row < img.rows; ++row) {
for (int col = 0; col < img.cols; ++col) {
Vec3b currentColor = img.at<Vec3b>(row,col);
if(currentColor == backgroundColor){
mask.at<uchar>(row,col) = 0;
}else{
mask.at<uchar>(row,col) = 255;
}
}
}
// 3. 形态学变换,去掉一些细小的边缘
erode(mask,mask,Mat::ones(3,3,CV_8UC1),Point(-1,-1),3);
// 创建一个蒙版
imshow("mask",mask);
// 5. 从原图中抠出人像
Mat copyImg;
src.copyTo(copyImg,mask); //利用掩膜拷贝图像
imshow("copy",copyImg);
// 6. 遍历抠出来的人像
for (int row = 0; row < copyImg.rows; ++row) {
for (int col = 0; col < copyImg.cols; ++col) {
int value = mask.at<uchar>(row,col);
if(value == 0){
copyImg.at<Vec3b>(row,col)=Vec3b(125,125,125);
}
}
}
imshow("result",copyImg);
}
void useKmeans(Mat &src, Mat &distImg) {// 封装数据点
int sampleCount = src.rows*src.cols;
// N 行 3列数据
Mat sampleData(sampleCount,src.channels(),CV_32F);
// 将图片的像素数据封装到样本数据中
for(int row = 0; row< src.rows;row++){
for(int col=0; col<src.cols;col++){
int index = row*src.cols + col;
// 从原图从取出颜色信息
Vec3b bgr = src.at<Vec3b>(row,col);
// 将数据填到数据列表中
sampleData.at<Vec3f>(index)=bgr;
}
}
// 调用kmeans函数
int clusterNum = 4;
Mat labels;
TermCriteria termCriteria(TermCriteria::EPS|TermCriteria::COUNT,10,0.1);
Mat centers;
kmeans(sampleData,clusterNum,labels,termCriteria,3,KMEANS_PP_CENTERS,centers);
//取出每一个样本数据,根据它的标签 填入颜色
for (int row = 0; row < src.rows; ++row) {
for (int col = 0; col < src.cols; ++col) {
int index = row*src.cols + col;
int label = labels.at<int>(index,0);
distImg.at<Vec3b>(row,col)[0] = colors[label][0];
distImg.at<Vec3b>(row,col)[1] = colors[label][1];
distImg.at<Vec3b>(row,col)[2] = colors[label][2];
}
}
imshow("kmeans",distImg);
}